Initial repo created
authorNik Okuntseff <support@anuko.com>
Mon, 29 Feb 2016 04:10:38 +0000 (20:10 -0800)
committerNik Okuntseff <support@anuko.com>
Mon, 29 Feb 2016 04:10:38 +0000 (20:10 -0800)
Populated a repo with hopefully all needed files.

430 files changed:
WEB-INF/config.php.dist [new file with mode: 0644]
WEB-INF/lib/Auth.class.php [new file with mode: 0644]
WEB-INF/lib/DateAndTime.class.php [new file with mode: 0644]
WEB-INF/lib/I18n.class.php [new file with mode: 0644]
WEB-INF/lib/Period.class.php [new file with mode: 0644]
WEB-INF/lib/PieChartEx.class.php [new file with mode: 0644]
WEB-INF/lib/auth/Auth_db.class.php [new file with mode: 0644]
WEB-INF/lib/auth/Auth_ldap.class.php [new file with mode: 0644]
WEB-INF/lib/common.lib.php [new file with mode: 0644]
WEB-INF/lib/form/ActionErrors.class.php [new file with mode: 0644]
WEB-INF/lib/form/ActionForm.class.php [new file with mode: 0644]
WEB-INF/lib/form/Calendar.class.php [new file with mode: 0644]
WEB-INF/lib/form/Checkbox.class.php [new file with mode: 0644]
WEB-INF/lib/form/CheckboxCellRenderer.class.php [new file with mode: 0644]
WEB-INF/lib/form/CheckboxGroup.class.php [new file with mode: 0644]
WEB-INF/lib/form/Combobox.class.php [new file with mode: 0644]
WEB-INF/lib/form/DateField.class.php [new file with mode: 0644]
WEB-INF/lib/form/DefaultCellRenderer.class.php [new file with mode: 0644]
WEB-INF/lib/form/FloatField.class.php [new file with mode: 0644]
WEB-INF/lib/form/Form.class.php [new file with mode: 0644]
WEB-INF/lib/form/FormElement.class.php [new file with mode: 0644]
WEB-INF/lib/form/Hidden.class.php [new file with mode: 0644]
WEB-INF/lib/form/Submit.class.php [new file with mode: 0644]
WEB-INF/lib/form/Table.class.php [new file with mode: 0644]
WEB-INF/lib/form/TableColumn.class.php [new file with mode: 0644]
WEB-INF/lib/form/TextArea.class.php [new file with mode: 0644]
WEB-INF/lib/form/TextField.class.php [new file with mode: 0644]
WEB-INF/lib/form/UploadFile.class.php [new file with mode: 0644]
WEB-INF/lib/html/HttpRequest.class.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/libchart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/model/DataSet.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/model/Point.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/model/XYDataSet.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/axis/Axis.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/axis/Bound.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/caption/Caption.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/BarChart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/Chart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/LineChart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/PieChart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/color/Color.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/color/ColorSet.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/color/Palette.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/plot/Plot.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/primitive/Padding.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/primitive/Primitive.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php [new file with mode: 0644]
WEB-INF/lib/libchart/classes/view/text/Text.php [new file with mode: 0644]
WEB-INF/lib/libchart/fonts/DejaVuSansCondensed-Bold.ttf [new file with mode: 0644]
WEB-INF/lib/libchart/fonts/DejaVuSansCondensed.ttf [new file with mode: 0644]
WEB-INF/lib/mail/Mailer.class.php [new file with mode: 0644]
WEB-INF/lib/pear/INSTALL [new file with mode: 0644]
WEB-INF/lib/pear/LICENSE [new file with mode: 0644]
WEB-INF/lib/pear/MDB2.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Date.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Datatype/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Datatype/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Datatype/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Function/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Function/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Function/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Manager/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Manager/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Manager/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Native/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Native/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Native/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Reverse/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Reverse/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/Reverse/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/mysql.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Driver/mysqli.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Extended.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/Iterator.php [new file with mode: 0644]
WEB-INF/lib/pear/MDB2/LOB.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/RFC822.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/mail.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/mock.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/null.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/sendmail.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/smtp.php [new file with mode: 0644]
WEB-INF/lib/pear/Mail/smtpmx.php [new file with mode: 0644]
WEB-INF/lib/pear/Net/SMTP.php [new file with mode: 0644]
WEB-INF/lib/pear/Net/Socket.php [new file with mode: 0644]
WEB-INF/lib/pear/OS/Guess.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Autoloader.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Builder.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/ChannelFile.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Auth.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Auth.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Build.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Build.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Channels.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Channels.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Config.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Config.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Install.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Install.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Mirror.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Mirror.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Package.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Package.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Pickle.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Pickle.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Registry.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Registry.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Remote.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Remote.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Test.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Command/Test.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Config.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Dependency2.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/DependencyDB.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Downloader.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Downloader/Package.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/ErrorStack.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Exception.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Frontend.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Frontend/CLI.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Data.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Data.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Doc.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Ext.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Php.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Php.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Script.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Script.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Src.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Src.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Test.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Test.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Www.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Installer/Role/Www.xml [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/v1.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/v2.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Packager.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/REST.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/REST/10.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/REST/11.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/REST/13.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Registry.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/RunTest.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Common.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Replace.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Replace/rw.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Unixeol.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Windowseol.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Validate.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/Validator/PECL.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR/XMLParser.php [new file with mode: 0644]
WEB-INF/lib/pear/PEAR5.php [new file with mode: 0644]
WEB-INF/lib/pear/README [new file with mode: 0644]
WEB-INF/lib/pear/System.php [new file with mode: 0644]
WEB-INF/lib/pear/package.dtd [new file with mode: 0644]
WEB-INF/lib/pear/readme_pear_integration.txt [new file with mode: 0644]
WEB-INF/lib/pear/scripts/pear.bat [new file with mode: 0644]
WEB-INF/lib/pear/scripts/pear.sh [new file with mode: 0644]
WEB-INF/lib/pear/scripts/pearcmd.php [new file with mode: 0644]
WEB-INF/lib/pear/scripts/peardev.bat [new file with mode: 0644]
WEB-INF/lib/pear/scripts/peardev.sh [new file with mode: 0644]
WEB-INF/lib/pear/scripts/pecl.bat [new file with mode: 0644]
WEB-INF/lib/pear/scripts/pecl.sh [new file with mode: 0644]
WEB-INF/lib/pear/scripts/peclcmd.php [new file with mode: 0644]
WEB-INF/lib/pear/template.spec [new file with mode: 0644]
WEB-INF/lib/smarty/Smarty.class.php [new file with mode: 0644]
WEB-INF/lib/smarty/debug.tpl [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/block.php.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/block.textformat.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.counter.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.cycle.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.fetch.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_checkboxes.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_image.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_options.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_radios.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_select_date.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_select_time.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.html_table.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.mailto.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/function.math.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.capitalize.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.date_format.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.debug_print_var.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.escape.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.regex_replace.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.replace.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.spacify.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifier.truncate.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.cat.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.count_characters.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.count_paragraphs.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.count_sentences.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.count_words.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.default.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.indent.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.lower.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.noprint.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.string_format.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.strip.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.strip_tags.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.upper.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/modifiercompiler.wordwrap.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/outputfilter.trimwhitespace.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/shared.make_timestamp.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php [new file with mode: 0644]
WEB-INF/lib/smarty/plugins/variablefilter.htmlspecialchars.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_append.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_assign.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_break.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_call.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_capture.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_config_load.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_continue.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_debug.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_eval.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_extends.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_if.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_ldelim.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_nocache.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_block_plugin.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_function_plugin.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_block_function.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_function.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_block.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_function.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_rdelim.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_section.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_while.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_compilebase.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_config.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_configfilelexer.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_configfileparser.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_filter.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_filter_handler.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_function_call_handler.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_get_include_path.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_nocache_insert.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_register.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_eval.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_extends.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_file.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_php.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_string.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_utility.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_wrapper.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_internal_write_file.php [new file with mode: 0644]
WEB-INF/lib/smarty/sysplugins/smarty_security.php [new file with mode: 0644]
WEB-INF/lib/tdcron/class.tdcron.entry.php [new file with mode: 0644]
WEB-INF/lib/tdcron/class.tdcron.php [new file with mode: 0644]
WEB-INF/lib/ttChartHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttClientHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttCustomFieldHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttExpenseHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttExportHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttFavReportHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttImportHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttInvoiceHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttNotificationHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttProjectHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttReportHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttSysConfig.class.php [new file with mode: 0644]
WEB-INF/lib/ttTaskHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttTeamHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttTimeHelper.class.php [new file with mode: 0644]
WEB-INF/lib/ttUser.class.php [new file with mode: 0644]
WEB-INF/lib/ttUserHelper.class.php [new file with mode: 0644]
WEB-INF/resources/ca.lang.php [new file with mode: 0644]
WEB-INF/resources/cs.lang.php [new file with mode: 0644]
WEB-INF/resources/da.lang.php [new file with mode: 0644]
WEB-INF/resources/de.lang.php [new file with mode: 0644]
WEB-INF/resources/en.lang.php [new file with mode: 0644]
WEB-INF/resources/es.lang.php [new file with mode: 0644]
WEB-INF/resources/et.lang.php [new file with mode: 0644]
WEB-INF/resources/fa.lang.php [new file with mode: 0644]
WEB-INF/resources/fi.lang.php [new file with mode: 0644]
WEB-INF/resources/fr.lang.php [new file with mode: 0644]
WEB-INF/resources/he.lang.php [new file with mode: 0644]
WEB-INF/resources/hu.lang.php [new file with mode: 0644]
WEB-INF/resources/is.lang.php [new file with mode: 0644]
WEB-INF/resources/it.lang.php [new file with mode: 0644]
WEB-INF/resources/ja.lang.php [new file with mode: 0644]
WEB-INF/resources/ko.lang.php [new file with mode: 0644]
WEB-INF/resources/nl.lang.php [new file with mode: 0644]
WEB-INF/resources/no.lang.php [new file with mode: 0644]
WEB-INF/resources/pl.lang.php [new file with mode: 0644]
WEB-INF/resources/pt.lang.php [new file with mode: 0644]
WEB-INF/resources/ro.lang.php [new file with mode: 0644]
WEB-INF/resources/ru.lang.php [new file with mode: 0644]
WEB-INF/resources/sk.lang.php [new file with mode: 0644]
WEB-INF/resources/sl.lang.php [new file with mode: 0644]
WEB-INF/resources/sv.lang.php [new file with mode: 0644]
WEB-INF/resources/tr.lang.php [new file with mode: 0644]
WEB-INF/resources/zh-cn.lang.php [new file with mode: 0644]
WEB-INF/resources/zh-tw.lang.php [new file with mode: 0644]
WEB-INF/templates/access_denied.tpl [new file with mode: 0644]
WEB-INF/templates/admin_options.tpl [new file with mode: 0644]
WEB-INF/templates/admin_team_add.tpl [new file with mode: 0644]
WEB-INF/templates/admin_team_delete.tpl [new file with mode: 0644]
WEB-INF/templates/admin_team_edit.tpl [new file with mode: 0644]
WEB-INF/templates/admin_teams.tpl [new file with mode: 0644]
WEB-INF/templates/cf_custom_field_add.tpl [new file with mode: 0644]
WEB-INF/templates/cf_custom_field_delete.tpl [new file with mode: 0644]
WEB-INF/templates/cf_custom_field_edit.tpl [new file with mode: 0644]
WEB-INF/templates/cf_custom_fields.tpl [new file with mode: 0644]
WEB-INF/templates/cf_dropdown_option_add.tpl [new file with mode: 0644]
WEB-INF/templates/cf_dropdown_option_delete.tpl [new file with mode: 0644]
WEB-INF/templates/cf_dropdown_option_edit.tpl [new file with mode: 0644]
WEB-INF/templates/cf_dropdown_options.tpl [new file with mode: 0644]
WEB-INF/templates/charts.tpl [new file with mode: 0644]
WEB-INF/templates/client_add.tpl [new file with mode: 0644]
WEB-INF/templates/client_delete.tpl [new file with mode: 0644]
WEB-INF/templates/client_edit.tpl [new file with mode: 0644]
WEB-INF/templates/clients.tpl [new file with mode: 0644]
WEB-INF/templates/datetime_format_preview.tpl [new file with mode: 0644]
WEB-INF/templates/expense_delete.tpl [new file with mode: 0644]
WEB-INF/templates/expense_edit.tpl [new file with mode: 0644]
WEB-INF/templates/expenses.tpl [new file with mode: 0644]
WEB-INF/templates/export.tpl [new file with mode: 0644]
WEB-INF/templates/footer.tpl [new file with mode: 0644]
WEB-INF/templates/header.tpl [new file with mode: 0644]
WEB-INF/templates/import.tpl [new file with mode: 0644]
WEB-INF/templates/index.tpl [new file with mode: 0644]
WEB-INF/templates/invoice_add.tpl [new file with mode: 0644]
WEB-INF/templates/invoice_delete.tpl [new file with mode: 0644]
WEB-INF/templates/invoice_view.tpl [new file with mode: 0644]
WEB-INF/templates/invoices.tpl [new file with mode: 0644]
WEB-INF/templates/login.db.tpl [new file with mode: 0644]
WEB-INF/templates/login.ldap.tpl [new file with mode: 0644]
WEB-INF/templates/login.tpl [new file with mode: 0644]
WEB-INF/templates/mail.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/access_denied.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/header.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/index.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/login.db.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/login.ldap.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/login.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/time.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/time_delete.tpl [new file with mode: 0644]
WEB-INF/templates/mobile/time_edit.tpl [new file with mode: 0644]
WEB-INF/templates/notification_add.tpl [new file with mode: 0644]
WEB-INF/templates/notification_delete.tpl [new file with mode: 0644]
WEB-INF/templates/notification_edit.tpl [new file with mode: 0644]
WEB-INF/templates/notifications.tpl [new file with mode: 0644]
WEB-INF/templates/password_change.tpl [new file with mode: 0644]
WEB-INF/templates/password_reset.tpl [new file with mode: 0644]
WEB-INF/templates/profile_edit.tpl [new file with mode: 0644]
WEB-INF/templates/project_add.tpl [new file with mode: 0644]
WEB-INF/templates/project_delete.tpl [new file with mode: 0644]
WEB-INF/templates/project_edit.tpl [new file with mode: 0644]
WEB-INF/templates/projects.tpl [new file with mode: 0644]
WEB-INF/templates/register.tpl [new file with mode: 0644]
WEB-INF/templates/report.tpl [new file with mode: 0644]
WEB-INF/templates/reports.tpl [new file with mode: 0644]
WEB-INF/templates/task_add.tpl [new file with mode: 0644]
WEB-INF/templates/task_delete.tpl [new file with mode: 0644]
WEB-INF/templates/task_edit.tpl [new file with mode: 0644]
WEB-INF/templates/tasks.tpl [new file with mode: 0644]
WEB-INF/templates/time.tpl [new file with mode: 0644]
WEB-INF/templates/time_delete.tpl [new file with mode: 0644]
WEB-INF/templates/time_edit.tpl [new file with mode: 0644]
WEB-INF/templates/user_add.tpl [new file with mode: 0644]
WEB-INF/templates/user_delete.tpl [new file with mode: 0644]
WEB-INF/templates/user_edit.tpl [new file with mode: 0644]
WEB-INF/templates/users.tpl [new file with mode: 0644]
WEB-INF/templates_c/keepme [new file with mode: 0644]
images/1x1.gif [new file with mode: 0644]
images/calendar.gif [new file with mode: 0644]
images/subm_bg.gif [new file with mode: 0644]
images/top_bg.gif [new file with mode: 0644]
images/tt_logo.png [new file with mode: 0644]
js/strftime.js [new file with mode: 0644]
js/strptime.js [new file with mode: 0644]
mobile/access_denied.php [new file with mode: 0644]
mobile/index.php [new file with mode: 0644]
mobile/login.php [new file with mode: 0644]
mobile/time.php [new file with mode: 0644]
mobile/time_delete.php [new file with mode: 0644]
mobile/time_edit.php [new file with mode: 0644]
plugins/CustomFields.class.php [new file with mode: 0644]

diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist
new file mode 100644 (file)
index 0000000..3ab2357
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+
+// Set include path for PEAR and its modules, which we include in the distribution.
+//
+set_include_path(realpath(dirname(__FILE__).'/lib/pear') . PATH_SEPARATOR . get_include_path());
+
+
+// Database connection parameters.
+//
+// CHANGE 3 PARAMETERS HERE!
+// In this example: "root" is username, "no" is password, "dbname" is database name.
+//
+define('DSN', 'mysqli://root:no@localhost/dbname?charset=utf8');
+// Do NOT change charset unless you upgraded from an older Time Tracker where charset was NOT specified
+// and now you see some corrupted characters. See http://dev.mysql.com/doc/refman/5.0/en/charset-mysql.html
+
+
+// MULTITEAM_MODE option defines whether users can create their own teams.
+//
+// Available values are true or false.
+// If true users can create their own teams.
+// If false only admin can create teams.
+//
+define('MULTITEAM_MODE', true);
+
+
+// Application name.
+// If you install time tracker into a sub-directory of your site reflect this in the APP_NAME parameter.
+// For example, for http://localhost/timetracker/ define APP_NAME as 'timetracker'.
+//
+// define('APP_NAME', 'timetracker');
+//
+define('APP_NAME', '');
+
+
+// OLD_PASSWORDS
+//
+// You may need to set this option if you migrate an older version of TT installation to a new server.
+// Older versions of TT used mysql password function to hash user passwords. Newer versions use md5.
+// Because the password function may behave differently between servers, the OLD_PASSWORD settings
+// gives you an opportunity to control it, if needed.
+//  
+// define('OLD_PASSWORDS', true);
+
+
+// Holidays. At this time holiday days are defined in the localization files (one file per language).
+// The SHOW_HOLIDAYS option defines whether holidays are highlighted with holiday color.
+//
+define('SHOW_HOLIDAYS', true);
+
+
+// COST_ON_REPORTS - defines the availability of the Cost field on the Reports page.
+//
+define('COST_ON_REPORTS', true);
+
+
+// READONLY_START_FINISH - defines whether the start and finish fields on time entry pages are read-only.
+// This applies to regular users only. Manager and co-managers can edit these values.
+//
+// define('READONLY_START_FINISH', false);
+
+// FUTURE_ENTRIES - defines whether users can create entries for future dates. Defaults to true.
+//
+// define('FUTURE_ENTRIES', false);
+
+
+// WEEKEND_START_DAY
+//
+// This option defines which days are highlighted with weekend color.
+// 6 means Saturday. For Saudi Arabia, etc. set it to 4 for Thursday and Friday to be weekend days.
+//
+define('WEEKEND_START_DAY', 6);
+
+
+// PHPSESSID_TTL
+//
+// Lifetime in seconds for tt_PHPSESSID cookie. Time to live is extended by this value
+// with each visit to the site so that users don't have to re-login. 
+// define('PHPSESSID_TTL', 86400);
+
+
+// Forum and help links from the main menu.
+//
+define('FORUM_LINK', 'https://www.anuko.com/forum/viewforum.php?f=4');
+define('HELP_LINK', 'https://www.anuko.com/time_tracker/user_guide/index.htm');
+
+
+// Default sender for mail.
+//
+define('SENDER', '"Anuko Time Tracker" <no-reply@timetracker.anuko.com>');
+
+
+// MAIL_MODE - mail sending mode. Can be 'mail' or 'smtp'.
+// 'mail' - sending through php mail() function.
+// 'smtp' - sending directly through SMTP server.
+// See https://www.anuko.com/time_tracker/install_guide/mail.htm
+//
+define('MAIL_MODE', 'smtp');
+define('MAIL_SMTP_HOST', 'localhost'); // For gmail use 'ssl://smtp.gmail.com' instead of 'localhost' and port 465.
+// define('MAIL_SMTP_PORT', '465');
+// define('MAIL_SMTP_USER', 'yourname@yourdomain.com');
+// define('MAIL_SMTP_PASSWORD', 'yourpassword');
+// define('MAIL_SMTP_AUTH', true);
+// define('MAIL_SMTP_DEBUG', true);
+
+
+// CSS files. They are located in the root of Time Tracker installation.
+//
+define('DEFAULT_CSS', 'default.css');
+define('RTL_CSS', 'rtl.css'); // For right to left languages.
+
+
+// Default date format. Behaviour with not included formats is undefined. Possible values:
+// '%Y-%m-%d'
+// '%m/%d/%Y'
+// '%d.%m.%Y'
+// '%d.%m.%Y %a'
+define('DATE_FORMAT_DEFAULT', '%Y-%m-%d');
+
+
+// Default time format. Behaviour with not included formats is undefined. Possible values:
+// '%H:%M'
+// '%I:%M %p'
+define('TIME_FORMAT_DEFAULT', '%H:%M');
+
+
+// Default week start day.
+// Possible values: 0 - 6. 0 means Sunday.
+//
+define('WEEK_START_DEFAULT', 0);
+
+
+// Default language of the application.
+// Possible values: en, fr, nl, etc. Empty string means the language is defined by user browser.
+// 
+define('LANG_DEFAULT', '');
+
+
+// Default currency symbol. Use €, £, a more specific dollar like US$, CAD, etc.
+// 
+define('CURRENCY_DEFAULT', '$');
+
+
+// EXPORT_DECIMAL_DURATION - defines whether time duration values are decimal in CSV and XML data exports (1.25 vs 1:15).
+// 
+define('EXPORT_DECIMAL_DURATION', true);
+
+
+// Authentication module (see WEB-INF/lib/auth/)
+// Possible authentication methods:
+//   db - internal database, logins and password hashes are stored in time tracker database.
+//   ldap - authentication against an LDAP directory such as OpenLDAP or Windows Active Directory.
+define('AUTH_MODULE', 'db');
+
+// LDAP authentication examples.
+// Go to https://www.anuko.com/time_tracker/install_guide/ldap_auth/index.htm for detailed configuration instructions.
+
+// Configuration example for OpenLDAP server:
+// define('AUTH_MODULE', 'ldap');
+// $GLOBALS['AUTH_MODULE_PARAMS'] = array(
+//  'server' => '127.0.0.1',                    // OpenLDAP server address or name.
+//  'type' => 'openldap',                       // Type of server. openldap type should also work with Sun Directory Server when member_of is empty.
+                                                // It may work with other (non Windows AD) LDAP servers. For Windows AD use the 'ad' type.
+//  'base_dn' => 'ou=People,dc=example,dc=com', // Base distinguished name in LDAP catalog.
+//  'default_domain' => 'example.com',          // Default domain.
+//  'member_of' => array());                    // List of groups, membership in which is required for user to be authenticated.
+
+
+// Configuration example for Windows domains with Active Directory:
+// define('AUTH_MODULE', 'ldap');
+// $GLOBALS['AUTH_MODULE_PARAMS'] = array(
+//  'server' => '127.0.0.1',            // Domain controller IP address or name.
+//  'type' => 'ad',                     // Type of server.
+//  'base_dn' => 'DC=example,DC=com',   // Base distinguished name in LDAP catalog.
+//  'default_domain' => 'example.com',  // Default domain.
+//  'member_of' => array());            // List of groups, membership in which is required for user to be authenticated.
+
+// define('AUTH_DEBUG', false); // Note: enabling AUTH_DEBUG breaks redirects as debug output is printed before setting redirect header. Do not enable on production systems.
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/Auth.class.php b/WEB-INF/lib/Auth.class.php
new file mode 100644 (file)
index 0000000..243eed5
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class Auth {
+
+  // isAuthenticated - checks authentication status for user.
+  function isAuthenticated() {
+    if (isset($_SESSION['authenticated'])) {
+// This check does not work properly because we are not getting here. Need to improve.
+//        if (!isset($_COOKIE['tt_login'])) {
+//          die ("Your browser's cookie functionality is turned off. Please turn it on.");
+//        }
+
+      $GLOBALS['SMARTY']->assign('authenticated', true); // Used in header.tpl for menu display.
+      return true;
+    }
+    session_write_close();
+    return false;
+  }
+
+  /**
+   * authenticate - main function for authentication. Returns an array with 'login' key set to login
+   * and other values depending on the underlying authentication module.
+   * Returns false if error. For actual implementation see classes in WEB-INF/lib/auth/.
+   */
+  function authenticate($login, $password)
+  {
+    return false;
+  }
+  
+  // isPasswordExternal - returns true if actual password is not stored in the internal DB.
+  function isPasswordExternal()
+  {
+    return false;
+  }
+  
+  // doLogin - perfoms a login procedure.
+  function doLogin($login, $password) {
+    $auth = $this->authenticate($login, $password);
+      
+    if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+      echo '<br>'; var_dump($auth); echo '<br />';
+    }
+
+    if ($auth === false)
+      return false;
+
+    $login = $auth['login'];
+
+    $mdb2 = getConnection();
+    $sql = "SELECT id FROM tt_users WHERE login = ".$mdb2->quote($login)." AND status = 1";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) {
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG))
+        echo 'db error!<br />';
+      return false;
+    }
+    $val = $res->fetchRow();
+    if (!$val['id']) {
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG))
+        echo 'login "'.$login.'" does not exist in Time Tracker database.<br />';
+      return false;
+    }
+    
+    $this->setAuth($val['id'], $login);
+    return true;
+  }
+
+  // doLogout - clears logon data from session.
+  function doLogout() {
+    unset($_SESSION['authenticated']);
+    unset($_SESSION['authenticated_user_id']);
+    unset($_SESSION['login']);
+  }
+
+  // setAuth - stores authorization data in session.
+  function setAuth($userid, $username) {
+    $_SESSION['authenticated'] = true;
+    $_SESSION['authenticated_user_id'] = $userid; // NOTE: using "user_id" instead of "authenticated_user_id" gets us in trouble
+                                                  // with older PHP when register_globals = On. What happens is that any time we set
+                                                  // $user_id variable in script, $_SESSION['user_id'] is also changed automatically. 
+    $_SESSION['login'] = $username;
+  }
+
+  // getUserLogin - retrieves user login from session.
+  function getUserLogin() {
+    return $_SESSION['login'];
+  }
+    
+  // getUserId - retrieves user ID from session.
+  function getUserId() {
+    if (isset($_SESSION['authenticated_user_id']))
+      return $_SESSION['authenticated_user_id'];
+    else
+      return null;
+  }
+
+  static function &factory($module, $params = array())
+  {
+    import('auth.Auth_'.$module);
+    $class = 'Auth_' . $module;
+    if (class_exists($class)) {
+      $new_class = new $class($params);
+      return $new_class;
+    } else {
+      die('Class '.$class.' not found');
+    }
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/DateAndTime.class.php b/WEB-INF/lib/DateAndTime.class.php
new file mode 100644 (file)
index 0000000..26a51c5
--- /dev/null
@@ -0,0 +1,365 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+/**
+ * Parse a time/date generated with strftime().
+ *
+ * This function is the same as the original one defined by PHP (Linux/Unix only),
+ *  but now you can use it on Windows too.
+ *  Limitation : Only this format can be parsed %S, %M, %H, %d, %m, %Y
+ *
+ * @author Lionel SAURON
+ * @version 1.0
+ * @public
+ *
+ * @param $sDate(string)    The string to parse (e.g. returned from strftime()).
+ * @param $sFormat(string)  The format used in date  (e.g. the same as used in strftime()).
+ * @return (array)          Returns an array with the <code>$sDate</code> parsed, or <code>false</code> on error.
+ */
+
+function my_strptime($sDate, $sFormat)
+{
+    $aResult = array
+    (
+        'tm_sec'   => 0,
+        'tm_min'   => 0,
+        'tm_hour'  => 0,
+        'tm_mday'  => 1,
+        'tm_mon'   => 0,
+        'tm_year'  => 0,
+        'tm_wday'  => 0,
+        'tm_yday'  => 0,
+        'unparsed' => $sDate,
+    );
+
+    while($sFormat != "")
+    {
+        // ===== Search a %x element, Check the static string before the %x =====
+        $nIdxFound = strpos($sFormat, '%');
+        if($nIdxFound === false)
+        {
+
+            // There is no more format. Check the last static string.
+            $aResult['unparsed'] = ($sFormat == $sDate) ? "" : $sDate;
+            break;
+        }
+
+        $sFormatBefore = mb_substr($sFormat, 0, $nIdxFound);
+        $sDateBefore   = mb_substr($sDate,   0, $nIdxFound);
+
+        if($sFormatBefore != $sDateBefore) break;
+
+        // ===== Read the value of the %x found =====
+        $sFormat = mb_substr($sFormat, $nIdxFound);
+        $sDate   = mb_substr($sDate,   $nIdxFound);
+
+        $aResult['unparsed'] = $sDate;
+
+        $sFormatCurrent = mb_substr($sFormat, 0, 2);
+        $sFormatAfter   = mb_substr($sFormat, 2);
+
+        $nValue = -1;
+        $sDateAfter = "";
+        switch($sFormatCurrent)
+        {
+            case '%S': // Seconds after the minute (0-59)
+
+                sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter);
+
+                if(($nValue < 0) || ($nValue > 59)) return false;
+
+                $aResult['tm_sec']  = $nValue;
+                break;
+
+            // ----------
+            case '%M': // Minutes after the hour (0-59)
+                sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter);
+
+                if(($nValue < 0) || ($nValue > 59)) return false;
+
+                $aResult['tm_min']  = $nValue;
+                break;
+
+            // ----------
+            case '%H': // Hour since midnight (0-23)
+                sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter);
+
+                if(($nValue < 0) || ($nValue > 23)) return false;
+
+                $aResult['tm_hour']  = $nValue;
+                break;
+
+            // ----------
+            case '%d': // Day of the month (1-31)
+                sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter);
+
+                if(($nValue < 1) || ($nValue > 31)) return false;
+
+                $aResult['tm_mday']  = $nValue;
+                break;
+
+            // ----------
+            case '%m': // Months since January (0-11)
+                sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter);
+
+                if(($nValue < 1) || ($nValue > 12)) return false;
+
+                $aResult['tm_mon']  = ($nValue - 1);
+                break;
+
+            // ----------
+            case '%Y': // Years since 1900
+                sscanf($sDate, "%4d%[^\\n]", $nValue, $sDateAfter);
+
+                if($nValue < 1900) return false;
+
+                $aResult['tm_year']  = ($nValue - 1900);
+                break;
+
+            // ----------
+            default:
+              //sscanf($sDate, "%s%[^\\n]", $skip, $sDateAfter);
+              preg_match('/^(.+)(\s|$)/uU', $sDate, $matches);
+              if (isset($matches[1])) {
+                $sDateAfter = mb_substr($sDate, mb_strlen($matches[1]));
+              } else {
+                $sDateAfter = '';
+              }
+              //break 2; // Break Switch and while
+              break;
+        }
+
+        // ===== Next please =====
+        $sFormat = $sFormatAfter;
+        $sDate   = $sDateAfter;
+
+        $aResult['unparsed'] = $sDate;
+
+    } // END while($sFormat != "")
+
+
+    // ===== Create the other value of the result array =====
+    $nParsedDateTimestamp = mktime($aResult['tm_hour'], $aResult['tm_min'], $aResult['tm_sec'],
+                            $aResult['tm_mon'] + 1, $aResult['tm_mday'], $aResult['tm_year'] + 1900);
+
+    // Before PHP 5.1 return -1 when error
+    if(($nParsedDateTimestamp === false)
+    ||($nParsedDateTimestamp === -1)) return false;
+
+    $aResult['tm_wday'] = (int) strftime("%w", $nParsedDateTimestamp); // Days since Sunday (0-6)
+    $aResult['tm_yday'] = (strftime("%j", $nParsedDateTimestamp) - 1); // Days since January 1 (0-365)
+
+    return $aResult;
+} // END of function
+
+class DateAndTime {
+  var $mHour = 0;
+  var $mMinute = 0;
+  var $mSecond = 0;
+  var $mMonth;
+  var $mDay;           // day of week
+  var $mDate;          // day of month
+  var $mYear;
+  var $mIntrFormat     = "%d.%m.%Y %H:%M:%S"; //29.02.2004 16:21:42 internal format date
+  var $mLocalFormat;
+  var $mParseResult = 0;
+  var $mAutoComplete = true;
+
+  /**
+   * Constructor
+   *
+   * @param String $format
+   * @param String $strfDateTime
+   * @return DateAndTime
+   */
+  function DateAndTime($format="",$strfDateTime="") {
+    $this->mLocalFormat = ($format ? $format : $this->mIntrFormat);
+    $d = ($strfDateTime ? $strfDateTime : $this->do_strftime($this->mLocalFormat));
+    $this->parseVal($d);
+  }
+
+  function setFormat($format) {
+    $this->mLocalFormat = $format;
+  }
+
+  function getFormat() {
+    return $this->mLocalFormat;
+  }
+
+  //01 to 31
+  function getDate() { return $this->mDate; }
+
+  //0 (for Sunday) through 6 (for Saturday)
+  function getDay() { return $this->mDay; }
+
+  //01 through 12
+  function getMonth() { return $this->mMonth; }
+
+  //1999 or 2003
+  function getYear() { return $this->mYear; }
+
+  function setDate($value) { $this->mDate = $value; }
+  function setMonth($value) { $this->mMonth = $value; }
+  function setYear($value) { $this->mYear = $value; }
+
+  function setTimestamp($ts) {
+    $this->mDate = date("d",$ts);
+    $this->mDay = date("w",$ts);
+    $this->mMonth = date("m",$ts);
+    $this->mYear = date("Y",$ts);
+    $this->mHour = date("H",$ts);
+    $this->mMinute = date("i",$ts);
+    $this->mSecond = date("s",$ts);
+  }
+
+  /**
+   * Return UNIX timestamp
+   */
+  function getTimestamp() {
+    return @mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate, $this->mYear);
+  }
+
+  function compare($datetime) {
+    $ts1 = $this->getTimestamp();
+    $ts2 = $datetime->getTimestamp();
+    if ($ts1<$ts2) return -1;
+    if ($ts1==$ts2) return 0;
+    if ($ts1>$ts2) return 1;
+  }
+
+  function toString($format="") {
+    if ($this->mParseResult==0) {
+      if ($format) {
+        return $this->do_strftime($format, $this->getTimestamp());
+      } else {
+        return $this->do_strftime($this->mLocalFormat, $this->getTimestamp());
+      }
+    } else {
+      if ($format) {
+        return $this->do_strftime($format);
+      } else {
+        return $this->do_strftime($this->mLocalFormat);
+      }
+    }
+  }
+
+  function parseVal($szDate, $format="") {
+    $useformat = ($format ? $format : $this->mLocalFormat);
+    $res = my_strptime($szDate, $useformat);
+    if ($res !== false) {
+      $this->mDate = $res['tm_mday'];
+      $this->mDay = $res['tm_wday'];
+      $this->mMonth = $res['tm_mon'] + 1; // tm_mon - Months since January (0-11)
+      $this->mYear = 1900 + $res['tm_year']; // tm_year - Years since 1900
+      $this->mHour = $res['tm_hour'];
+      $this->mMinute = $res['tm_min'];
+      $this->mSecond = $res['tm_sec'];
+      $this->mParseResult = 0;
+    } elseif ($this->mAutoComplete) {
+      $this->setTimestamp(time());
+      $this->mParseResult = 1;
+    }
+  }
+
+  function isError() {
+    if ($this->mParseResult!=0) return true;
+    return false;
+  }
+
+  function getClone() {
+    if (version_compare(phpversion(), '5.0') < 0) {
+               $d = new DateAndTime($this->getFormat());
+               $d->setTimestamp($this->getTimestamp());
+               return $d;
+    } else {
+               return clone($this);
+    }
+  }
+
+  function before(/*DateAndTime*/ $obj) {
+    if ($this->getTimestamp()<$obj->getTimestamp()) return true;
+    return false;
+  }
+
+  function after(/*DateAndTime*/ $obj) {
+    if ($this->getTimestamp()>$obj->getTimestamp()) return true;
+    return false;
+  }
+
+  function equals(/*DateAndTime*/ $obj) {
+    if ($this->getTimestamp() == $obj->getTimestamp()) return true;
+    return false;
+  }
+
+  function nextDate() {
+    $d = $this->getClone();
+    $d->incDay();
+    return $d;
+  }
+
+  function decDay(/*int*/$days=1) {
+    $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate - $days, $this->mYear));
+  }
+
+  function incDay(/*int*/$days=1) {
+    $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate + $days, $this->mYear));
+  }
+
+  /**
+   * @param $format string Datetime format string
+   * @return string Preprocessed string with all locale-depended format
+   *                characters replaced by localized i18n strings.
+   */
+  function preprocessFormatString($format) {
+    global $i18n;
+    if (isset($GLOBALS['i18n'])) {
+      // replace locale-dependent strings
+      $format = str_replace('%a', mb_substr($i18n->getWeekDayName($this->mDay), 0, 3, 'utf-8'), $format);
+      $format = str_replace('%A', $i18n->getWeekDayName($this->mDay), $format);
+      $abbrev_month = mb_substr($i18n->monthNames[$this->mMonth], 0, 3, 'utf-8');
+      $format = str_replace('%b', $abbrev_month, $format);
+      $format = str_replace('%h', $abbrev_month, $format);
+      $format = str_replace('%z', date('O'), $format);
+      $format = str_replace('%Z', date('O'), $format); // format as 'O' for consistency with JS strftime
+      if (strpos($format, '%c') !== false) {
+        $format = str_replace('%c', $this->preprocessFormatString('%a %d %b %Y %T %Z'), $format);
+      }
+    }
+    return $format;
+  }
+
+  function do_strftime($format, $timestamp = null)
+  {
+    if (!is_null($timestamp)) {
+      return strftime($this->preprocessFormatString($format), $timestamp);
+    } else {
+      return strftime($this->preprocessFormatString($format));
+    }
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/I18n.class.php b/WEB-INF/lib/I18n.class.php
new file mode 100644 (file)
index 0000000..96c3743
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class I18n {
+  var $lang = 'en'; // Language for the class.
+  var $defaultLang = 'en'; // English is the default language.
+  var $monthNames;
+  var $weekdayNames;
+  var $weekdayShortNames;
+  var $holidays;
+  var $keys = array(); // These are our localized strings.
+
+  // The getKey obtains localized keyword value.
+  function getKey($kword) {
+    $value = '';
+    $pos = strpos($kword, '.'); // Keywords can have separating dots such as in form.login.about.
+    if (!($pos === false)) {
+      $words = explode('.', $kword);
+      $str = '';
+      foreach ($words as $word) {
+        $str .= "['".$word."']";
+      }
+      eval("\$value = \$this->keys".$str.";");
+    } else {
+      $value = $this->keys[$kword];
+    }
+    return $value;
+  }
+
+  // TODO: refactoring ongoing down from here...
+    function getWeekDayName($id) {
+      $id = intval($id);
+      return $this->weekdayNames[$id];
+    }
+
+    function load($localName) {
+    $kw = array();
+    $filename = strtolower($localName) . '.lang.php';
+    $inc_filename = RESOURCE_DIR . '/' . $this->defaultLang . '.lang.php';
+
+    if (file_exists($inc_filename)) {
+      include($inc_filename);
+
+      $this->monthNames = $i18n_months;
+      $this->weekdayNames = $i18n_weekdays;
+        
+        $this->weekdayShortNames = $i18n_weekdays_short;
+      if (defined('SHOW_HOLIDAYS') && isTrue(SHOW_HOLIDAYS)) {
+        $this->holidays = $i18n_holidays;
+      }
+    
+      foreach ($i18n_key_words as $kword=>$value) {
+         $pos = strpos($kword, ".");
+         if (!($pos === false)) {
+                   $p = explode(".", $kword);
+                   $str = "";
+                   foreach ($p as $w) {
+                       $str .= "[\"".$w."\"]";
+                   }
+                   //$value = addslashes($value);
+                   eval("\$this->keys".$str."='".$value."';");
+               } else {
+                   $this->keys[$kword] = $value;
+               }
+      }
+    }
+
+    $inc_filename = RESOURCE_DIR . '/' . $filename;
+    if (file_exists($inc_filename) && ($localName != $this->defaultLang)) {
+      require($inc_filename);
+
+      $this->lang = $localName;
+      $this->monthNames = $i18n_months;
+      $this->weekdayNames = $i18n_weekdays;
+        $this->weekdayShortNames = $i18n_weekdays_short;
+      if (defined('SHOW_HOLIDAYS') && isTrue(SHOW_HOLIDAYS)) {
+        $this->holidays = $i18n_holidays;
+      }
+      foreach ($i18n_key_words as $kword=>$value) {
+         if (!$value) continue;
+         $pos = strpos($kword, ".");
+         if (!($pos === false)) {
+                   $p = explode(".", $kword);
+                   $str = "";
+                   foreach ($p as $w) {
+                       $str .= "[\"".$w."\"]";
+                   }
+                   //$value = addslashes($value);
+                   eval("\$this->keys".$str."='".$value."';");
+               } else {
+                   $this->keys[$kword] = $value;
+               }
+      }
+        return true;
+    }
+  }
+
+  function hasLang($lang)
+  {
+    $filename = RESOURCE_DIR . '/' . strtolower($lang) . '.lang.php';
+    return file_exists($filename);
+  }
+
+  function getBrowserLanguage()
+  {
+    $acclang = @$_SERVER['HTTP_ACCEPT_LANGUAGE'];
+    if (empty($acclang)) {
+      return "";
+    }
+    $lang_prefs = explode(',', $acclang);
+    foreach ($lang_prefs as $lang_pref) {
+      $lang_pref_parts = explode(';', trim($lang_pref));
+      $lang_parts = explode('-', trim($lang_pref_parts[0]));
+      $lang_main = $lang_parts[0];
+      if ($this->hasLang($lang_main)) {
+        return $lang_main;
+      }
+    }
+    return "";
+  }
+
+  // getLangFileList() returns a list of language files.
+  static function getLangFileList() {
+    $fileList = array();
+    $d = @opendir(RESOURCE_DIR);
+    while (($file = @readdir($d))) {
+      if (($file != ".") && ($file != "..")) {
+               if (strpos($file, ".lang.php")) {
+                 $fileList[] = @basename($file);
+               }
+      }
+    }
+    @closedir($d);
+    return $fileList;
+  }
+   
+  static function getLangFromFilename($filename)
+  {
+    return substr($filename, 0, strpos($filename, '.'));
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/Period.class.php b/WEB-INF/lib/Period.class.php
new file mode 100644 (file)
index 0000000..1880cf1
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+define('INTERVAL_THIS_DAY', 1);
+define('INTERVAL_THIS_WEEK', 2);
+define('INTERVAL_THIS_MONTH', 3);
+define('INTERVAL_THIS_YEAR', 4);
+define('INTERVAL_ALL_TIME', 5);
+define('INTERVAL_LAST_WEEK', 6);
+define('INTERVAL_LAST_MONTH', 7);
+
+class Period {
+       var $mBeginDate;
+       var $mEndDate;
+
+       function Period($period_name=0, $date_point=null) {
+               global $user;
+               
+               if (!$date_point || !($date_point instanceof DateAndTime)) {
+                       $date_point = new DateAndTime();
+               }
+               $startWeek = $user->week_start;
+
+               $date_begin = new DateAndTime();
+               $date_begin->setFormat($date_point->getFormat());
+               $date_end       = new DateAndTime();
+               $date_end->setFormat($date_point->getFormat());
+               $t_arr = localtime($date_point->getTimestamp());
+               $t_arr[5] = $t_arr[5] + 1900;
+
+               if ($t_arr[6] < $startWeek) {
+                 $startWeekBias = $startWeek - 7;
+               } else {
+                 $startWeekBias = $startWeek;
+               }
+
+               switch ($period_name) {
+                       case INTERVAL_THIS_DAY:
+                               $date_begin->setTimestamp($date_point->getTimestamp());
+                               $date_end->setTimestamp($date_point->getTimestamp());
+                       break;
+                       case INTERVAL_THIS_WEEK:
+                         $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5]));
+                               $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5]));
+                       break;
+                       case INTERVAL_LAST_WEEK:
+                               $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-7+$startWeekBias,$t_arr[5]));
+                               $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-1+$startWeekBias,$t_arr[5]));
+                       break;
+                       case INTERVAL_THIS_MONTH:
+                               $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,1,$t_arr[5]));
+                               $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+2,0,$t_arr[5]));
+                       break;
+                       case INTERVAL_LAST_MONTH:
+                               $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4],1,$t_arr[5]));
+                               $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,0,$t_arr[5]));
+                       break;
+
+                       case INTERVAL_THIS_YEAR:
+                               $date_begin->setTimestamp(mktime(0, 0, 0, 1, 1, $t_arr[5]));
+                               $date_end->setTimestamp(mktime(0, 0, 0, 12, 31, $t_arr[5]));
+                       break;
+               }
+               $this->mBeginDate       = &$date_begin;
+               $this->mEndDate         = &$date_end;
+       }
+
+       /**
+        * Return all days by period
+        *
+        * @return array
+        */
+       function getAllDays() {
+               $ret_array = array();
+               if ($this->mBeginDate->before($this->mEndDate)) {
+                       $d = $this->getBegin();
+                       while ($d->before($this->getEnd())) {
+                               array_push($ret_array, $d);
+                               $d = $d->nextDate();
+                       }
+                       array_push($ret_array, $d);
+               } else {
+                       array_push($ret_array, $this->mBeginDate);
+               }
+               return $ret_array;
+       }
+
+       function setPeriod($b_date, $e_date) {
+               $this->mBeginDate = $b_date;
+               $this->mEndDate = $e_date;
+       }
+
+       // return date object
+       function getBegin() {
+               return $this->mBeginDate;
+       }
+
+       // return date object
+       function getEnd() {
+               return $this->mEndDate;
+       }
+
+       // return date string
+       function getBeginDate($format="") {
+               return $this->mBeginDate->toString($format);
+       }
+
+       // return date string
+       function getEndDate($format="") {
+               return $this->mEndDate->toString($format);
+       }
+
+       function getArray($format="") {
+               $result = array();
+               $d = $this->getBegin();
+               while ($d->before($this->getEnd())) {
+                       $result[] = $d->toString($format);
+                       $d = $d->nextDate();
+               }
+               return $result;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/PieChartEx.class.php b/WEB-INF/lib/PieChartEx.class.php
new file mode 100644 (file)
index 0000000..6f482b5
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+  require_once(LIBRARY_DIR.'/libchart/classes/libchart.php');
+
+       /**
+       * Pie chart extension to render pies with no title and labels
+       *
+       * @author   pingw33n
+       */
+
+       class PieChartEx extends PieChart
+       {
+               /**
+               * Render the chart image
+               *
+               * @access       public
+               * @param        array           options: fileName, hideLogo, hideTitle, hidePie, hideLabel
+               */
+
+               public function renderEx($options)
+               {
+                       $hideLabel = isset($options['hideLabel']) && $options['hideLabel'] == true;
+
+                       $this->computePercent();
+
+                       if ($hideLabel) {
+                        $this->plot->setGraphPadding(new Padding(0));
+                        $this->plot->setTitleHeight(0);
+                       }
+                       $this->computeLayout(!$hideLabel);
+
+                       $this->createImage();
+
+                       if (!isset($options['hideLogo']) || $options['hideLogo'] == false)
+                               $this->plot->printLogo();
+                       if (!isset($options['hideTitle']) || $options['hideTitle'] == false)
+                               $this->plot->printTitle();
+                       if (!isset($options['hidePie']) || $options['hidePie'] == false)
+                               $this->printPie();
+                       if (!$hideLabel)
+                               $this->printLabel();
+
+                       /*if(isset($options['fileName']))
+                               imagepng($this->img, $options['fileName']);
+                       else
+                               imagepng($this->img); */
+                       $this->plot->render($options['fileName']);
+               }
+       }
+?>
diff --git a/WEB-INF/lib/auth/Auth_db.class.php b/WEB-INF/lib/auth/Auth_db.class.php
new file mode 100644 (file)
index 0000000..7475e6b
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/content/time_tracker/open_source/credits.htm
+// +----------------------------------------------------------------------+
+
+/**
+* Auth_db class is used to authenticate users against internal DB
+* @package TimeTracker
+*/
+class Auth_db extends Auth {
+
+  /**
+   * Authenticate user against internal users DB
+   *
+   * @param string $login
+   * @param string $password
+   * @return mixed
+   */
+  function authenticate($login, $password)
+  {
+       $mdb2 = getConnection();
+       
+       // Try md5 password match first.
+       $sql = "SELECT id FROM tt_users 
+      WHERE login = ".$mdb2->quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1";
+
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) {
+      die($res->getMessage());
+    }
+
+    $val = $res->fetchRow();
+    if ($val['id'] > 0) {
+      return array('login'=>$login,'id'=>$val['id']);
+    } else {
+       
+      // If the OLD_PASSWORDS option is defined - set it.
+      if (defined('OLD_PASSWORDS') && isTrue(OLD_PASSWORDS)) {
+        $sql = "SET SESSION old_passwords = 1";
+        $res = $mdb2->query($sql);
+        if (is_a($res, 'PEAR_Error')) {
+          die($res->getMessage());
+        }      
+      }
+
+      // Try legacy password match. This is needed for compatibility with older versions of TT.
+      $sql = "SELECT id FROM tt_users
+        WHERE login = ".$mdb2->quote($login)." AND password = password(".$mdb2->quote($password).") AND status = 1";
+      $res = $mdb2->query($sql);
+      if (is_a($res, 'PEAR_Error')) {
+        die($res->getMessage());
+      }
+      $val = $res->fetchRow();
+      if ($val['id'] > 0) {
+        return array('login'=>$login,'id'=>$val['id']);
+      }
+      return false;
+    }
+  }
+
+  function isPasswordExternal() {
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/auth/Auth_ldap.class.php b/WEB-INF/lib/auth/Auth_ldap.class.php
new file mode 100644 (file)
index 0000000..93fdebf
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/content/time_tracker/open_source/credits.htm
+// +----------------------------------------------------------------------+
+
+// NOTES:
+//
+// Auth_ldap.class.php was originally written for LDAP authentication with Windows Active Directory.
+// It June 2011, it was extended to include support for OpenLDAP. The difference in the code is in the format
+// of user identification that we pass to ldap_bind().
+//
+// Windows AD accepts username@domain.com while OpenLDAP needs something like "uid=username,ou=people,dc=domain,dc=com".
+// Therefore, some branching in the code.
+//
+// In April 2012, a previously mandatory search for group membership was put in a conditional block (if ($member_of) -
+// when mandatory membership in groups is actually defined in config.php).
+// This made the module work with Sun Directory Server when NO GROUP MEMBERSHIP is specified.
+// Note 1: search is likely to fail with Sun DS if 'member_of' => array()); is used in config.php.
+// Note 2: search is likely to not work properly with OpenLDAP as well because of Windows specific filtering code in there
+// (we are looking for matches for Windows-specific samaccountname property). Search needs to be redone during the next
+// refactoring effort.
+
+
+/**
+* Auth_ldap class to authenticate users against an LDAP server (Windows AD, OpenLDAP, and others).
+* @package TimeTracker
+*/
+class Auth_ldap extends Auth {
+  var $params;
+
+  function Auth_ldap($params)
+  {
+    $this->params = $params;
+    if (isset($GLOBALS['smarty'])) {
+      $GLOBALS['smarty']->assign('Auth_ldap_params', $this->params);
+    }
+  }
+
+  function ldap_escape($str){
+    $illegal = array("(", ")", "#");
+    $legal = array();
+    foreach ($illegal as $id => $char) {
+      $legal[$id] = "\\".$char;
+    }
+    $str = str_replace($illegal, $legal,$str); //replace them
+    return $str;
+  }
+
+  /**
+   * Authenticate user against LDAP server.
+   *
+   * @param string $login
+   * @param string $password
+   * @return mixed
+   */
+  function authenticate($login, $password)
+  {
+    if (!function_exists('ldap_bind')) {
+      die ('php_ldap extension not loaded!');
+    }
+
+    if (empty($this->params['server']) || empty($this->params['base_dn'])) {
+      die('You must set server and base_dn in AUTH_MODULE_PARAMS in config.php');
+    }
+
+    $member_of = @$this->params['member_of'];
+
+    $lc = ldap_connect($this->params['server']);
+
+    if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+      echo '<br />';
+      echo '$lc='; var_dump($lc); echo '<br />';
+      echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+    }
+
+    if (!$lc) return false;
+    
+    ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, 3);
+    ldap_set_option($lc, LDAP_OPT_REFERRALS, 0);
+    if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+      ldap_set_option($lc, LDAP_OPT_DEBUG_LEVEL, 7);
+    }
+    
+    // We need to handle Windows AD and OpenLDAP differently.
+    if ($this->params['type'] != 'openldap') {
+      
+      // check if the user specified full login
+      if (strpos($login, '@') === false) {
+        // append default domain
+        $login .= '@' . $this->params['default_domain'];
+      }
+
+
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+        echo '$login='; var_dump($login); echo '<br />';
+      }
+
+      $lb = @ldap_bind($lc, $login, $password);
+    
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+        echo '$lb='; var_dump($lb); echo '<br />';
+        echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+      }
+
+      if (!$lb) {
+        ldap_unbind($lc);
+        return false;
+      }
+
+         if ($member_of) {
+        // get groups
+
+        $filter = 'samaccountname='.Auth_ldap::ldap_escape($login);
+        $fields = array('samaccountname', 'mail', 'memberof', 'department', 'displayname', 'telephonenumber', 'primarygroupid');
+        $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields);
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$sr='; var_dump($sr); echo '<br />';
+          echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+        }
+
+        // if search failed it's likely that account is disabled
+        if (!$sr) {
+          ldap_unbind($lc);
+          return false;
+        }
+
+        $entries = @ldap_get_entries($lc, $sr);
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$entries='; var_dump($entries); echo '<br />';
+          echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+        }
+
+        if ($entries === false) {
+          ldap_unbind($lc);
+          return false;
+        }
+
+        $groups = array();
+
+        // extract group names from
+        // assuming the groups are in format: CN=<group_name>,...
+        for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) {
+          $grp = $entries[0]['memberof'][$i];
+          $grp_fields = explode(',', $grp);
+          $groups[] = substr($grp_fields[0], 3);
+        }
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$member_of'; var_dump($member_of); echo '<br />';
+        };
+
+        // check for group membership
+            foreach ($member_of as $check_grp) {
+          if (!in_array($check_grp, $groups)) {
+            ldap_unbind($lc);
+            return false;
+          }
+        }
+      }
+
+      ldap_unbind($lc);
+
+      // handle special case - admin account, strip domain part
+      if (strpos($login, 'admin@') !== false) {
+        $login = substr($login, 0, 5);
+      }
+
+      return array('login' => $login, 'data' => $entries, 'member_of' => $groups);
+    } else {
+       
+      // Assuming OpenLDAP server.
+      $login_oldap = 'uid='.$login.','.$this->params['base_dn'];
+
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+        echo '$login_oldap='; var_dump($login_oldap); echo '<br />';
+      }
+      
+      // check if the user specified full login
+      if (strpos($login, '@') === false) {
+        // append default domain
+        $login .= '@' . $this->params['default_domain'];
+      }
+
+      $lb = @ldap_bind($lc, $login_oldap, $password);
+    
+      if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+        echo '$lb='; var_dump($lb); echo '<br />';
+        echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+      }
+
+      if (!$lb) {
+        ldap_unbind($lc);
+        return false;
+      }
+
+         if ($member_of) {
+        // get groups
+
+        $filter = 'samaccountname='.Auth_ldap::ldap_escape($login_oldap);
+        $fields = array('samaccountname', 'mail', 'memberof', 'department', 'displayname', 'telephonenumber', 'primarygroupid');
+        $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields);
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$sr='; var_dump($sr); echo '<br />';
+          echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+        }
+
+        // if search failed it's likely that account is disabled
+        if (!$sr) {
+          ldap_unbind($lc);
+          return false;
+        }
+
+        $entries = @ldap_get_entries($lc, $sr);
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$entries='; var_dump($entries); echo '<br />';
+          echo 'ldap_error()='; echo ldap_error($lc); echo '<br />';
+        }
+
+        if ($entries === false) {
+          ldap_unbind($lc);
+          return false;
+        }
+
+        $groups = array();
+
+        // extract group names from
+        // assuming the groups are in format: CN=<group_name>,...
+        for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) {
+          $grp = $entries[0]['memberof'][$i];
+          $grp_fields = explode(',', $grp);
+          $groups[] = substr($grp_fields[0], 3);
+        }
+
+        if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) {
+          echo '$member_of'; var_dump($member_of); echo '<br />';
+        };
+
+        // check for group membership
+        foreach ($member_of as $check_grp) {
+          if (!in_array($check_grp, $groups)) {
+            ldap_unbind($lc);
+            return false;
+          }
+        }
+      }
+
+      ldap_unbind($lc);
+
+      // handle special case - admin account, strip domain part
+      if (strpos($login, 'admin@') !== false) {
+        $login = substr($login, 0, 5);
+      }
+
+      return array('login' => $login, 'data' => $entries, 'member_of' => $groups);
+    }
+  }
+
+  function isPasswordExternal() {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php
new file mode 100644 (file)
index 0000000..4111c3e
--- /dev/null
@@ -0,0 +1,351 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+       /**
+        * @return unknown
+        * @param file unknown
+        * @param version = "" unknown
+        * @desc Loads a class
+        */
+       function import( $class_name ) {
+           $libs = array(
+                       dirname($_SERVER["SCRIPT_FILENAME"]),
+                       LIBRARY_DIR
+               );
+
+           $pos = strpos($class_name, ".");
+        if (!($pos === false)) {
+            $peaces = explode(".", $class_name);
+            $p = "";
+            for ($i=0; $i<count($peaces)-1; $i++) {
+                $p = $p . "/" . $peaces[$i];
+            }
+                       $libs = array_merge(array(LIBRARY_DIR . $p),$libs);
+            $class_name = $peaces[count($peaces)-1];
+        }
+
+               $filename = $class_name . '.class.php';
+
+               foreach($libs as $lib) {
+                       $inc_filename = $lib . '/' . $filename;
+                       if (file_exists($inc_filename)) {
+                                       require_once($inc_filename);
+                                       return $class_name;
+                       }
+               }
+
+               print '<br><b>load_class: error loading file "'.$filename.'"</b>';
+               die();
+       }
+
+       // The mu_sort function is used to sort a multi-dimensional array.
+       // It looks like the code example is taken from the PHP manual http://ca2.php.net/manual/en/function.sort.php
+       function mu_sort($array, $key_sort) {
+               $n = 0;
+               if (!is_array($array) || count($array)==0)
+                       return array();
+
+               $key_sorta = explode(",", $key_sort);
+               $keys = array_keys($array[0]);
+
+               for($m=0; $m < count($key_sorta); $m++) {
+                       $nkeys[$m] = trim($key_sorta[$m]);
+               }
+               $n += count($key_sorta);
+
+               for($i=0; $i < count($keys); $i++) {
+                       if(!in_array($keys[$i], $key_sorta)) {
+                               $nkeys[$n] = $keys[$i];
+                               $n += "1";
+                       }
+               }
+
+               for($u=0;$u<count($array); $u++) {
+                       $arr = $array[$u];
+                       for($s=0; $s<count($nkeys); $s++) {
+                               $k = $nkeys[$s];
+                               $output[$u][$k] = $array[$u][$k];
+                       }
+               }
+               sort($output);
+               return $output;
+       }
+
+       /**
+        * return float type
+        *
+        * @param unknown $value
+        * @return unknown
+        */
+       function toFloat($value) {
+               if (isset($value) && (strlen($value) > 0)) {
+                       $value = str_replace(",",".",$value);
+                       return floatval($value);
+               }
+               return null;
+       }
+
+       function stripslashes_deep($value) {
+           $value = is_array($value) ?
+                array_map('stripslashes_deep', $value) :
+                stripslashes($value);
+       return $value;
+       }
+
+       function &getConnection() {
+        if (!isset($GLOBALS["_MDB2_CONNECTION"])) {
+
+               require_once('MDB2.php');
+
+               $mdb2 = MDB2::connect(DSN);
+                       if (is_a($mdb2, 'PEAR_Error')) {
+                       die($mdb2->getMessage());
+                       }
+
+                       $mdb2->setOption('debug', true);
+                       $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC);
+                       
+                       $GLOBALS["_MDB2_CONNECTION"] = $mdb2;
+       }
+       return $GLOBALS["_MDB2_CONNECTION"];
+       }
+
+
+       function closeConnection() {
+               if (isset($GLOBALS["_DB_CONNECTION"])) {
+                       $GLOBALS["_DB_CONNECTION"]->close();
+                       unset($GLOBALS["_DB_CONNECTION"]);
+               }
+       }
+
+function time_to_decimal($a) {
+  $tmp = explode(":", $a);
+  if($tmp[1]{0}=="0") $tmp[1] = $tmp[1]{1};
+
+  $m = round($tmp[1]*100/60);
+
+  if($m<10) $m = "0".$m;
+  $time = $tmp[0].".".$m;
+  return $time;
+}
+
+function sec_to_time_fmt_hm($sec)
+{
+  return sprintf("%d:%02d", $sec / 3600, $sec % 3600 / 60);
+}
+
+function magic_quotes_off()
+{
+  // if (get_magic_quotes_gpc()) { // This check is now done before calling this function.
+    $_POST = array_map('stripslashes_deep', $_POST);
+    $_GET = array_map('stripslashes_deep', $_GET);
+    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
+  // }
+}
+
+// check_extension checks whether a required PHP extension is loaded and dies if not so.
+function check_extension($ext)
+{
+  if (!extension_loaded($ext))
+    die("PHP extension '{$ext}' is required but is not loaded. Read Time Tracker Install Guide for help.");
+}
+
+// isTrue is a helper function to return correct false for older config.php values defined as a string 'false'.
+function isTrue($val)
+{
+  return ($val == false || $val === 'false') ? false : true;
+}
+
+// ttValidString is used to check user input to validate a string.
+function ttValidString($val, $emptyValid = false)
+{
+  $val = trim($val);
+  if (strlen($val) == 0 && !$emptyValid)
+    return false;
+    
+  // String must not be XSS evil (to insert JavaScript).
+  if (stristr($val, '<script>') || stristr($val, '<script '))
+    return false;
+    
+  return true;    
+}
+
+// ttValidEmail is used to check user input to validate an email string.
+function ttValidEmail($val, $emptyValid = false)
+{
+  $val = trim($val);
+  if (strlen($val) == 0)
+    return ($emptyValid ? true : false);
+       
+  // String must not be XSS evil (to insert JavaScript).
+  if (stristr($val, '<script>') || stristr($val, '<script '))
+    return false;
+    
+  // Validate a single email address. TODO: improve for compliancy with RFC.
+  if (!preg_match("/^[_a-zA-Z\d\'-\.]+@([_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+)$/", $val))
+    return false;
+  
+  return true;    
+}
+
+// ttValidEmailList is used to check user input to validate an email string.
+function ttValidEmailList($val, $emptyValid = false)
+{
+  $val = trim($val);
+  if (strlen($val) == 0)
+    return ($emptyValid ? true : false);
+       
+  // String must not be XSS evil (to insert JavaScript).
+  if (stristr($val, '<script>') || stristr($val, '<script '))
+    return false;
+    
+  // Validates a list of email addresses separated by a comma with optional spaces.
+  if (!preg_match("/^[_a-zA-Z\d\'-\.]+@([_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+)(,\s*[_a-zA-Z\d\'-\.]+@([_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+))*$/", $val))
+    return false;
+    
+  return true;
+}
+
+// ttValidFloat is used to check user input to validate a float value.
+function ttValidFloat($val, $emptyValid = false)
+{
+  $val = trim($val);
+  if (strlen($val) == 0)
+    return ($emptyValid ? true : false);
+    
+  global $user;
+  $decimal = $user->decimal_mark;
+       
+  if (!preg_match('/^-?[0-9'.$decimal.']+$/', $val))
+    return false;
+    
+  return true;    
+}
+
+// ttValidDate is used to check user input to validate a date.
+function ttValidDate($val)
+{
+  $val = trim($val);
+  if (strlen($val) == 0)
+    return false;
+
+  // This should accept a string in format 'YYYY-MM-DD', 'MM/DD/YYYY', 'DD.MM.YYYY', or 'DD.MM.YYYY whatever'.
+  if (!preg_match('/^\d\d\d\d-\d\d-\d\d$/', $val) &&
+    !preg_match('/^\d\d\/\d\d\/\d\d\d\d$/', $val) &&
+    !preg_match('/^\d\d\.\d\d\.\d\d\d\d$/', $val) &&
+    !preg_match('/^\d\d\.\d\d\.\d\d\d\d .+$/', $val))
+    return false;
+    
+  return true;    
+}
+
+// ttValidInteger is used to check user input to validate an integer.
+function ttValidInteger($val, $emptyValid = false)
+{
+  $val = trim($val);
+  if (strlen($val) == 0)
+    return ($emptyValid ? true : false);
+    
+  if (!preg_match('/^[0-9]+$/', $val))
+    return false;
+
+  return true;
+}
+
+// ttValidCronSpec is used to check user input to validate cron specification.
+function ttValidCronSpec($val)
+{
+  // This code is adapted from http://stackoverflow.com/questions/235504/validating-crontab-entries-w-php
+  $numbers= array(
+     'min'=>'[0-5]?\d',
+     'hour'=>'[01]?\d|2[0-3]',
+     'day'=>'0?[1-9]|[12]\d|3[01]',
+     'month'=>'[1-9]|1[012]',
+     'dow'=>'[0-7]'
+  );
+
+  foreach($numbers as $field=>$number) {
+    $range= "($number)(-($number)(\/\d+)?)?";
+    $field_re[$field]= "\*(\/\d+)?|$range(,$range)*";
+  }
+
+  $field_re['month'].='|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec';
+  $field_re['dow'].='|mon|tue|wed|thu|fri|sat|sun';
+
+  $fields_re= '('.join(')\s+(', $field_re).')';
+
+  /*
+  $replacements= '@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly';
+
+  $regexp = '^\s*('.
+                '$'.
+                '|#'.
+                '|\w+\s*='.
+                "|$fields_re\s+\S".
+                "|($replacements)\s+\S".
+            ')';
+   */
+  // The above block from the link did not work for me.
+
+  // But this works.
+  $regexp = '/^'.$fields_re.'$/';
+       
+  if (!preg_match($regexp, $val))
+    return false;
+
+  return true;
+}
+
+// ttAccessCheck is used to check whether user is allowed to proceed. This function is used
+// as an initial check on all publicly available pages.
+function ttAccessCheck($required_rights)
+{
+  global $auth;
+  global $user;
+  
+  // Redirect to login page if user is not authenticated.
+  if (!$auth->isAuthenticated()) {
+    header('Location: login.php');
+    exit();
+  }
+  
+  // Check rights.
+  if (!($required_rights & $user->rights))
+    return false;
+    
+  return true;
+}
+
+
+
+
+
+
+
+       
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/ActionErrors.class.php b/WEB-INF/lib/form/ActionErrors.class.php
new file mode 100644 (file)
index 0000000..44b2b2c
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class ActionErrors {
+    var $errors = array();
+    
+    function ActionErrors() {
+    }
+    
+    function isEmpty() {
+        return (count($this->errors)>0 ? false : true );
+    }
+    
+    function add($message, $arg0 = '', $arg1 = '') {
+       $patterns = array ("/\{0\}/","/\{1\}/");
+               $replace = array ($arg0, $arg1);
+               $message = preg_replace ($patterns, $replace, $message);
+        $this->errors[]["message"] = $message;
+    }    
+    
+    function addAll($arr) {
+       if (is_array($arr)) {
+               foreach ($arr as $k=>$v) {
+                       $this->errors[$k] = $v;
+               }
+       }
+    }
+    
+    function get($key) {
+        return $this->errors["$key"]["message"];
+    }
+    
+    function dump() {
+        print_r($this->errors);
+    }
+    
+    function getErrors() {
+        return $this->errors;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/ActionForm.class.php b/WEB-INF/lib/form/ActionForm.class.php
new file mode 100644 (file)
index 0000000..30d5c93
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import("DateAndTime");
+
+class ActionForm {
+       var $mName              = "";
+       var $mSessionCell;
+    var $mValues       = array(); // values without localisation
+    var $mVariables = array();
+    var $mForm         = null;
+    var $mInitForm     = false;
+
+    function ActionForm($name, &$form, $request=null) {
+       $this->setName($name);
+       $form->setRequest($request);
+               $this->setForm($form);
+               //if ($request) $this->initAttributes($request);
+               $this->initAttributes($request);
+    }
+    
+    function setForm(&$form) {
+       $this->mForm = $form;
+       $elements = $form->getElements();
+       if (is_array($elements))
+               $this->setVariablesNames(array_keys($elements));
+    }
+    
+    function &getFormElement($name) {
+       if ($this->mForm!=null) {
+                       return  $this->mForm->mElements[$name];
+       }
+       return null;
+    }
+    
+    function getName() {
+               return $this->mName;
+       }
+
+    function setName($name) {
+               $this->mName = $name;
+               $this->mSessionCell = "formbean_".$this->mName;
+       }
+    
+    /**
+     * init parameters and form
+     *
+     * @param object $request
+     */
+    function initAttributes(&$request) {
+        //$submit_flag = $this->isSubmit();
+        $submit_flag = (is_object($request) && ($request->getMethod() == 'POST'));
+               
+        if ($submit_flag) {
+               // fill ActionForm and Form from Request
+
+               foreach ($this->mVariables as $name) {
+                       if ($this->mForm->mElements[$name] && $request->getParameter($name)) {
+                           $this->mForm->mElements[$name]->setValue($request->getParameter($name));
+                           $this->mValues[$name] = $this->mForm->mElements[$name]->getValue();
+                       }
+               }
+        } else {
+               // fill ActionForm from Session
+               $this->loadBean();
+        }
+        
+        // fill Form by ActionForm
+        if ($this->mForm) {
+               $elements = $this->mForm->getElements();
+                       foreach ($elements as $name=>$el) {
+                       if ($this->mForm->mElements[$name] && isset($this->mValues[$name])) {
+                                   $this->mForm->mElements[$name]->setValue($this->mValues[$name]);
+                   }
+               }
+               $this->mInitForm = true;
+        }
+    }
+    
+    /**
+     * Init custom variables
+     *
+     * @param unknown $request
+     * @param unknown $respons
+     */
+    function initVariables(&$request, &$respons) {
+       
+    }
+    
+    function setVariablesNames($namelist) {
+        $this->mVariables = $namelist;
+    }
+
+    function setAttribute($name,$value) {
+       global $user;
+       
+        $this->mValues[$name] = $value;
+        if ($this->mForm) {
+               if (isset($this->mForm->mElements[$name])) {
+                       if ($this->mForm->mElements[$name]->cClassName=="DateField") {
+                               $dt = new DateAndTime($user->date_format, $value);
+                                       $value = $dt->toString(DB_DATEFORMAT);
+                       }
+                       $this->mForm->mElements[$name]->setValueSafe($value);
+               }
+        }
+    }
+
+    function getAttribute($name) {
+        return @$this->mValues[$name];
+    }
+       
+       function getAttributes() {
+        return $this->mValues;
+    }
+
+    function validate(&$actionMapping, &$request) {
+        return null;
+    }
+
+       function setAttributes($value) {
+               global $user;
+               
+        $this->mValues = $value;
+        if (is_array($this->mValues))
+        foreach ($this->mValues as $name=>$value) {
+               if ($this->mForm) {
+                       if (isset($this->mForm->mElements[$name])) {
+                               if ($this->mForm->mElements[$name]->cClassName=="DateField") {
+                                       $dt = new DateAndTime($user->date_format, $value);
+                                               $value = $dt->toString(DB_DATEFORMAT);
+                               }
+                               $this->mForm->mElements[$name]->setValueSafe($value);
+                       }
+               }
+        }
+    }
+    
+    function dump() {
+        print_r($this->mValues);
+    }
+    
+    function isSubmit() {
+        $res = false;
+        if (is_object($this->mForm)) {
+            $res = $this->mForm->isSubmit();
+        }
+        return $res;
+    }
+    
+    function saveBean() {
+       if ($this->mForm) {
+               $elements = $this->mForm->getElements();
+               $el_list = array();
+               foreach ($elements as $el) {
+                       $el_list[] = array("name"=>$el->getName(),"class"=>$el->getClass());
+                       
+                               $_SESSION[$this->mSessionCell . "_" . $el->getName()] = $el->getValueSafe();
+               }
+               $_SESSION[$this->mSessionCell . "session_store_elements"] = $el_list;
+       }
+       //print_r($_SESSION);
+    }
+    
+    function loadBean() {
+       $el_list = @$_SESSION[$this->mSessionCell . "session_store_elements"];
+       if (is_array($el_list)) {
+               foreach ($el_list as $ref_el) {
+                       
+                       // restore form elements
+                       import('form.'.$ref_el["class"]);
+                       $class_name = $ref_el["class"];
+                       $el = new $class_name($ref_el["name"]);
+                       if (isset($GLOBALS["I18N"])) $el->setLocalization($GLOBALS["I18N"]);
+                       $el->setValueSafe(@$_SESSION[$this->mSessionCell . "_" .$el->getName()]);
+                       
+                               if ($this->mForm && !isset($this->mForm->mElements[$ref_el["name"]])) {
+                                       $this->mForm->mElements[$ref_el["name"]] = &$el;
+                               }
+                       $this->mValues[$el->getName()] = $el->getValue();
+               }
+       }
+               //print_r($_SESSION);
+    }
+    
+    function destroyBean() {
+       $el_list = @$_SESSION[$this->mSessionCell . "session_store_elements"];
+       if (is_array($el_list)) {
+               foreach ($el_list as $ref_el) {
+                       unset($_SESSION[$this->mSessionCell . "_" .$ref_el["name"]]);
+               }
+       }
+       unset($_SESSION[$this->mSessionCell . "session_store_elements"]);
+    }
+    
+    function isSaved() {
+       return (isset($_SESSION[$this->mSessionCell . "session_store_elements"]) ? true : false);
+    }
+}
+?>
diff --git a/WEB-INF/lib/form/Calendar.class.php b/WEB-INF/lib/form/Calendar.class.php
new file mode 100644 (file)
index 0000000..811a3fc
--- /dev/null
@@ -0,0 +1,284 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+import('DateAndTime');
+
+class Calendar extends FormElement {
+  var $holidays = array();
+  var $showHolidays = true;
+  var $weekStartDay = 0;
+  
+    var $mHeader = "padding: 5px; font-size: 8pt; color: #333333; background-color: #d9d9d9;";
+    var $mDayCell = "padding: 5px; border: 1px solid silver; font-size: 8pt; color: #333333; background-color: #ffffff;";
+    var $mDaySelected = "padding: 5px; border: 1px solid silver; font-size: 8pt; color: #666666; background-color: #a6ccf7;";
+    var $mDayWeekend = "padding: 5px; border: 1px solid silver; font-size: 8pt; color: #666666; background-color: #f7f7f7;";
+    var $mDayHoliday = "padding: 5px; border: 1px solid silver; font-size: 8pt; color: #666666; background-color: #f7f7f7;";
+    var $mDayHeader = "padding: 5px; border: 1px solid white; font-size: 8pt; color: #333333;";
+    var $mDayHeaderWeekend = "padding: 5px; border: 1px solid white; font-size: 8pt; color: #999999;";
+
+    var $controlName = "";
+    var $highlight = "time"; // Determines what type of active days to highlight ("time" or "expenses"). 
+    // var $mAllDays       = true;
+    var $cClassName    = "Calendar";
+
+    function Calendar($name) {
+        $this->controlName = $name;
+        $this->mMonthNames = array('January','February','March','April','May','June','July','August','September','October','November','December');
+        $this->mWeekDayShortNames = array('Su','Mo','Tu','We','Th','Fr','Sa');
+    }
+    
+    function setHighlight($highlight) {
+       if ($highlight && $highlight != 'time')
+         $this->highlight = $highlight;
+    }
+
+    function setLocalization($i18n) {
+      global $user;
+      
+      FormElement::setLocalization($i18n);
+      $this->mMonthNames    = $i18n->monthNames;
+      $this->mWeekDayShortNames = $i18n->weekdayShortNames;
+      if (is_array($i18n->holidays)) {
+        foreach ($i18n->holidays as $fday) {
+          $date_a = explode("/",$fday); // format mm/dd
+          $this->holidays[] = mktime(0,0,0, $date_a[0], $date_a[1], date("Y"));// + 7200;
+        }
+      }
+      $this->weekStartDay = $user->week_start;
+    }
+
+    function setStyle($style) { $this->mStyle = $style; }
+    function setCellStyle($style) { $this->mCellStyle = $style; }
+    function setACellStyle($style) { $this->mACellStyle = $style; }
+    function setLinkStyle($style) { $this->mLinkStyle = $style; }
+
+    function setShowHolidays($value) {
+      $this->showHolidays = $value;
+    }
+
+    /**
+     * @return void
+     * @param date
+     * @desc Enter description here...
+     */
+    function toString($date="") {
+      global $i18n;
+       
+      $indate = $this->mValue;
+      if (!$indate) $indate = strftime(DB_DATEFORMAT);
+
+      if (!$this->isRenderable()) return "";
+
+      //current year and month
+      if ( strlen ( $indate ) > 0 ) {
+        $indateObj = new DateAndTime(DB_DATEFORMAT, $indate);
+        $thismonth = $indateObj->getMonth();
+        $thisyear = $indateObj->getYear();
+      } else {
+        $thismonth = date("m");
+        $thisyear = date("Y");
+      }
+
+      // next date, month, year
+      $next = mktime ( 2, 0, 0, $thismonth + 1, 1, $thisyear );
+      $nextyear = date ( "Y", $next );
+      $nextmonth = date ( "m", $next );
+      $nextdate = strftime (DB_DATEFORMAT, $next );
+
+      // prev date, month, year
+      $prev = mktime ( 2, 0, 0, $thismonth - 1, 1, $thisyear );
+      $prevyear = date ( "Y", $prev );
+      $prevmonth = date ( "m", $prev );
+      $prevdate = strftime(DB_DATEFORMAT, $prev );
+
+      $str = $this->_genStyles();
+
+      $str .= '<table cellpadding="0" cellspacing="0" border="0" width="100%">
+          <tr><td align="center"><div class="CalendarHeader">'.
+          //'<a href="?date='.$prevyear.'">&lt;&lt;</a> '.
+          '<a href="?date='.$prevdate.'" tabindex="-1">&lt;&lt;&lt;</a>  '.
+          $this->mMonthNames[$thismonth-1].'&nbsp;'.$thisyear.
+          '  <a href="?date='.$nextdate.'" tabindex="-1">&gt;&gt;&gt;</a>'.
+          //' <a href="?date='.$nextyear.'">&gt;&gt;</a>'.
+          '</div></td></tr>
+          </table>';
+
+      $str .= '<center>
+          <table border="0" cellpadding="1" cellspacing="1" width="100%">
+          <tr>';
+
+      $str .= "<tr>";
+
+      $weekend_start = 6 - $this->weekStartDay;      // Saturday by default.
+      $weekend_end = (7 - $this->weekStartDay) % 7;  // Sunday by default.
+      if (defined('WEEKEND_START_DAY')) {
+       $weekend_start = (7 + WEEKEND_START_DAY - $this->weekStartDay) % 7;
+       $weekend_end = (7 + WEEKEND_START_DAY + 1 - $this->weekStartDay) % 7;
+      } 
+
+      for ( $i=0; $i<7; $i++ ) {
+        $weekdayNameIdx = ($i + $this->weekStartDay) % 7;
+        if ($i==$weekend_start || $i==$weekend_end) {
+          $str .= '<td class="CalendarDayHeaderWeekend">'.$this->mWeekDayShortNames[$weekdayNameIdx].'</td>';
+        } else {
+          $str .= '<td class="CalendarDayHeader">'.$this->mWeekDayShortNames[$weekdayNameIdx].'</td>';
+        }
+      }
+
+      $str .= "</tr>\n";
+
+      list($wkstart,$monthstart,$monthend,$start_date) = $this->_getWeekDayBefore( $thisyear, $thismonth );
+
+      $active_dates = $this->_getActiveDates($monthstart, $monthend);
+
+      for ( $i = $wkstart; $i<=$monthend;  $i=mktime(0,0,0,$thismonth,$start_date+=7,$thisyear) ) {
+        $str .= "<TR>\n";
+          for ( $j = 0; $j < 7; $j++ ) {
+            $date = mktime(0,0,0,$thismonth,$start_date+$j,$thisyear);
+            if (($date >= $monthstart) && ($date <= $monthend)) {
+
+            $stl_cell = "";
+            $stl_link = "";
+
+            // weeekend
+            if ($j==$weekend_start || $j==$weekend_end) {
+              $stl_cell = ' class="CalendarDayWeekend"';
+              $stl_link = ' class="CalendarLinkWeekend"';
+            } else {
+              $stl_cell = ' class="CalendarDay"';
+            }
+
+              // holidays
+              if ($this->showHolidays) {
+              foreach ($this->holidays as $day) {
+                if($day == $date) {
+                  $stl_cell = ' class="CalendarDayHoliday"';
+                  $stl_link = ' class="CalendarLinkHoliday"';
+                }
+              }
+            }
+
+            // selected day
+            if ( $indate == strftime(DB_DATEFORMAT, $date))
+              $stl_cell = ' class="CalendarDaySelected"';
+
+
+            $str .= '<td'.$stl_cell.'>';
+
+            // Entries exist.
+            if($active_dates) {
+              if( in_array(strftime(DB_DATEFORMAT, $date), $active_dates) )
+                $stl_link = ' class="CalendarLinkRecordsExist"';
+            }
+
+            $str .= "<a".$stl_link." href=\"?".$this->controlName."=".strftime(DB_DATEFORMAT, $date)."\" tabindex=\"-1\">".date("d",$date)."</a>";
+
+            $str .= "</TD>";
+          }
+          else {
+            $str .= "<TD>&nbsp;</TD>\n";
+          }
+        }
+        $str .= "</TR>\n";
+      }
+
+      $str .= "<tr><td colspan=\"7\" align=\"center\"><a id=\"today_link\" href=\"?".$this->controlName."=".strftime(DB_DATEFORMAT)."\" tabindex=\"-1\">".$i18n->getKey('label.today')."</a></td></tr>\n";
+      $str .= "</table>\n";
+
+      $str .= "<input type=\"hidden\" name=\"$this->controlName\" value=\"$indate\">\n";
+
+      // Add script to adjust today link to match browser today, as PHP may run in a different timezone.
+      $str .= "<script>\n";
+      $str .= "function adjustToday() {\n";
+      $str .= "  var browser_today = new Date();\n";
+      $str .= "  document.getElementById('today_link').href = '?$this->controlName='+browser_today.strftime('".DB_DATEFORMAT."');\n";
+      $str .= "}\n";
+      $str .= "adjustToday();\n";
+      $str .= "</script>\n";
+      
+      return $str;
+    }
+
+    function toStringControl() {
+        return $this->toString();
+    }
+
+    function _getWeekDayBefore($year, $month) {
+      $weekday = date ( "w", mktime ( 2, 0, 0, $month, 1 - $this->weekStartDay, $year ) );
+      return array(
+        mktime ( 0, 0, 0, $month, 1 - $weekday, $year ),
+        mktime ( 0, 0, 0, $month, 1, $year ),
+      mktime ( 0, 0, 0, $month + 1, 0, $year ),
+      (1 - $weekday)
+      );
+    }
+
+    function _genStyles() {
+      $str = "<style>\n";
+      $str .= ".CalendarHeader {". $this->mHeader ."}\n";
+      $str .= ".CalendarDay {". $this->mDayCell  ."}\n";
+      $str .= ".CalendarDaySelected {". $this->mDaySelected  ."}\n";
+      $str .= ".CalendarDayWeekend {". $this->mDayWeekend ."}\n";
+      $str .= ".CalendarDayHoliday {". $this->mDayHoliday ."}\n";
+      $str .= ".CalendarDayHeader {". $this->mDayHeader ."}\n";
+      $str .= ".CalendarDayHeaderWeekend {". $this->mDayHeaderWeekend ."}\n";
+      
+      $str .= ".CalendarLinkWeekend {color: #999999;}\n";
+      $str .= ".CalendarLinkHoliday {color: #999999;}\n";
+      $str .= ".CalendarLinkRecordsExist {color: #FF0000;}\n";
+        $str .= "</style>\n";
+        return $str;
+    }
+    
+    // _getActiveDates returns an array of dates, for which entries exist for user.
+    // Type of entries (time or expenses) is determined by $this->highlight value.
+    function _getActiveDates($start, $end) {
+      
+      global $user;
+      $user_id = $user->getActiveUser();
+      
+      $table = ($this->highlight == 'expenses') ? 'tt_expense_items' : 'tt_log';
+      
+      $mdb2 = getConnection();
+
+      $start_date = date("Y-m-d", $start);
+      $end_date = date("Y-m-d", $end);
+      $sql = "SELECT date FROM $table WHERE date >= '$start_date' AND date <= '$end_date' AND user_id = $user_id AND status = 1";
+      $res = $mdb2->query($sql);
+      if (!is_a($res, 'PEAR_Error')) {
+        while ($row = $res->fetchRow()) {
+          $out[] = date('Y-m-d', strtotime($row['date']));
+        }
+        return @$out;
+      }
+      else
+        return false;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Checkbox.class.php b/WEB-INF/lib/form/Checkbox.class.php
new file mode 100644 (file)
index 0000000..f605edf
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+
+class Checkbox extends FormElement {
+    var $mChecked      = false;
+    var $mOptions      = null;
+    var $cClassName            = "Checkbox";
+
+       function Checkbox($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+
+       function setChecked($value)     { $this->mChecked = $value; }
+       function isChecked() { return $this->mChecked; }
+       
+       function setData($value)        { $this->mOptions = $value; }
+       function getData() { return $this->mOptions; }
+       
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+               $html = "\n\t<input type=\"checkbox\"";
+               $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+               
+               if ($this->mTabindex!="")
+                  $html .= " tabindex=\"$this->mTabindex\"";
+                  
+               if ($this->mOnChange!="")
+                  $html .= " onchange=\"$this->mOnChange\"";
+                  
+               if ($this->mStyle!="")
+                  $html .= " style=\"$this->mStyle\"";
+
+               if ($this->mChecked || (($this->mValue == $this->mOptions) && ($this->mValue != null)))
+                  $html .= " checked=\"true\"";
+                  
+               if (!$this->isEnable())
+                  $html .= " disabled=\"disabled\"";
+                  
+               $html .= " value=\"".htmlspecialchars($this->mOptions)."\"";
+               
+               $html .= "/>\n";   
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/CheckboxCellRenderer.class.php b/WEB-INF/lib/form/CheckboxCellRenderer.class.php
new file mode 100644 (file)
index 0000000..5c3180e
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.DefaultCellRenderer');
+
+class CheckboxCellRenderer extends DefaultCellRenderer {
+
+  function render(&$table, $value, $row, $column, $selected = false) {
+    $html = '<td';
+    $html .= ($this->mWidth!='' ? ' width="'.$this->mWidth.'"' : '');
+    $html .= "><input name=\"".$table->getName()."[]\" id=\"".$table->getName()."_".$row."\" type=\"checkbox\" value=\"".$value."\"";
+    if($this->getOnChangeAdd()) {
+      $html .= " onclick=\"".$this->getOnChangeAdd()."\"";
+    }
+    if ($selected) {
+      $html .= " checked=\"true\"";
+    }
+    $html .= "></td>\n";
+    return $html;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/CheckboxGroup.class.php b/WEB-INF/lib/form/CheckboxGroup.class.php
new file mode 100644 (file)
index 0000000..6fef231
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+
+class CheckboxGroup extends FormElement {
+    var $mChecked      = false;
+    var $mOptions      = array();
+    var $mLayout       = "V";
+    var $mGroupIn      = 1;
+    var $cClassName    = "CheckboxGroup";
+    var $mDataKeys     = array();
+    var $mDataDeep     = 1;
+    var $lSelAll       = "All";
+    var $lSelNone      = "None";
+
+       function CheckboxGroup($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+
+       function setChecked($value)     { $this->mChecked = $value; }
+       function isChecked() { return $this->mChecked; }
+       
+       function setData($value)        { $this->mOptions = $value; }
+       function getData() { return $this->mOptions; }
+       
+       function setDataKeys($keys)     { $this->mDataKeys = $keys; $this->mDataDeep = 2; }
+       function getDataKeys() { return $this->mDataKeys; }
+       
+       function setLayout($value)      { $this->mLayout = $value; }
+       function getLayout() { return $this->mLayout; }
+       
+       function setGroupIn($value)     { $this->mGroupIn = $value; if ($this->mGroupIn<1) $this->mGroupIn = 1;}
+       function getGroupIn() { return $this->mGroupIn; }
+       
+       function setLocalization($i18n) {
+               FormElement::setLocalization($i18n);
+               $this->lSelAll = $i18n->getKey('label.select_all');
+               $this->lSelNone = $i18n->getKey('label.select_none');
+       }
+               
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+           $renderArray = array();
+           $renderCols = 0;
+           $renderRows = 0;
+           
+           if ($this->mLayout=="H") {
+               $i = 0;
+               if (is_array($this->mOptions)) {
+                       $renderCols = $this->mGroupIn;
+                       $renderRows = ceil(count($this->mOptions) / $this->mGroupIn);
+                       $col = $row = 0;
+                           foreach ($this->mOptions as $optkey=>$optval) {
+                               if ($this->mDataDeep>1) {
+                                               $optkey = $optval[$this->mDataKeys[0]];
+                                               $optval = $optval[$this->mDataKeys[1]];
+                                       }
+                               $html = "<input type=\"checkbox\" name=\"$this->mName[]\" id=\"$this->mId"."_".$i."\"";
+                               if (is_array($this->mValue)) {
+                                       foreach ($this->mValue as $value) {
+                                               if (($value == $optkey) && ($value != null))
+                                                       $html .= " checked=\"true\"";
+                                       }
+                               }
+                                       $html .= " value=\"".htmlspecialchars($optkey)."\">&nbsp;<label for=\"$this->mId"."_".$i."\">".htmlspecialchars($optval)."</label>";
+                                       $renderArray[$col][$row] = $html;
+                                       
+                               $col++;                         
+                                       if ($col==$this->mGroupIn) { $col = 0; $row++; }
+                               $i++;
+                           }
+               }
+           }
+           
+           if ($this->mLayout=="V") {
+                       $i = 0;
+               if (is_array($this->mOptions)) {
+                       $renderCols = ceil(count($this->mOptions) / $this->mGroupIn);
+                       $renderRows = $this->mGroupIn;
+                       $col = $row = 0;
+                           foreach ($this->mOptions as $optkey=>$optval) {
+                               if ($this->mDataDeep>1) {
+                                               $optkey = $optval[$this->mDataKeys[0]];
+                                               $optval = $optval[$this->mDataKeys[1]];
+                                       }
+                               $html = "<input type=\"checkbox\" name=\"$this->mName[]\" id=\"$this->mId"."_".$i."\"";
+                               if (is_array($this->mValue)) {
+                                       foreach ($this->mValue as $value) {
+                                               if (($value == $optkey) && ($value != null))
+                                                       $html .= " checked=\"true\"";
+                                       }
+                               }
+                                       $html .= " value=\"".htmlspecialchars($optkey)."\">&nbsp;<label for=\"$this->mId"."_".$i."\">".htmlspecialchars($optval)."</label>";
+                                       $renderArray[$col][$row] = $html;
+
+                                       $row++;
+                               if ($row==$this->mGroupIn) { $row = 0; $col++; }
+                               $i++;
+                           }
+               }
+           }
+           
+           
+           $html = "\n\t<table style=\"".$this->mStyle."\"><tr><td align=\"center\" bgcolor=\"eeeeee\">\n";
+           $html .= '<a href="#" onclick="setAll'.$this->getName().'(true);return false;">'.$this->lSelAll.'</a>&nbsp;/&nbsp;<a href="#" onclick="setAll'.$this->getName().'(false);return false;">'.$this->lSelNone.'</a>';
+           $html .= "</td></tr>\n";
+           $html .= "<tr><td>";
+           $html .= "\n\t<table width=\"100%\">\n";
+           for ($i = 0; $i < $renderRows; $i++) {
+               $html .= "<tr>";
+               for ($j = 0; $j < $renderCols; $j++) {
+                       $html .= "\t<td width=\"".(floor(100/$renderCols))."%\">".(isset($renderArray[$j][$i])?$renderArray[$j][$i]:"&nbsp;")."</td>\n";
+               }
+               $html .= "</tr>\n";
+           }
+           $html .= "</table>\n";
+           $html .= "</td></tr></table>\n";
+           
+           $str = "<script>\n";
+               $str .= "function setAll".$this->getName()."(value) {\n";
+               $str .= "\tvar formInputs = document.getElementsByTagName(\"input\");\n";
+               $str .= "\tfor (var i = 0; i < formInputs.length; i++) {\n";
+        $str .= "\t\tif ((formInputs.item(i).type=='checkbox') && (formInputs.item(i).name=='".$this->getName()."[]')) {\n";
+        $str .= "\t\tformInputs.item(i).checked=value;\n";
+        $str .= "\t}\n}\n";
+               $str .= "}\n";
+               $str .= "</script>\n";
+           
+               return $html.$str;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Combobox.class.php b/WEB-INF/lib/form/Combobox.class.php
new file mode 100644 (file)
index 0000000..31b89be
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+//  name        CDATA          #IMPLIED  -- field name --
+//  size        NUMBER         #IMPLIED  -- rows visible --
+//  multiple    (multiple)     #IMPLIED  -- default is single selection --
+//  disabled    (disabled)     #IMPLIED  -- unavailable in this context --
+//  tabindex    NUMBER         #IMPLIED  -- position in tabbing order --
+//  onfocus     %Script;       #IMPLIED  -- the element got the focus --
+//  onblur      %Script;       #IMPLIED  -- the element lost the focus --
+//  onchange    %Script;       #IMPLIED  -- the element value was changed --
+
+class Combobox extends FormElement {
+    var $mMultiple     = false;
+    var $mOptions      = array();
+    var $mOptionsEmpty = array();
+    var $mCompareOn = "key"; // or "value"
+    var $mDataDeep = 1;
+    var $mDataKeys = array();
+    var $cClassName    = "Combobox";
+
+       function Combobox($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+
+       function setMultiple($value)    { $this->mMultiple = $value; }
+       function isMultiple() { return $this->mMultiple; }
+       
+       function setData($value)        { $this->mOptions = $value; }
+       function getData() { return $this->mOptions; }
+       
+       function setDataDefault($value) { $this->mOptionsEmpty = $value; }
+       function getDataDefault() { return $this->mOptionsEmpty; }
+       
+       function setDataKeys($keys)     { $this->mDataKeys = $keys; $this->mDataDeep = 2; }
+       function getDataKeys() { return $this->mDataKeys; }
+       
+       
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+               $html = "\n\t<select";
+               $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+               
+               if ($this->mSize!="")
+                 $html .= " size=\"$this->mSize\"";
+                
+               if ($this->mMultiple)
+                 $html .= " multiple";
+                 
+               if ($this->mTabindex!="")
+                  $html .= " tabindex=\"$this->mTabindex\"";
+                  
+               if ($this->mOnChange!="")
+                  $html .= " onchange=\"$this->mOnChange\"";
+                  
+               if ($this->mStyle!="")
+                  $html .= " style=\"$this->mStyle\"";
+                  
+               $html .= ">\n";   
+               if (is_array($this->mOptionsEmpty) && (count($this->mOptionsEmpty) > 0))
+               foreach ($this->mOptionsEmpty as $key=>$value) {
+                       $html .= "<option value=\"".$key."\"";
+                       if (($this->mValue == $value) && ($this->mValue != '')) $html .= " selected";
+                       $html .= ">".$value."</option>\n";
+               }
+               if (is_array($this->mOptions) && (count($this->mOptions) > 0))
+               foreach ($this->mOptions as $key=>$value) {
+
+                       if ($this->mDataDeep>1) {
+                               $key = $value[$this->mDataKeys[0]];
+                               $value = $value[$this->mDataKeys[1]];
+                       }
+                       $html .= "<option value=\"".$key."\"";
+                       if (($this->mValue == $key) && ($this->mValue != '')) $html .= " selected";
+                       $html .= ">".htmlspecialchars($value)."</option>\n";
+               }
+               
+               $html .= "</select>";
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/DateField.class.php b/WEB-INF/lib/form/DateField.class.php
new file mode 100644 (file)
index 0000000..aa8a149
--- /dev/null
@@ -0,0 +1,430 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.TextField');
+import('DateAndTime');
+
+class DateField extends TextField {
+  var $mWeekStartDay = 0;
+  var $mDateFormat  = "d/m/Y";
+  var $lToday      = "Today";
+
+  var $mDateObj;
+  var $cClassName  = "DateField";
+
+  var $lCalendarButtons = array('today'=>'Today', 'close'=>'Close');
+
+  function DateField($name) {
+    $this->mName  = $name;
+    $this->mDateObj  = new DateAndTime();
+
+    if (isset($GLOBALS["I18N"])) {
+      $this->setLocalization($GLOBALS["I18N"]);
+    }
+  }
+
+  function setLocalization($i18n)  {
+       global $user;
+       
+    FormElement::setLocalization($i18n);
+    $this->mDateObj->setFormat($user->date_format);
+
+    $this->mMonthNames = $i18n->monthNames;
+    $this->mWeekDayShortNames = $i18n->weekdayShortNames;
+    $this->lToday = $i18n->getKey('label.today');
+    $this->lCalendarButtons['today'] = $i18n->getKey('label.today');
+    $this->lCalendarButtons['close'] = $i18n->getKey('button.close');
+
+    $this->mDateFormat = $user->date_format;
+    $this->mWeekStartDay = $user->week_start;
+  }
+
+  // set current value taken from session or database
+  function setValueSafe($value)  {
+    if (isset($value) && (strlen($value) > 0)) {
+      $this->mDateObj->parseVal($value, DB_DATEFORMAT);
+      $this->mValue = $this->mDateObj->toString($this->mDateFormat); //?
+    }
+  }
+  // get value for storing in session or database
+  function getValueSafe() {
+    if (strlen($this->mValue)>0) {
+      $this->mDateObj->parseVal($this->mValue, $this->mDateFormat);  //?
+      return $this->mDateObj->toString(DB_DATEFORMAT);
+    } else {
+      return null;
+    }
+  }
+
+  function toStringControl()  {
+    if (!$this->isRenderable()) return "";
+
+    if (!$this->isEnable()) {
+      $html = htmlspecialchars($this->getValue()).
+        "<input type=\"hidden\" name=\"$this->mName\" value=\"".htmlspecialchars($this->getValue())."\">\n";
+    } else {
+
+        if ($this->mId=="") $this->mId = $this->mName;
+
+      $html = "";
+
+      // http://www.nsftools.com/tips/JavaScriptTips.htm#datepicker
+
+      $html .= "<style>
+            .dpDiv {}
+            .dpTable {font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: 12px; text-align: center; color: #505050; background-color: #ece9d8; border: 1px solid #AAAAAA;}
+            .dpTR {}
+            .dpTitleTR {}
+            .dpDayTR {}
+            .dpTodayButtonTR {}
+            .dpTD {border: 1px solid #ece9d8;}
+            .dpDayHighlightTD {background-color: #CCCCCC;border: 1px solid #AAAAAA;}
+            .dpTDHover {background-color: #aca998;border: 1px solid #888888;cursor: pointer;color: red;}
+            .dpTitleTD {}
+            .dpButtonTD {}
+            .dpTodayButtonTD {}
+            .dpDayTD {background-color: #CCCCCC;border: 1px solid #AAAAAA;color: white;}
+            .dpTitleText {font-size: 12px;color: gray;font-weight: bold;}
+            .dpDayHighlight {color: 4060ff;font-weight: bold;}
+            .dpButton {font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;font-size: 10px;color: gray;background: #d8e8ff;font-weight: bold;padding: 0px;}
+            .dpTodayButton {font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;font-size: 10px;color: gray;  background: #d8e8ff;font-weight: bold;}
+            </style>\n";
+      $html .= "<script>
+            var datePickerDivID = \"datepicker\";
+            var iFrameDivID = \"datepickeriframe\";
+
+            var dayArrayShort = new Array('".join("','",$this->mWeekDayShortNames)."');
+            var dayArrayMed = new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
+            var dayArrayLong = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+            var monthArrayShort = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
+            var monthArrayMed = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec');
+            var monthArrayLong = new Array('".join("','",$this->mMonthNames)."');
+
+            var defaultDateSeparator = \"".$this->mDateFormat[1]."\";
+            var defaultDateFormat = \"".$this->mDateFormat."\";
+            var dateSeparator = defaultDateSeparator;
+            var dateFormat = defaultDateFormat;
+            var startWeek = ".$this->mWeekStartDay.";
+
+          ";
+      $html .= "
+
+            function getStartWeekDayNumber(date) {
+              var res = date.getDay() - startWeek;
+              if (res < 0) {
+                res += 7;
+              }
+              return res;
+            }
+
+            function displayDatePicker(dateFieldName, displayBelowThisObject, dtFormat, dtSep) {
+              var targetDateField = document.getElementsByName(dateFieldName).item(0);
+
+              if (!displayBelowThisObject) displayBelowThisObject = targetDateField;
+              if (dtSep)
+                dateSeparator = dtSep;
+              else
+                dateSeparator = defaultDateSeparator;
+
+              if (dtFormat)
+                dateFormat = dtFormat;
+              else
+                dateFormat = defaultDateFormat;
+
+              var x = displayBelowThisObject.offsetLeft;
+              var y = displayBelowThisObject.offsetTop + displayBelowThisObject.offsetHeight ;
+
+              var parent = displayBelowThisObject;
+              while (parent.offsetParent) {
+                parent = parent.offsetParent;
+                x += parent.offsetLeft;
+                y += parent.offsetTop ;
+              }
+
+              drawDatePicker(targetDateField, x, y);
+            }
+            function drawDatePicker(targetDateField, x, y) {
+              var dt = getFieldDate(targetDateField.value );
+
+              if (!document.getElementById(datePickerDivID)) {
+                var newNode = document.createElement(\"div\");
+                newNode.setAttribute(\"id\", datePickerDivID);
+                newNode.setAttribute(\"class\", \"dpDiv\");
+                newNode.setAttribute(\"style\", \"visibility: hidden;\");
+                document.body.appendChild(newNode);
+              }
+
+              var pickerDiv = document.getElementById(datePickerDivID);
+              pickerDiv.style.position = \"absolute\";
+              pickerDiv.style.left = x + \"px\";
+              pickerDiv.style.top = (y + 3) + \"px\";
+              pickerDiv.style.visibility = (pickerDiv.style.visibility == \"visible\" ? \"hidden\" : \"visible\");
+              pickerDiv.style.display = (pickerDiv.style.display == \"block\" ? \"none\" : \"block\");
+              pickerDiv.style.zIndex = 10000;
+
+              refreshDatePicker(targetDateField.name, dt.getFullYear(), dt.getMonth(), dt.getDate());
+            }
+            function refreshDatePicker(dateFieldName, year, month, day) {
+              var thisDay = new Date();
+
+              if ((month >= 0) && (year > 0)) {
+                thisDay = new Date(year, month, 1);
+              } else {
+                day = thisDay.getDate();
+                thisDay.setDate(1);
+              }
+
+              var crlf = \"\\r\\n\";
+              var TABLE = \"<table cols=7 class='dpTable'>\" + crlf;
+              var xTABLE = \"</table>\" + crlf;
+              var TR = \"<tr class='dpTR'>\";
+              var TR_title = \"<tr class='dpTitleTR' width='150' align='center'>\";
+              var TR_days = \"<tr class='dpDayTR'>\";
+              var TR_todaybutton = \"<tr class='dpTodayButtonTR'>\";
+              var xTR = \"</tr>\" + crlf;
+              var TD = \"<td class='dpTD' onMouseOut='this.className=\\\"dpTD\\\";' onMouseOver=' this.className=\\\"dpTDHover\\\";' \";
+              var TD_title = \"<td colspan=5 class='dpTitleTD'>\";
+              var TD_buttons = \"<td class='dpButtonTD' width='50'>\";
+              var TD_todaybutton = \"<td colspan=7 class='dpTodayButtonTD'>\";
+              var TD_days = \"<td class='dpDayTD'>\";
+              var TD_selected = \"<td class='dpDayHighlightTD' onMouseOut='this.className=\\\"dpDayHighlightTD\\\";' onMouseOver='this.className=\\\"dpTDHover\\\";' \";
+              var xTD = \"</td>\" + crlf;
+              var DIV_title = \"<div class='dpTitleText'>\";
+              var DIV_selected = \"<div class='dpDayHighlight'>\";
+              var xDIV = \"</div>\";
+
+              var html = TABLE;
+
+              html += TR_title + '<td colspan=7>';
+              html += '<table width=\"250\">'+ TR_title;
+              html += TD_buttons + getButtonCodeYear(dateFieldName, thisDay, -1, \"&lt;&lt;\") + getButtonCode(dateFieldName, thisDay, -1, \"&lt;\") + xTD;
+              html += TD_title + DIV_title + monthArrayLong[ thisDay.getMonth()] + \" \" + thisDay.getFullYear() + xDIV + xTD;
+              html += TD_buttons + getButtonCode(dateFieldName, thisDay, 1, \"&gt;\") + getButtonCodeYear(dateFieldName, thisDay, 1, \"&gt;&gt;\") + xTD;
+              html += xTR + '</table>' + xTD;
+              html += xTR;
+
+              html += TR_days;
+              for(i = 0; i < dayArrayShort.length; i++)
+                html += TD_days + dayArrayShort[(i + startWeek) % 7] + xTD;
+              html += xTR;
+
+              html += TR;
+
+              //var startD = (thisDay.getDay()-startWeek<0?6:thisDay.getDay()-startWeek);
+              var startD = getStartWeekDayNumber(thisDay);
+              for (i = 0; i < startD; i++)
+                html += TD + \"&nbsp;\" + xTD;
+
+              do {
+                dayNum = thisDay.getDate();
+                TD_onclick = \" onclick=\\\"updateDateField('\" + dateFieldName + \"', '\" + getDateString(thisDay) + \"');\\\">\";
+
+                if (dayNum == day)
+                  html += TD_selected + TD_onclick + DIV_selected + dayNum + xDIV + xTD;
+                else
+                  html += TD + TD_onclick + dayNum + xTD;
+
+                var startD = getStartWeekDayNumber(thisDay);
+
+                if (startD == 6)
+                  html += xTR + TR;
+
+                thisDay.setDate(thisDay.getDate() + 1);
+              } while (thisDay.getDate() > 1)
+
+              var startD = getStartWeekDayNumber(thisDay);
+              if (startD > 0) {
+                for (i = 6; i >= startD; i--) {
+                  html += TD + \"&nbsp;\" + xTD;
+                }
+              }              
+              html += xTR;
+
+              var today = new Date();
+              var todayString = \"Today is \" + dayArrayMed[today.getDay()] + \", \" + monthArrayMed[ today.getMonth()] + \" \" + today.getDate();
+              html += TR_todaybutton + TD_todaybutton;
+              html += \"<button class='dpTodayButton' onClick=\\\"refreshDatePicker('\" + dateFieldName + \"'); updateDateFieldOnly('\" + dateFieldName + \"', '\" + getDateString(new Date()) + \"');\\\">".$this->lCalendarButtons['today']."</button> \";
+              html += \"<button class='dpTodayButton' onClick='updateDateField(\\\"\" + dateFieldName + \"\\\");'>".$this->lCalendarButtons['close']."</button>\";
+              html += xTD + xTR;
+
+              html += xTABLE;
+
+              document.getElementById(datePickerDivID).innerHTML = html;
+              adjustiFrame();
+            }
+
+
+            function getButtonCode(dateFieldName, dateVal, adjust, label) {
+              var newMonth = (dateVal.getMonth () + adjust) % 12;
+              var newYear = dateVal.getFullYear() + parseInt((dateVal.getMonth() + adjust) / 12);
+              if (newMonth < 0) {
+                newMonth += 12;
+                newYear += -1;
+              }
+
+              return \"<button class='dpButton' onClick='refreshDatePicker(\\\"\" + dateFieldName + \"\\\", \" + newYear + \", \" + newMonth + \");'>\" + label + \"</button>\";
+            }
+
+            function getButtonCodeYear(dateFieldName, dateVal, adjust, label) {
+              var newMonth = dateVal.getMonth();
+              var newYear = dateVal.getFullYear() + adjust;
+
+              return \"<button class='dpButton' onClick='refreshDatePicker(\\\"\" + dateFieldName + \"\\\", \" + newYear + \", \" + newMonth + \");'>\" + label + \"</button>\";
+            }
+
+
+            function getDateString(dateVal) {\n";
+            if (isset($GLOBALS['i18n'])) {
+              $html .= "dateVal.locale = \"".$GLOBALS['i18n']->lang."\";\n";
+            }
+            $html .=  "return dateVal.strftime(dateFormat);
+            }
+
+            function getFieldDate(dateString) {
+              try {
+                var dateVal = strptime(dateString, dateFormat);
+              } catch(e) {
+                dateVal = new Date();
+              }
+              if (dateVal == null) {
+                dateVal = new Date();
+              }
+              return dateVal;
+            }
+
+            function splitDateString(dateString) {
+              var dArray;
+              if (dateString.indexOf(\"/\") >= 0)
+                dArray = dateString.split(\"/\");
+              else if (dateString.indexOf(\".\") >= 0)
+                dArray = dateString.split(\".\");
+              else if (dateString.indexOf(\"-\") >= 0)
+                dArray = dateString.split(\"-\");
+              else if (dateString.indexOf(\"\\\\\") >= 0)
+                dArray = dateString.split(\"\\\\\");
+              else
+                dArray = false;
+
+              return dArray;
+            }
+
+            function updateDateField(dateFieldName, dateString)  {
+              var targetDateField = document.getElementsByName(dateFieldName).item(0);
+              if (dateString)
+                targetDateField.value = dateString;
+
+              var pickerDiv = document.getElementById(datePickerDivID);
+              pickerDiv.style.visibility = \"hidden\";
+              pickerDiv.style.display = \"none\";
+
+              adjustiFrame();
+              targetDateField.focus();
+
+              if ((dateString) && (typeof(datePickerClosed) == \"function\"))
+                datePickerClosed(targetDateField);
+            }
+
+            function updateDateFieldOnly(dateFieldName, dateString)  {
+              var targetDateField = document.getElementsByName(dateFieldName).item(0);
+              if (dateString)
+                targetDateField.value = dateString;
+            }
+
+            function adjustiFrame(pickerDiv, iFrameDiv) {
+              var is_opera = (navigator.userAgent.toLowerCase().indexOf(\"opera\") != -1);
+              if (is_opera)
+                return;
+
+              try {
+                if (!document.getElementById(iFrameDivID)) {
+                  var newNode = document.createElement(\"iFrame\");
+                  newNode.setAttribute(\"id\", iFrameDivID);
+                  newNode.setAttribute(\"src\", \"javascript:false;\");
+                  newNode.setAttribute(\"scrolling\", \"no\");
+                  newNode.setAttribute (\"frameborder\", \"0\");
+                  document.body.appendChild(newNode);
+                }
+
+                if (!pickerDiv)
+                  pickerDiv = document.getElementById(datePickerDivID);
+                if (!iFrameDiv)
+                  iFrameDiv = document.getElementById(iFrameDivID);
+
+                try {
+                  iFrameDiv.style.position = \"absolute\";
+                  iFrameDiv.style.width = pickerDiv.offsetWidth;
+                  iFrameDiv.style.height = pickerDiv.offsetHeight ;
+                  iFrameDiv.style.top = pickerDiv.style.top;
+                  iFrameDiv.style.left = pickerDiv.style.left;
+                  iFrameDiv.style.zIndex = pickerDiv.style.zIndex - 1;
+                  iFrameDiv.style.visibility = pickerDiv.style.visibility ;
+                  iFrameDiv.style.display = pickerDiv.style.display;
+                } catch(e) {
+                }
+
+              } catch (ee) {
+              }
+            }\n";
+      $html .= "</script>\n";
+
+      $html .= "\n\t<input type=\"text\"";
+      $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+
+      if ($this->mSize!="")
+        $html .= " size=\"$this->mSize\"";
+
+      if ($this->mStyle!="")
+         $html .= " style=\"$this->mStyle\"";
+
+        $html .= " maxlength=\"50\"";
+
+      if ($this->mOnChange!="")
+         $html .= " onchange=\"$this->mOnChange\"";
+
+      if ($this->mOnBlur!="")
+         $html .= " onblur=\"$this->mOnBlur\"";
+
+      if ($this->mOnClick!="")
+         $html .= " onclick=\"$this->mOnClick\"";
+
+      if ($this->mOnFocus!="")
+         $html .= " onfocus=\"$this->mOnFocus\"";
+
+      $html .= " value=\"".htmlspecialchars($this->getValue())."\"";
+      $html .= ">";
+      
+      if (APP_NAME)
+       $app_root = '/'.APP_NAME;
+
+      $html .= "&nbsp;<img src=\"".$app_root."/images/calendar.gif\" width=\"16\" height=\"16\" onclick=\"displayDatePicker('".$this->mName."');\">\n";
+    }
+
+    return $html;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/DefaultCellRenderer.class.php b/WEB-INF/lib/form/DefaultCellRenderer.class.php
new file mode 100644 (file)
index 0000000..adbd141
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class DefaultCellRenderer {
+       var $mCellValue         = null;
+       var $mCellOptions       = array();
+       var $mWidth                     = null;
+       var $mOnChangeAdd       = null;
+       
+       function DefaultCellRenderer() {
+               
+       }
+
+       function getValue() { return $this->mCellValue; }
+       function setValue($value) { $this->mCellValue = $value; }
+
+       function getOptions() { return $this->mCellOptions; }
+       function setOptions($value) { $this->mCellOptions = $value; }
+       
+       function getOnChangeAdd() { return $this->mOnChangeAdd; }
+       function setOnChangeAdd($value) { $this->mOnChangeAdd = $value; }
+
+       function toStringOpenTag() {
+               $html = "<td";
+               foreach ($this->mCellOptions as $k=>$v) {
+                       $html .= " $k=\"$v\"";
+               }
+               $html .= ">";
+               return $html;
+       }
+       
+       function toStringCloseTag() {
+               return "</td>";
+       }
+       
+       function toStringValue($value) {
+               return ($this->mCellValue=='' || $this->mCellValue==null ? '&nbsp;' : $this->mCellValue);
+       }
+       
+       function toString() {
+               return $this->toStringOpenTag() . $this->toStringValue('') . $this->toStringCloseTag();
+       }
+       
+       function render(&$table, $value, $row, $column, $selected = false) {
+               $this->setValue($value);
+               return $this->toString();
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/FloatField.class.php b/WEB-INF/lib/form/FloatField.class.php
new file mode 100644 (file)
index 0000000..370b28b
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.TextField');
+       
+class FloatField extends TextField {
+       var $mDelimiter = '.';
+       var $mFFormat;
+       var $cClassName = "FloatField";
+
+       function FloatField($name) {
+               $this->mName    = $name;
+       }
+       
+       function setLocalization($i18n) {
+               FormElement::setLocalization($i18n);
+               global $user;
+               $this->mDelimiter = $user->decimal_mark;
+       }
+       
+       function setFormat($format)     {
+               $this->mFFormat = $format;
+       }
+       
+       function setValue($value) {
+               if (isset($this->mFFormat) && isset($value) && strlen($value)) {
+                       $value = str_replace($this->mDelimiter,".",$value);
+                       $value = sprintf("%".$this->mFFormat."f",$value);
+                       $value = str_replace(".",$this->mDelimiter,$value);
+               }
+               $this->mValue = $value;
+       }
+       
+       function setValueSafe($value)   {
+               // '.' to ',' , apply localisation 
+               if (strlen($value)>0)
+                       $this->mValue = str_replace(".",$this->mDelimiter,$value);
+       }
+       
+       function getValueSafe() {
+               // ',' to '.'
+               if (strlen($this->mValue)>0) {
+                       return str_replace($this->mDelimiter,".",$this->mValue);
+               } else {
+                       return null;
+               }
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Form.class.php b/WEB-INF/lib/form/Form.class.php
new file mode 100644 (file)
index 0000000..340637d
--- /dev/null
@@ -0,0 +1,321 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// +----------------------------------------------------------------------+
+// |
+// | Class generates elements of specification HTML 4.01
+// | http://www.w3.org/TR/1999/REC-html401-19991224
+// |
+// +----------------------------------------------------------------------+
+
+class Form {
+    var $formName      = "";
+       var $mAction       = "";
+       var $mMethod       = "post";
+       var $mEnctype      = "";
+       var $mId           = "";
+    var $error;
+       var $debugFunction;
+       var $mElements     = array();
+       var $mRequest;
+//     var $mFormBean;
+    
+    function Form($formid) {
+        $this->formName = $formid;
+    }
+    
+    function setRequest(&$request) {
+        $this->mRequest = &$request;
+    }
+    
+/*    function setFormBean(&$bean) {
+        $this->mFormBean = &$bean;
+    }
+*/    
+    function &getElement($name) {
+       return $this->mElements[$name];
+    }
+    
+    function &getElements() {
+       return $this->mElements;
+    }
+    
+       //// FORM element
+       // action
+       // method - GET, POST
+       // enctype - enctype="multipart/form-data"
+       // name
+       // onsubmit
+       // onreset
+       function setName($value) { $this->formName = $value; }
+    function getName() { return $this->formName; }
+    
+    function setId($value) { $this->mId = $value; }
+    function getId() { return $this->mId; }
+    
+    function setAction($value) { $this->mAction = $value; }
+    function getAction() { return $this->mAction; }
+    
+    function setMethod($value) { $this->mMethod = $value; }
+    function getMethod() { return $this->mMethod; }
+    
+    function setEnctype($value) { $this->mEnctype = $value; }
+    function getEnctype() { return $this->mEnctype; }
+    
+    function isSubmit()        {
+       if (!isset($this->mRequest)) return false;
+        $result = false;
+           foreach ($this->mElements as $el) {
+               if (strtolower(get_class($el))=="submit") {
+                   $name = $el->getName();
+                   $value = $this->mRequest->getAttribute($name);
+                   if($value) {
+                      $result = true; 
+                   }
+               }
+           }
+        return $result;
+    }
+       
+       function OutputError($error,$scope="")
+       {
+               $this->error=(strcmp($scope,"") ? $scope.": ".$error : $error);
+               if(strcmp($function=$this->debugFunction,"")
+               && strcmp($this->error,""))
+                       $function($this->error);
+               return($this->error);
+       }
+       
+       //// INPUT element
+       // type = TEXT | PASSWORD | CHECKBOX | RADIO | SUBMIT | RESET | FILE | HIDDEN | IMAGE | BUTTON
+       // name
+       // value
+       // checked - for type radio and checkbox
+       // size - width pixels or chars
+       // maxlength
+       // src - for type image
+       // tabindex - support  A, AREA, BUTTON, INPUT, OBJECT, SELECT, and TEXTAREA
+       // accesskey - support A, AREA, BUTTON, INPUT, LABEL, and LEGEND, and TEXTAREA
+       // onfocus
+       // onblur
+       // onselect -  INPUT and TEXTAREA
+       // onchange
+       function addInput($arguments) {
+               if(strcmp(gettype($arguments),"array"))
+                       $this->OutputError("arguments must be array","AddInput");
+                       
+               if(!isset($arguments["type"]) || !strcmp($arguments["type"],""))
+                       return($this->OutputError("Type not defined","AddInput"));
+                       
+               if(!isset($arguments["name"]) || !strcmp($arguments["name"],""))
+                       return($this->OutputError("Name of element not defined","AddInput"));
+                       
+               if (isset($this->mElements[$arguments["name"]]))
+                   return($this->OutputError("it was specified '".$arguments["name"]."' name of an already defined input","AddInput"));
+                       
+               switch($arguments["type"]) {
+                   
+                       case "textfield":
+                       case "text":
+                           import('form.TextField');
+                           $el = new TextField($arguments["name"]);
+                           $el->setMaxLength(@$arguments["maxlength"]);
+                           if (isset($arguments["aspassword"])) $el->setAsPassword($arguments["aspassword"]);
+                           break;
+                           
+                       case "datefield":
+                           import('form.DateField');
+                           $el = new DateField($arguments["name"]);
+                               $el->setMaxLength("10");
+                           break;
+                           
+                       case "floatfield":
+                           import('form.FloatField');
+                           $el = new FloatField($arguments["name"]);
+                           if (isset($arguments["format"])) $el->setFormat($arguments["format"]);
+                           break;
+                           
+                       case "textarea":
+                           import('form.TextArea');
+                           $el = new TextArea($arguments["name"]);
+                           $el->setColumns(@$arguments["cols"]);
+                           $el->setRows(@$arguments["rows"]);
+                           if (isset($arguments["maxlength"])) $el->setMaxLength($arguments["maxlength"]);
+                           break;
+                           
+                       case "checkbox":
+                           import('form.Checkbox');
+                           $el = new Checkbox($arguments["name"]);
+                           if (@$arguments["checked"]) $el->setChecked(true);
+                           $el->setData(@$arguments["data"]);
+                           break;
+                           
+                       case "checkboxgroup":
+                           import('form.CheckboxGroup');
+                           $el = new CheckboxGroup($arguments["name"]);
+                           if (isset($arguments["layout"])) $el->setLayout($arguments["layout"]);
+                           if (isset($arguments["groupin"])) $el->setGroupIn($arguments["groupin"]);
+                           if (isset($arguments["datakeys"])) $el->setDataKeys($arguments["datakeys"]);
+                           $el->setData(@$arguments["data"]);
+                           break;
+                           
+                       case "combobox":
+                           import('form.Combobox');
+                           $el = new Combobox($arguments["name"]);
+                           $el->setData(@$arguments["data"]);
+                           $el->setDataDefault(@$arguments["empty"]);
+                           if (isset($arguments["datakeys"])) $el->setDataKeys($arguments["datakeys"]);
+                           break;
+                           
+                       case "hidden":
+                           import('form.Hidden');
+                           $el = new Hidden($arguments["name"]);
+                           break;
+                        
+                       case "submit":
+                           import('form.Submit');
+                           $el = new Submit($arguments["name"]);
+                           break;
+                           
+                       case "calendar":
+                           import('form.Calendar');
+                           $el = new Calendar($arguments["name"]);
+                           $el->setHighlight(@$arguments["highlight"]);
+                           break;  
+                           
+                       case "table":
+                           import('form.Table');
+                           $el = new Table($arguments["name"]);
+                           $el->setData(@$arguments["data"]);
+                           $el->setWidth(@$arguments["width"]);
+                           break;
+                           
+                       case "upload":
+                           import('form.UploadFile');
+                           $el = new UploadFile($arguments["name"]);
+                           if (isset($arguments["maxsize"])) $el->setMaxSize($arguments["maxsize"]);
+                           break;
+                             
+                       default:
+                               return($this->OutputError("Type not found for input element","AddInput"));
+               }
+               if ($el!=null) {
+                       $el->setFormName($this->formName);
+                       if (isset($arguments["id"])) $el->setId($arguments["id"]);
+                       if (isset($GLOBALS["I18N"])) $el->setLocalization($GLOBALS["I18N"]);
+                       if (isset($arguments["render"])) $el->setRenderable($arguments["render"]);
+                       if (isset($arguments["enable"])) $el->setEnable($arguments["enable"]);
+                       
+                       if (isset($arguments["style"])) $el->setStyle($arguments["style"]);
+                       if (isset($arguments["size"])) $el->setSize($arguments["size"]);
+                       
+                       if (isset($arguments["label"])) $el->setLabel($arguments["label"]);
+                       if (isset($arguments["value"])) $el->setValue($arguments["value"]);
+                       
+                       if (isset($arguments["onchange"])) $el->setOnChange($arguments["onchange"]);
+                       if (isset($arguments["onclick"])) $el->setOnClick($arguments["onclick"]);
+                       
+                       $this->mElements[$arguments["name"]] = &$el;
+               }
+       }
+       
+       function addInputElement(&$el) {
+               if ($el && is_object($el)) {
+                       if (!$el->getName())
+                           return($this->OutputError("no name in element","addInputElement"));
+                           
+                       if (isset($GLOBALS["I18N"])) $el->setLocalization($GLOBALS["I18N"]);
+               
+                       $el->setFormName($this->formName);
+                       $this->mElements[$el->getName()] = &$el;
+               }
+       }
+       
+       
+       function toStringOpenTag() {
+        $html = "<form name=\"$this->formName\"";
+        
+        if ($this->mId!="") 
+            $html .= " id=\"$this->mId\"";
+            
+        if ($this->mAction!="") 
+            $html .= " action=\"$this->mAction\"";
+        
+        if ($this->mMethod!="") 
+            $html .= " method=\"$this->mMethod\"";
+        
+        // for upload forms
+        foreach ($this->mElements as $elname=>$el) {
+            if (strtolower(get_class($this->mElements[$elname]))=="uploadfile") {
+               $this->mEnctype = "multipart/form-data";
+            }
+        }
+        
+        if ($this->mEnctype!="")
+               $html .= " enctype=\"$this->mEnctype\"";
+        
+        $html .= ">";
+        return $html;
+    }
+    
+    function toStringCloseTag() {
+       $html = "\n";
+       foreach ($this->mElements as $elname=>$el) {
+            if (strtolower(get_class($this->mElements[$elname]))=="hidden") {
+               $html .= $this->mElements[$elname]->toStringControl()."\n";
+            }
+        }
+        $html .= "</form>";
+        return $html;
+    }
+       
+       function toArray() {
+        $vars = array();
+        $vars['open'] = $this->toStringOpenTag();
+        $vars['close'] = $this->toStringCloseTag();
+        
+        foreach ($this->mElements as $elname=>$el) {
+            if (is_object($this->mElements[$elname])) 
+                $vars[$elname] = $this->mElements[$elname]->toArray();
+        }
+//print_r($vars);
+        return $vars;
+    }
+    
+    function getValueByElement($elname) {
+       return $this->mElements[$elname]->getValue();
+    }
+    
+    function setValueByElement($elname, $value) {
+       if (isset($this->mElements[$elname])) {
+               $this->mElements[$elname]->setValue($value);
+       }
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/FormElement.class.php b/WEB-INF/lib/form/FormElement.class.php
new file mode 100644 (file)
index 0000000..9e27392
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class FormElement {
+       var $mId                        = "";
+       var $mName;
+       var $mFormName          = "";
+       var $mValue                     = "";
+       var $mSize                      = "";
+       var $mMaxLength         = "";
+       var $mTabindex          = "";
+       var $mAccesskey     = "";
+       var $mOnBlur            = "";
+       var $mOnSelect          = "";
+       var $mOnChange          = "";
+       var $mOnClick           = "";
+       var $mOnKeyPress        = "";
+       var $mOnFocus           = "";
+       var $mLabel         = "";
+       var $mStyle         = "";
+       var $mRenderable    = true;
+       var $mEnabled           = true;
+       var $cClassName         = "FormElement";
+       var $mI18n                      = null;
+
+       function FormElement() {
+       }
+
+       function getClass()     { return $this->cClassName; }
+       
+       function setName($name) { $this->mName = $name; }
+       function getName()      { return $this->mName; }
+       
+       function setFormName($name)     { $this->mFormName = $name;     }
+       function getFormName()  { return $this->mFormName; }
+       
+       function setValue($value)       { $this->mValue = $value;}
+       function getValue() { return $this->mValue; }
+       
+       function setValueSafe($value)   { $this->mValue = $value;}
+       function getValueSafe() { return $this->mValue; }
+
+       function setId($id)     { $this->mId = $id;     }
+       function getId() { return $this->mId; }
+       
+       function setSize($value)        { $this->mSize = $value; }
+       function getSize() { return $this->mSize; }
+
+       function setLabel($label)       { $this->mLabel = $label; }
+       function getLabel() { return $this->mLabel; }
+       
+       function setMaxLength($value)   { $this->mMaxLength = $value; }
+       function getMaxLength() { return $this->mMaxLength; }
+       
+       function setTabindex($value)    { $this->mTabindex = $value; }
+       function getTabindex() { return $this->mTabindex; }
+       
+       function setAccesskey($value)   { $this->mAccesskey = $value; }
+       function getAccesskey() { return $this->mAccesskey; }
+
+       function setStyle($value)       { $this->mStyle = $value; }
+       function getStyle() { return $this->mStyle; }
+       
+       function setRenderable($flag)   { $this->mRenderable = $flag;   }
+       function isRenderable() { return $this->mRenderable; }
+       
+       function setEnable($flag)       { $this->mEnabled = $flag;      }
+       function isEnable()     { return $this->mEnabled; }
+       
+       function setOnChange($str)      { $this->mOnChange = $str; }
+       function setOnClick($str)       { $this->mOnClick = $str; }
+       function setOnSelect($str)      { $this->mOnSelect = $str; }
+       function setOnBlue($str)        { $this->mOnBlur = $str; }
+       function setOnKeyPress($str){ $this->mOnKeyPress = $str; }
+       
+       function setLocalization($i18n) {
+               $this->mI18n = $i18n;
+       }
+       
+       function toStringControl()      {
+               return "";
+       }
+       
+       function toStringLabel() {
+           return "<label for=\"" . $this->mId . "\">" . $this->mLabel . "</label>";
+       }
+       
+       function toArray() {
+           return array(
+                    "label"=>$this->toStringLabel(),
+                    "control"=>$this->toStringControl()
+                  );
+       }
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Hidden.class.php b/WEB-INF/lib/form/Hidden.class.php
new file mode 100644 (file)
index 0000000..660870b
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+class Hidden extends FormElement {
+    var $mValue;
+    var $cClassName    = "Hidden";
+
+       function Hidden($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+
+       function toStringControl()      {
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+               $html = "\n\t<input";
+               $html .= " type=\"hidden\" name=\"$this->mName\" id=\"$this->mId\"";
+               
+               $html .= " value=\"".$this->getValue()."\"";
+               $html .= ">";
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Submit.class.php b/WEB-INF/lib/form/Submit.class.php
new file mode 100644 (file)
index 0000000..d621850
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+class Submit extends FormElement {
+       var $cClassName = "Submit";
+
+       function Submit($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+               $html = "\n\t<input";
+               $html .= " type=\"submit\" name=\"$this->mName\" id=\"$this->mId\"";
+               
+               if (!$this->isEnable()) {
+                       $html .= " disabled=\"true\"";
+               }
+               
+               $html .= " value=\"$this->mValue\"";
+               
+               if ($this->mOnClick) {
+                       $html .= " onclick=\"".$this->mOnClick."\"";
+               }
+               
+               $html .= ">";
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/Table.class.php b/WEB-INF/lib/form/Table.class.php
new file mode 100644 (file)
index 0000000..89f6543
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+import('form.TableColumn');
+
+class Table extends FormElement {
+
+  var $mColumns       = array(); // array of columns in table
+  var $mData          = null;    // array of rows with data for column cells
+  var $mHeaders       = array(); // column headers
+  var $mInteractive   = true;    // adds a clickable checkbox column to table
+  var $mIAScript      = null;    // sctipt to execute when a checkbox is clicked
+  var $mKeyField      = '';      // identifies a column used as key to access row data
+  var $mColumnFields  = array(); // field names (from mData) for data in each column
+  var $mBgColor       = '#ffffff';
+  var $mBgColorOver   = '#eeeeff';
+  var $mWidth         = '';
+  var $cClassName     = 'Table';
+  var $mTableOptions  = array();
+  var $mRowOptions    = array();
+  var $mHeaderOptions = array();
+  var $mProccessed    = false;
+       
+  function Table($name, $value='') {
+    $this->mName = $name;
+    $this->mValue = $value;
+  }
+  
+  function setKeyField($value) {
+       $this->mKeyField = $value;
+  }
+  
+  function setData($data) {
+       if (is_array($data) && isset($data[0]) && is_array($data[0]))
+      $this->mData = &$data;
+  }
+  
+  function addColumn($column) {
+    if ($column != null) $column->setTable($this);
+    $this->mColumns[] = &$column;
+  }
+  
+  function setInteractive($value) { $this->mInteractive = $value; }
+  function isInteractive() { return $this->mInteractive; }
+  
+  function setIAScript($value) { $this->mIAScript = $value; }
+  function getIAScript() { return $this->mIAScript; }
+               
+  function setWidth($value) { $this->mWidth = $value; }
+  function getWidth() { return $this->mWidth; }
+
+  function setTableOptions($value) { $this->mTableOptions = $value; }
+  function getTableOptions() { return $this->mTableOptions; }
+  
+  function setRowOptions($value) { $this->mRowOptions = $value; }
+  function getRowOptions() { return $this->mRowOptions; }
+
+  function setHeaderOptions($value) { $this->mHeaderOptions = $value; }
+  function getHeaderOptions() { return $this->mHeaderOptions; }
+  
+  function getValueAt($rowindex, $colindex) {
+    if (!$this->mProccessed) $this->_process();
+    return @$this->mData[$rowindex][$this->mColumnFields[$colindex]];
+  }
+       
+  function getValueAtName($rowindex,$fieldname) {
+    if (!$this->mProccessed) $this->_process();
+    return @$this->mData[$rowindex][$fieldname];
+  }
+
+  function _process() {
+       $this->mProccessed = true;
+       
+       if ($this->mInteractive) {
+      // Add a column of clickable checkboxes.
+      $column = new TableColumn("","<input type=\"checkbox\" name=\"".$this->getName()."_all\" onclick=\"setAll(this.checked)\">");
+      import('form.CheckboxCellRenderer');
+      $cb = new CheckboxCellRenderer();
+      if ($this->getIAScript()) $cb->setOnChangeAdd($this->getIAScript()."(this)");
+      $column->setRenderer($cb);
+      $column->setTable($this);
+      array_unshift($this->mColumns, $column);
+    }
+    
+    foreach ($this->mColumns as $column) {
+      $this->mColumnFields[] = $column->getField();
+      $this->mHeaders[] = $column->getHeader();
+    }
+  }
+  
+  function toStringControl() {
+    if (!$this->isRenderable()) return "";
+    if (!$this->mProccessed) $this->_process();
+    
+    $html = "";
+    if ($this->mInteractive) $html .= $this->_addJavaScript();
+
+    $html .= "<table";
+    if (count($this->mTableOptions) > 0) {
+      foreach ($this->mTableOptions as $k=>$v) {
+        $html .= " $k=\"$v\"";
+      }
+    } else {
+      $html .= " border=\"1\"";
+    }
+    if ($this->mWidth!="") $html .= " width=\"".$this->mWidth."\"";
+    $html .= ">\n";
+    
+    // Print headers.
+       if (($this->mInteractive && (count($this->mHeaders) > 1)) || (!$this->mInteractive && (count($this->mHeaders) > 0))) {
+      $html .= "<tr";
+      if (count($this->mRowOptions) > 0) {
+        foreach ($this->mRowOptions as $k=>$v) {
+          $html .= " $k=\"$v\"";
+        }
+      }
+      $html .= ">\n";
+      foreach ($this->mHeaders as $header) {
+        $html .= "<th";
+        if (count($this->mHeaderOptions) > 0) {
+          foreach ($this->mHeaderOptions as $k=>$v) {
+               $html .= " $k=\"$v\"";
+          }
+        }
+        $html .= ">$header</th>\n";
+      }
+      $html .= "</tr>\n";
+    }
+    
+    // Print rows.
+    for ($row = 0; $row < count($this->mData); $row++) {
+      $html .= "\n<tr bgcolor=\"".$this->mBgColor."\" onmouseover=\"setRowBackground(this, '".$this->mBgColorOver."')\" onmouseout=\"setRowBackground(this, null)\">\n";
+      for ($col = 0; $col < $this->getColumnCount(); $col++) {
+        if (0 == $col && strtolower(get_class($this->mColumns[$col]->getRenderer())) == 'checkboxcellrenderer') {
+          // Checkbox for the row. Determine if selected.
+          $selected = false;
+          if (is_array($this->mValue)) {
+            foreach ($this->mValue as $p) {
+              if ($p == $this->mData[$row][$this->mKeyField]) {
+               $selected = true;
+               break;
+              }
+            }
+          }
+          // Render control checkbox.
+          $html .= $this->mColumns[$col]->renderCell($this->mData[$row][$this->mKeyField], $row, $col, $selected);
+        } else {
+          // Render regular cell.
+          $html .= $this->mColumns[$col]->renderCell($this->getValueAt($row, $col), $row, $col);
+        }
+      }
+      $html .= "</tr>\n";
+    }
+
+    $html .= "</table>";
+    return $html;
+  }
+  
+  function getColumnCount() {
+    return count($this->mColumns);
+  }
+
+  function _addJavaScript() {
+    $html = "<script>\n";
+    // setAll - checks / unchecks all checkboxes in the table.
+    $html .= "function setAll(value) {\n";
+    $html .= "\tfor (var i = 0; i < ".$this->getFormName().".elements.length; i++) {\n";
+    $html .= "\t\tif ((".$this->getFormName().".elements[i].type=='checkbox') && (".$this->getFormName().".elements[i].name=='".$this->getName()."[]')) {\n";
+    $html .= "\t\t\t".$this->getFormName().".elements[i].checked=value;\n";
+    if ($this->getIAScript()) {
+      $html .= "\t\t\t".$this->getIAScript()."(".$this->getFormName().".elements[i]);\n";
+    }
+    $html .= "\t}}\n";
+    $html .= "}\n\n";
+    
+    $html .= "var rowBgColors;\n";
+    // setRowBackground - sets background for a row.
+    $html .= "function setRowBackground(theRow, thePointerColor) {\n";
+    $html .= "\tif (typeof(theRow.style) == 'undefined' || typeof(theRow.cells) == 'undefined') {\n";
+    $html .= "\t\treturn false;\n\t}\n\n";
+    $html .= "\tvar row_cells_cnt = theRow.cells.length;\n";
+    $html .= "\tif (thePointerColor != null) {\n";
+    $html .= "\t\trowBgColors = new Array(row_cells_cnt);\n";
+    $html .= "\t\tfor (var c = 0; c < row_cells_cnt; c++) {\n";
+    $html .= "\t\t\trowBgColors[c]=theRow.cells[c].bgColor;\n\t\t}\n";
+    $html .= "\t}\n";
+    $html .= "\tfor (var c = 0; c < row_cells_cnt; c++) {\n";
+    $html .= "\t\ttheRow.cells[c].bgColor = thePointerColor;\n\t}\n\n";
+    $html .= "\tif (thePointerColor == null) {\n";
+    $html .= "\t\tfor (var c = 0; c < row_cells_cnt; c++) {\n";
+    $html .= "\t\t\ttheRow.cells[c].bgColor=rowBgColors[c];\n\t\t}\n";
+    $html .= "\t\tdelete rowBgColors;\n";
+    $html .= "\t}\n";
+    $html .= "\treturn true;\n}\n";
+    $html .= "</script>\n";
+    
+    return $html;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/TableColumn.class.php b/WEB-INF/lib/form/TableColumn.class.php
new file mode 100644 (file)
index 0000000..3af7b2a
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.DefaultCellRenderer');
+
+class TableColumn {
+       var $mTitle                     = "";
+       var $mIndexField        = "";
+       var $mRenderer          = null;
+       var $mWidth                     = "";
+       var $mTable         = null;
+       var $mBgColor           = "#ffffff";
+       var $mFgColor           = "#000000";
+       
+       function TableColumn($indexField, $title="",$renderer=null) {
+               $this->mIndexField      = $indexField;
+               $this->mTitle       = $title;
+               if ($renderer!=null) {
+                 $this->mRenderer      = $renderer;
+               } else {
+                 $this->mRenderer      = new DefaultCellRenderer();
+               }
+       }
+       
+       function getHeader() { return $this->mTitle; }
+       
+       function getField() { return $this->mIndexField; }
+    
+    function setTable(&$table) { $this->mTable = &$table; }
+
+    function setRenderer(&$renderer) { $this->mRenderer = &$renderer; }
+    function &getRenderer() { return $this->mRenderer; }    
+    
+    function setFgColor($value) { $this->mFgColor = $value; }
+    function getFgColor() { return $this->mFgColor; }
+    
+    function setBgColor($value) { $this->mBgColor = $value; }
+    function getBgColor() { return $this->mBgColor; }
+    
+    function renderCell($value,$row,$column,$selected=false) {
+       if ($this->mRenderer!=null) {
+               return $this->mRenderer->render($this->mTable, $value, $row, $column, $selected);
+       } else {
+               return null;
+       }
+    }
+    
+    function setWidth($value) {
+       $this->mWidth = $value;
+       if ($this->mRenderer!=null) $this->mRenderer->setWidth($value);
+    }
+    
+    function getWidth() {
+        return $this->mWidth;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/TextArea.class.php b/WEB-INF/lib/form/TextArea.class.php
new file mode 100644 (file)
index 0000000..9cfd431
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+class TextArea extends FormElement {
+    var $mValue;
+    var $mPassword     = false;
+    var $mColumns      = "";
+    var $mRows         = "";
+    var $cClassName            = "TextArea";
+
+       function TextArea($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+       
+       function setColumns($value)     { $this->mColumns = $value;     }
+       function getColumns()   { return $this->mColumns; }
+
+       function setRows($value)        { $this->mRows = $value;        }
+       function getRows()      { return $this->mRows; }
+       
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+           $js_maxlen = "";
+           
+               $html = "\n\t<textarea";
+               $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+               
+               if ($this->mColumns!="")
+                 $html .= " cols=\"$this->mColumns\"";
+                 
+               if ($this->mRows!="")
+                  $html .= " rows=\"$this->mRows\"";
+                  
+               if ($this->mMaxLength!="") {
+                       if ($this->mOnKeyPress) $this->mOnKeyPress .= ";";
+                       $this->mOnKeyPress .= "return validateMaxLenght_".$this->mName."(this, event);";
+                       $js_maxlen = $this->getExtraScript();
+                       $html .= " maxlength=\"$this->mMaxLength\"";
+               }
+
+               if ($this->mStyle!="")
+                  $html .= " style=\"$this->mStyle\"";
+
+               if ($this->mOnKeyPress) {
+                       $html .= " onkeypress=\"$this->mOnKeyPress\"";
+               }
+                       
+               $html .= ">".htmlspecialchars($this->getValue())."</textarea>";
+               if ($js_maxlen) $html = $js_maxlen."\n".$html;
+               
+               return $html;
+       }
+       
+       function getExtraScript() {
+               $s = "<script>\n";
+               $s .= "var isNS4 = (navigator.appName==\"Netscape\")?1:0;\n";
+               $s .= "function validateMaxLenght_".$this->mName."(element, event) {\n";
+               $s .= "\tmaxlength=".$this->mMaxLength.";\n";
+               $s .= "\tvar iKey = (!isNS4?event.keyCode:event.which);\n";
+               //$s .= "alert(iKey);";
+               $s .= "\tvar re = new RegExp(\"".'\r\n'."\",\"g\");\n";
+               $s .= "\tvar x = element.value.replace(re,\"\").length;\n";
+               $s .= "\tif ((x>=maxlength) && ((iKey > 31 && iKey < 1200) || (iKey > 95 && iKey < 106)) && (iKey != 13)) {\n";
+               $s .= "\t\treturn false;\n";
+               $s .= "\t} else {\n";
+               $s .= "\t\treturn true;\n";
+               $s .= "\t}\n}\n";
+               $s .= "</script>\n";
+               return $s;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/TextField.class.php b/WEB-INF/lib/form/TextField.class.php
new file mode 100644 (file)
index 0000000..3d5ce7a
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+class TextField extends FormElement {
+    var $mValue;
+    var $mPassword     = false;
+    var $cClassName            = "TextField";
+
+       function TextField($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+       
+       function setAsPassword($name)   { $this->mPassword = $name;     }
+       function getAsPassword()        { return $this->mPassword; }
+
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+               if (!$this->isEnable()) {
+                       $html = "<input name=\"$this->mName\" value=\"".htmlspecialchars($this->getValue())."\" readonly>\n";  
+               } else {
+                       
+                   if ($this->mId=="") $this->mId = $this->mName;
+                   
+                       $html = "\n\t<input";
+                       $html .= ( $this->mPassword ? " type=\"password\"" : " type=\"text\"");
+                       $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+                       
+                       if ($this->mSize!="")
+                         $html .= " size=\"$this->mSize\"";
+                         
+                       if ($this->mStyle!="")
+                          $html .= " style=\"$this->mStyle\"";
+                         
+                       if ($this->mMaxLength!="")
+                          $html .= " maxlength=\"$this->mMaxLength\"";
+                          
+                       if ($this->mOnChange!="")
+                          $html .= " onchange=\"$this->mOnChange\"";
+
+                       $html .= " value=\"".htmlspecialchars($this->getValue())."\"";
+                       $html .= ">";
+               }
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/form/UploadFile.class.php b/WEB-INF/lib/form/UploadFile.class.php
new file mode 100644 (file)
index 0000000..be57fa4
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('form.FormElement');
+       
+class UploadFile extends FormElement {
+    var $mValue;
+    var $cClassName            = "UploadFile";
+    var $mMaxSize              = 100000;       // 100kb
+
+       function UploadFile($name,$value="")
+       {
+               $this->mName                    = $name;
+               $this->mValue                   = $value;
+       }
+       
+       function setMaxSize($value)     { $this->mMaxSize = $value;     }
+       function getMaxSize()   { return $this->mMaxSize; }
+       
+       function toStringControl()      {
+               if (!$this->isRenderable()) return "";
+           
+           if ($this->mId=="") $this->mId = $this->mName;
+           
+               $html = "\n\t<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"".$this->mMaxSize."\"/>";
+               $html .= "\n\t<input";
+               $html .= " name=\"$this->mName\" id=\"$this->mId\"";
+               
+               $html .= " type=\"file\"";
+               $html .= ">";
+               
+               // only IE
+               /*$html = "<input type=\"file\" name=\"".$this->mName."\" id=\"".$this->mId."\" style=\"display: none;\">\n";
+               $html .= "<input type=\"text\" name=\"".$this->mName."file\">\n";
+               $html .= "<input type=\"button\" 
+                       style=\"text-align:center;\" onClick=\"".$this->mName.".click();".$this->mName."file.value=".$this->mName.".value;".$this->mName.".disabled=true;\"";
+               $html .= " value=\"".$this->getValue()."\">\n";
+               $html .= "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"".$this->mMaxSize."\"/>";*/
+               
+               
+               return $html;
+       }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/html/HttpRequest.class.php b/WEB-INF/lib/html/HttpRequest.class.php
new file mode 100644 (file)
index 0000000..857194a
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class ttHttpRequest {
+  // The getMethod function returns the type of request (GET, POST, etc.).
+  function getMethod() {
+    return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : false;
+  }
+       
+  // The getParameter is the primary function of this class. It returns request parameter,
+  // identified by $name.
+  function getParameter($name = "", $default = null) { 
+    switch ($this->getMethod())
+    {
+      case 'GET':
+               if (isset($_GET[$name]) && ($_GET[$name] != ""))
+          return $_GET[$name];
+
+      case 'POST':
+        if (isset($_POST[$name]) && ($_POST[$name] != ""))
+          return  $_POST[$name];
+    }
+    return $default;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/libchart.php b/WEB-INF/lib/libchart/classes/libchart.php
new file mode 100644 (file)
index 0000000..f84210f
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+
+       require_once 'model/Point.php';
+       require_once 'model/DataSet.php';
+       require_once 'model/XYDataSet.php';
+       require_once 'model/XYSeriesDataSet.php';
+       
+       require_once 'view/primitive/Padding.php';
+       require_once 'view/primitive/Rectangle.php';
+       require_once 'view/primitive/Primitive.php';
+       require_once 'view/text/Text.php';
+       require_once 'view/color/Color.php';
+       require_once 'view/color/ColorSet.php';
+       require_once 'view/color/Palette.php';
+       require_once 'view/axis/Bound.php';
+       require_once 'view/axis/Axis.php';
+       require_once 'view/plot/Plot.php';
+       require_once 'view/caption/Caption.php';
+       require_once 'view/chart/Chart.php';
+       require_once 'view/chart/BarChart.php';
+       require_once 'view/chart/VerticalBarChart.php';
+       require_once 'view/chart/HorizontalBarChart.php';
+       require_once 'view/chart/LineChart.php';
+       require_once 'view/chart/PieChart.php';
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/model/DataSet.php b/WEB-INF/lib/libchart/classes/model/DataSet.php
new file mode 100644 (file)
index 0000000..295a9ec
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Superclass of all data sets.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 10 may 2007
+        */
+       abstract class DataSet {
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/model/Point.php b/WEB-INF/lib/libchart/classes/model/Point.php
new file mode 100644 (file)
index 0000000..79d6cac
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Point of coordinates (X,Y).
+        * The value of X isn't really of interest, but X is used as a label to display on the horizontal axis.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class Point {
+               private $x;
+               private $y;
+       
+               /**
+                * Creates a new sampling point of coordinates (x, y)
+                *
+                * @param integer x coordinate (label)
+                * @param integer y coordinate (value)
+                */
+               public function Point($x, $y) {
+                       $this->x = $x;
+                       $this->y = $y;
+               }
+
+               /**
+                * Gets the x coordinate (label).
+                *
+                * @return integer x coordinate (label)
+                */
+               public function getX() {
+                       return $this->x;
+               }
+
+               /**
+                * Gets the y coordinate (value).
+                *
+                * @return integer y coordinate (value)
+                */
+               public function getY() {
+                       return $this->y;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/model/XYDataSet.php b/WEB-INF/lib/libchart/classes/model/XYDataSet.php
new file mode 100644 (file)
index 0000000..2713c29
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Set of data in the form of (x, y) items.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 10 may 2007
+        */
+       class XYDataSet extends DataSet {
+               private $pointList;
+       
+               /**
+                * Constructor of XYDataSet.
+                *
+                */
+               public function XYDataSet() {
+                       $this->pointList = array();
+               }
+       
+               /**
+                * Add a new point to the dataset.
+                *
+                * @param Point Point to add to the dataset
+                */
+               
+               public function addPoint($point) {
+                       array_push($this->pointList, $point);
+               }
+
+               /**
+                * Getter of pointList.
+                *
+                * @return List of points.
+                */
+               public function getPointList() {
+                       return $this->pointList;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php b/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php
new file mode 100644 (file)
index 0000000..45f961c
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * This dataset comprises several series of points and is used to plot multiple lines charts.
+        * Each serie is a XYDataSet.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 20 july 2007
+        */
+       class XYSeriesDataSet extends DataSet {
+               /**
+                * List of titles
+                */
+               private $titleList;
+       
+               /**
+                * List of XYDataSet.
+                */
+               private $serieList;
+               
+               /**
+                * Constructor of XYSeriesDataSet.
+                *
+                */
+               public function XYSeriesDataSet() {
+                       $this->titleList = array();
+                       $this->serieList = array();
+               }
+       
+               /**
+                * Add a new serie to the dataset.
+                *
+                * @param string Title (label) of the serie.
+                * @param XYDataSet Serie of points to add
+                */
+               public function addSerie($title, $serie) {
+                       array_push($this->titleList, $title);
+                       array_push($this->serieList, $serie);
+               }
+               
+               /**
+                * Getter of titleList.
+                *
+                * @return List of titles.
+                */
+               public function getTitleList() {
+                       return $this->titleList;
+               }
+
+               /**
+                * Getter of serieList.
+                *
+                * @return List of series.
+                */
+               public function getSerieList() {
+                       return $this->serieList;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/axis/Axis.php b/WEB-INF/lib/libchart/classes/view/axis/Axis.php
new file mode 100644 (file)
index 0000000..8850196
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Automatic axis boundaries and ticks calibration
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class Axis {
+               private $min;
+               private $max;
+               private $guide;
+               private $delta;
+               private $magnitude;
+               private $displayMin;
+               private $displayMax;
+               private $tics;
+       
+               /**
+                * Creates a new axis formatter.
+                *
+                * @param integer minimum value on the axis
+                * @param integer maximum value on the axis
+                */
+               public function Axis($min, $max) {
+                       $this->min = $min;
+                       $this->max = $max;
+
+                       $this->guide = 10;
+               }
+
+               /**
+                * Computes value between two ticks.
+                */
+               public function quantizeTics() {
+                       // Approximate number of decades, in [1..10[
+                       $norm = $this->delta / $this->magnitude;
+
+                       // Approximate number of tics per decade
+                       $posns = $this->guide / $norm;
+
+                       if ($posns > 20) {
+                               $tics = 0.05;           // e.g. 0, .05, .10, ...
+                       } else if ($posns > 10) {
+                               $tics = 0.2;            // e.g.  0, .1, .2, ...
+                       } else if ($posns > 5) {
+                               $tics = 0.4;            // e.g.  0, 0.2, 0.4, ...
+                       } else if ($posns > 3) {
+                               $tics = 0.5;            // e.g. 0, 0.5, 1, ...
+                       } else if ($posns > 2) {
+                               $tics = 1;              // e.g. 0, 1, 2, ...
+                       } else if ($posns > 0.25) {
+                               $tics = 2;              // e.g. 0, 2, 4, 6 
+                       } else {
+                               $tics = ceil($norm);
+                       }
+                       
+                       $this->tics = $tics * $this->magnitude;
+               }
+
+               /**
+                * Computes automatic boundaries on the axis
+                */
+               public function computeBoundaries() {
+                       // Range
+                       $this->delta = abs($this->max - $this->min);
+
+                       // Check for null distribution
+                       if ($this->delta == 0)
+                               $this->delta = 1;
+                       
+                       // Order of magnitude of range
+                       $this->magnitude = pow(10, floor(log10($this->delta)));
+                       
+                       $this->quantizeTics();
+
+                       $this->displayMin = floor($this->min / $this->tics) * $this->tics;
+                       $this->displayMax = ceil($this->max / $this->tics) * $this->tics;
+                       $this->displayDelta = $this->displayMax - $this->displayMin;
+               
+                       // Check for null distribution
+                       if ($this->displayDelta == 0) {
+                               $this->displayDelta = 1;
+                       }
+               }
+
+               /**
+                * Get the lower boundary on the axis3
+                *
+                * @return integer lower boundary on the axis
+                */
+               public function getLowerBoundary() {
+                       return $this->displayMin;
+               }
+
+               /**
+                * Get the upper boundary on the axis3
+                *
+                * @return integer upper boundary on the axis
+                */
+               public function getUpperBoundary() {
+                       return $this->displayMax;
+               }
+
+               /**
+                * Get the value between two ticks3
+                *
+                * @return integer value between two ticks
+                */
+               public function getTics() {
+                       return $this->tics;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/axis/Bound.php b/WEB-INF/lib/libchart/classes/view/axis/Bound.php
new file mode 100644 (file)
index 0000000..5497b70
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Object representing the bounds of a dataset (its minimal and maximal values) on its vertical axis.
+        * The bounds are automatically calculated from a XYDataSet or XYSeriesDataSet.
+        * Default (calculated) bounds can be overriden using the setLowerBound() and setUpperBound() methods.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 25 july 2007
+        */
+        class Bound {
+               /**
+                * Manually set lower bound, overrides the value calculated by computeBound().
+                */
+               private $lowerBound = null;
+
+               /**
+                * Manually set upper bound, overrides the value calculated by computeBound().
+                */
+               private $upperBound = null;
+               
+               /**
+                * Computed min bound.
+                */
+               private $yMinValue = null;
+               
+               /**
+                * Computed max bound.
+                */
+               private $yMaxValue = null;
+               
+               /**
+                * Compute the boundaries on the axis.
+                *
+                * @param dataSet The data set
+                */
+               public function computeBound($dataSet) {
+                       // Check if the data set is empty
+                       $dataSetEmpty = true;
+                       $serieList = null;
+                       if ($dataSet instanceof XYDataSet) {
+                               $pointList = $dataSet->getPointList();
+                               $dataSetEmpty = count($pointList) == 0;
+                               
+                               if (!$dataSetEmpty) {
+                                       // Process it as a serie
+                                       $serieList = array();
+                                       array_push($serieList, $dataSet);
+                               }
+                       } else if ($dataSet instanceof XYSeriesDataSet) {
+                               $serieList = $dataSet->getSerieList();
+                               if (count($serieList) > 0) {
+                                       $serie = current($serieList);
+                                       $dataSetEmpty = count($serie) == 0;
+                               }
+                       } else {
+                               die("Error: unknown dataset type");
+                       }
+                       
+                       // If the dataset is empty, default some bounds
+                       $yMin = 0;
+                       $yMax = 1;
+                       if (!$dataSetEmpty) {
+                               // Compute lower and upper bound on the value axis
+                               unset($yMin);
+                               unset($yMax);
+
+                               foreach ($serieList as $serie) {
+                                       foreach ($serie->getPointList() as $point) {
+                                               $y = $point->getY();
+                                               
+                                               if (!isset($yMin)) {
+                                                       $yMin = $y;
+                                                       $yMax = $y;
+                                               } else {
+                                                       if ($y < $yMin) {
+                                                               $yMin = $y;
+                                                       }
+                       
+                                                       if ($y > $yMax) {
+                                                               $yMax = $y;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       // If user specified bounds and they are actually greater than computer bounds, override computed bounds
+                       if (isset($this->lowerBound) && $this->lowerBound < $yMin) {
+                               $this->yMinValue = $this->lowerBound;
+                       } else {
+                               $this->yMinValue = $yMin;
+                       }
+
+                       if (isset($this->upperBound) && $this->upperBound > $yMax) {
+                               $this->yMaxValue = $this->upperBound;
+                       } else {
+                               $this->yMaxValue = $yMax;
+                       }
+               }
+
+               /**
+                * Getter of yMinValue.
+                *
+                * @return min bound
+                */
+               public function getYMinValue() {
+                       return $this->yMinValue;
+               }
+
+               /**
+                * Getter of yMaxValue.
+                *
+                * @return max bound
+                */
+               public function getYMaxValue() {
+                       return $this->yMaxValue;
+               }
+
+               /**
+                * Set manually the lower boundary value (overrides the automatic formatting).
+                * Typical usage is to set the bars starting from zero.
+                *
+                * @param double lower boundary value
+                */
+               public function setLowerBound($lowerBound) {
+                       $this->lowerBound = $lowerBound;
+               }
+
+               /**
+                * Set manually the upper boundary value (overrides the automatic formatting).
+                *
+                * @param double upper boundary value
+                */
+               public function setUpperBound($upperBound) {
+                       $this->upperBound = $upperBound;
+               }
+        }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/caption/Caption.php b/WEB-INF/lib/libchart/classes/view/caption/Caption.php
new file mode 100644 (file)
index 0000000..9a48b49
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Caption.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 30 july 2007
+        */
+       class Caption {
+               protected $labelBoxWidth;
+               protected $labelBoxHeight;
+       
+               // Plot
+               protected $plot;
+               
+               // Label list
+               protected $labelList;
+               
+               // Color set
+               protected $colorSet;
+               
+               /**
+                * Constructor of Caption
+                */
+               public function Caption() {
+                       $this->labelBoxWidth = 15;
+                       $this->labelBoxHeight = 15;
+               }
+               
+               /**
+                * Render the caption.
+                */
+               public function render() {
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       $primitive = $this->plot->getPrimitive();
+                       
+                       // Get the caption area
+                       $captionArea = $this->plot->getCaptionArea();
+
+                       // Get the pie color set
+                       $colorSet = $this->colorSet;
+                       $colorSet->reset();
+                       
+                       $i = 0;
+                       foreach ($this->labelList as $label) {
+                               // Get the next color
+                               $color = $colorSet->currentColor();
+                               $colorSet->next();
+
+                               $boxX1 = $captionArea->x1;
+                               $boxX2 = $boxX1 + $this->labelBoxWidth;
+                               $boxY1 = $captionArea->y1 + 5 + $i * ($this->labelBoxHeight + 5);
+                               $boxY2 = $boxY1 + $this->labelBoxHeight;
+
+                               $primitive->outlinedBox($boxX1, $boxY1, $boxX2, $boxY2, $palette->axisColor[0], $palette->axisColor[1]);
+                               imagefilledrectangle($img, $boxX1 + 2, $boxY1 + 2, $boxX2 - 2, $boxY2 - 2, $color->getColor($img));
+
+                               $text->printText($img, $boxX2 + 5, $boxY1 + $this->labelBoxHeight / 2, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->VERTICAL_CENTER_ALIGN);
+
+                               $i++;
+                       }
+               }
+
+               /**
+                * Sets the plot.
+                *
+                * @param Plot The plot
+                */
+               public function setPlot($plot) {
+                       $this->plot = $plot;
+               }
+               
+               /**
+                * Sets the label list.
+                *
+                * @param Array label list
+                */
+               public function setLabelList($labelList) {
+                       $this->labelList = $labelList;
+               }
+               
+               
+               /**
+                * Sets the color set.
+                *
+                * @param Array Color set
+                */
+               public function setColorSet($colorSet) {
+                       $this->colorSet = $colorSet;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/BarChart.php b/WEB-INF/lib/libchart/classes/view/chart/BarChart.php
new file mode 100644 (file)
index 0000000..c5d9b82
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Base abstract class for rendering both horizontal and vertical bar charts.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       abstract class BarChart extends Chart {
+               protected $bound;
+               protected $axis;
+               protected $hasSeveralSerie;
+               
+               /**
+                * Creates a new bar chart.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               protected function BarChart($width, $height) {
+                       parent::Chart($width, $height);
+
+                       // Initialize the bounds
+                       $this->bound = new Bound();
+                       $this->bound->setLowerBound(0);
+               }
+
+               /**
+                * Compute the axis.
+                */
+               protected function computeAxis() {
+                       $this->axis = new Axis($this->bound->getYMinValue(), $this->bound->getYMaxValue());
+                       $this->axis->computeBoundaries();
+               }
+
+               /**
+                * Create the image.
+                */
+               protected function createImage() {
+                       parent::createImage();
+
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       $primitive = $this->plot->getPrimitive();
+                       
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       // Aqua-like background
+                       for ($i = $graphArea->y1; $i < $graphArea->y2; $i++) {
+                               $color = $palette->aquaColor[($i + 3) % 4];
+                               $primitive->line($graphArea->x1, $i, $graphArea->x2, $i, $color);
+                       }
+
+                       // Axis
+                       imagerectangle($img, $graphArea->x1 - 1, $graphArea->y1, $graphArea->x1, $graphArea->y2, $palette->axisColor[0]->getColor($img));
+                       imagerectangle($img, $graphArea->x1 - 1, $graphArea->y2, $graphArea->x2, $graphArea->y2 + 1, $palette->axisColor[0]->getColor($img));
+               }
+
+               /**
+                * Returns true if the data set has some data.
+                * @param minNumberOfPoint Minimum number of points (1 for bars, 2 for lines).
+                *
+                * @return true if data set empty
+                */
+               protected function isEmptyDataSet($minNumberOfPoint) {
+                       if ($this->dataSet instanceof XYDataSet) {
+                               $pointList = $this->dataSet->getPointList();
+                               $pointCount = count($pointList);
+                               return $pointCount < $minNumberOfPoint;
+                       } else if ($this->dataSet instanceof XYSeriesDataSet) {
+                               $serieList = $this->dataSet->getSerieList();
+                               reset($serieList);
+                               if (count($serieList) > 0) {
+                                       $serie = current($serieList);
+                                       $pointList = $serie->getPointList();
+                                       $pointCount = count($pointList);
+                                       return $pointCount < $minNumberOfPoint;
+                               }
+                       } else {
+                               die("Error: unknown dataset type");
+                       }
+               }
+
+               /**
+                * Checks the data model before rendering the graph.
+                */
+               protected function checkDataModel() {
+                       // Check if a dataset was defined
+                       if (!$this->dataSet) {
+                               die("Error: No dataset defined.");
+                       }
+                       
+                       // Bar charts accept both XYDataSet and XYSeriesDataSet
+                       if ($this->dataSet instanceof XYDataSet) {
+                               // The dataset contains only one serie
+                               $this->hasSeveralSerie = false;
+                       } else if ($this->dataSet instanceof XYSeriesDataSet) {
+                               // Check if each series has the same number of points
+                               unset($lastPointCount);
+                               $serieList = $this->dataSet->getSerieList();
+                               for ($i = 0; $i < count($serieList); $i++) {
+                                       $serie = $serieList[$i];
+                                       $pointCount = count($serie->getPointList());
+                                       if (isset($lastPointCount) && $pointCount != $lastPointCount) {
+                                               die("Error: serie <" . $i . "> doesn't have the same number of points as last serie (last one: <" . $lastPointCount. ">, this one: <" . $pointCount. ">).");
+                                       }
+                                       $lastPointCount = $pointCount;
+                               }
+                               
+                               // The dataset contains several series
+                               $this->hasSeveralSerie = true;
+                       } else {
+                               die("Error: Bar chart accept only XYDataSet and XYSeriesDataSet");
+                       }
+               }
+
+               /**
+                * Return the data as a series list (for consistency).
+                *
+                * @return List of series
+                */
+               protected function getDataAsSerieList() {
+                       // Get the data as a series list
+                       $serieList = null;
+                       if ($this->dataSet instanceof XYSeriesDataSet) {
+                               $serieList = $this->dataSet->getSerieList();
+                       } else if ($this->dataSet instanceof XYDataSet) {
+                               $serieList = array();
+                               array_push($serieList, $this->dataSet);
+                       }
+                       
+                       return $serieList;
+               }
+               
+               /**
+                * Return the first serie of the list, or the dataSet itself if there is no serie.
+                *
+                * @return XYDataSet
+                */
+               protected function getFirstSerieOfList() {
+                       $pointList = null;
+                       if ($this->dataSet instanceof XYSeriesDataSet) {
+                               // For a series dataset, print the legend from the first serie
+                               $serieList = $this->dataSet->getSerieList();
+                               reset($serieList);
+                               $serie = current($serieList);
+                               $pointList = $serie->getPointList();
+                       } else if ($this->dataSet instanceof XYDataSet) {
+                               $pointList = $this->dataSet->getPointList();
+                       }
+                       
+                       return $pointList;
+               }
+               
+               /**
+                * Retourns the bound.
+                *
+                * @return bound Bound
+                */
+               public function getBound() {
+                       return $this->bound;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/Chart.php b/WEB-INF/lib/libchart/classes/view/chart/Chart.php
new file mode 100644 (file)
index 0000000..599b98f
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /*! \mainpage Libchart
+        *
+        * This is the reference API, automatically compiled by <a href="http://www.stack.nl/~dimitri/doxygen/">Doxygen</a>.
+        * You can find here information that is not covered by the <a href="../samplecode/">tutorial</a>.
+        *
+        */
+
+       /**
+        * Base chart class.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       abstract class Chart {
+               /**
+                * The data set.
+                */
+               protected $dataSet;
+       
+               /**
+                * Plot (holds graphical attributes).
+                */
+               protected $plot;
+
+               /**
+                * Abstract constructor of Chart.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               protected function Chart($width, $height) {
+                       // Creates the plot
+                       $this->plot = new Plot($width, $height);
+                       $this->plot->setTitle("Untitled chart");
+                       $this->plot->setLogoFileName(dirname(__FILE__) . "/../../../images/PoweredBy.png");
+               }
+
+               /**
+                * Checks the data model before rendering the graph.
+                */
+               protected function checkDataModel() {
+                       // Check if a dataset was defined
+                       if (!$this->dataSet) {
+                               die("Error: No dataset defined.");
+                       }
+                       
+                       // Maybe no points are defined, but that's ok. This will yield and empty graph with default boundaries.
+               }
+               
+               /**
+                * Create the image.
+                */
+               protected function createImage() {
+                       $this->plot->createImage();
+               }
+
+               /**
+                * Sets the data set.
+                *
+                * @param dataSet The data set
+                */
+               public function setDataSet($dataSet) {
+                       $this->dataSet = $dataSet;
+               }
+               
+               /**
+                * Return the plot.
+                *
+                * @return plot
+                */
+               public function getPlot() {
+                       return $this->plot;
+               }
+               
+               /**
+                * Sets the title.
+                *
+                * @param string New title
+                */
+               public function setTitle($title) {
+                       $this->plot->setTitle($title);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php b/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php
new file mode 100644 (file)
index 0000000..5faad49
--- /dev/null
@@ -0,0 +1,217 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Horizontal bar chart
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class HorizontalBarChart extends BarChart {
+               /**
+                * Ratio of empty space beside the bars.
+                */
+               private $emptyToFullRatio;
+       
+               /**
+                * Creates a new horizontal bar chart.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               public function HorizontalBarChart($width = 600, $height = 250) {
+                       parent::BarChart($width, $height);
+
+                       $this->emptyToFullRatio = 1 / 5;
+                       $this->plot->setGraphPadding(new Padding(5, 30, 30, 50));
+               }
+
+               /**
+                * Computes the layout.
+                */
+               protected function computeLayout() {
+                       if ($this->hasSeveralSerie) {
+                               $this->plot->setHasCaption(true);
+                       }
+                       $this->plot->computeLayout();
+               }
+               
+               /**
+                * Print the axis.
+                */
+               protected function printAxis() {
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       $stepValue = $this->axis->getTics();
+
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       // Horizontal axis
+                       for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+                               $x = $graphArea->x1 + ($value - $minValue) * ($graphArea->x2 - $graphArea->x1) / ($this->axis->displayDelta);
+
+                               imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+                               $text->printText($img, $x, $graphArea->y2 + 5, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN);
+                       }
+
+                       // Get first serie of a list
+                       $pointList = $this->getFirstSerieOfList();
+
+                       // Vertical Axis
+                       $pointCount = count($pointList);
+                       reset($pointList);
+                       $rowHeight = ($graphArea->y2 - $graphArea->y1) / $pointCount;
+                       reset($pointList);
+                       for ($i = 0; $i <= $pointCount; $i++) {
+                               $y = $graphArea->y2 - $i * $rowHeight;
+
+                               imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+                               if ($i < $pointCount) {
+                                       $point = current($pointList);
+                                       next($pointList);
+       
+                                       $label = $point->getX();
+
+                                       $text->printText($img, $graphArea->x1 - 5, $y - $rowHeight / 2, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+                               }
+                       }
+               }
+
+               /**
+                * Print the bars.
+                */
+               protected function printBar() {
+                       // Get the data as a list of series for consistency
+                       $serieList = $this->getDataAsSerieList();
+                       
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       $stepValue = $this->axis->getTics();
+                       
+                       $barColorSet = $palette->barColorSet;
+                       $barColorSet->reset();
+
+                       $serieCount = count($serieList);
+                       for ($j = 0; $j < $serieCount; $j++) {
+                               $serie = $serieList[$j];
+                               $pointList = $serie->getPointList();
+                               $pointCount = count($pointList);
+                               reset($pointList);
+                               
+                               // Get the next color
+                               $color = $barColorSet->currentColor();
+                               $shadowColor = $barColorSet->currentShadowColor();
+                               $barColorSet->next();
+
+                               $rowHeight = ($graphArea->y2 - $graphArea->y1) / $pointCount;
+                               for ($i = 0; $i < $pointCount; $i++) {
+                                       $y = $graphArea->y2 - $i * $rowHeight;
+
+                                       $point = current($pointList);
+                                       next($pointList);
+
+                                       $value = $point->getY();
+                                       
+                                       $xmax = $graphArea->x1 + ($value - $minValue) * ($graphArea->x2 - $graphArea->x1) / ($this->axis->displayDelta);
+
+                                       // Bar dimensions
+                                       $yWithMargin = $y - $rowHeight * $this->emptyToFullRatio;
+                                       $rowWidthWithMargin = $rowHeight * (1 - $this->emptyToFullRatio * 2);
+                                       $barWidth = $rowWidthWithMargin / $serieCount;
+                                       $barOffset = $barWidth * $j;
+                                       $y1 = $yWithMargin - $barWidth - $barOffset;
+                                       $y2 = $yWithMargin - $barOffset - 1;
+
+                                       // Text
+                                       $text->printText($img, $xmax + 5, $y2 - $barWidth / 2, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->VERTICAL_CENTER_ALIGN);
+
+                                       imagefilledrectangle($img, $graphArea->x1 + 1, $y1, $xmax, $y2, $shadowColor->getColor($img));
+                                       
+                                       // Prevents drawing a small box when x = 0
+                                       if ($graphArea->x1 != $xmax) {
+                                               imagefilledrectangle($img, $graphArea->x1 + 2, $y1 + 1, $xmax - 4, $y2, $color->getColor($img));
+                                       }
+                               }
+                       }
+               }
+               
+               /**
+                * Renders the caption.
+                */
+               protected function printCaption() {
+                       // Get the list of labels
+                       $labelList = $this->dataSet->getTitleList();
+                       
+                       // Create the caption
+                       $caption = new Caption();
+                       $caption->setPlot($this->plot);
+                       $caption->setLabelList($labelList);
+                       
+                       $palette = $this->plot->getPalette();
+                       $barColorSet = $palette->barColorSet;
+                       $caption->setColorSet($barColorSet);
+                       
+                       // Render the caption
+                       $caption->render();
+               }
+
+               /**
+                * Render the chart image.
+                *
+                * @param string name of the file to render the image to (optional)
+                */
+               public function render($fileName = null) {
+                       // Check the data model
+                       $this->checkDataModel();
+                       
+                       $this->bound->computeBound($this->dataSet);
+                       $this->computeAxis();
+                       $this->computeLayout();
+                       $this->createImage();
+                       $this->plot->printLogo();
+                       $this->plot->printTitle();
+                       if (!$this->isEmptyDataSet(1)) {
+                               $this->printAxis();
+                               $this->printBar();
+                               if ($this->hasSeveralSerie) {
+                                       $this->printCaption();
+                               }
+                       }
+
+                       $this->plot->render($fileName);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/LineChart.php b/WEB-INF/lib/libchart/classes/view/chart/LineChart.php
new file mode 100644 (file)
index 0000000..811a060
--- /dev/null
@@ -0,0 +1,200 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Line chart.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class LineChart extends BarChart {
+               /**
+                * Creates a new line chart.
+                * Line charts allow for XYDataSet and XYSeriesDataSet in order to plot several lines.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               public function LineChart($width = 600, $height = 250) {
+                       parent::BarChart($width, $height);
+
+                       $this->plot->setGraphPadding(new Padding(5, 30, 50, 50));
+               }
+
+               /**
+                * Computes the layout.
+                */
+               protected function computeLayout() {
+                       if ($this->hasSeveralSerie) {
+                               $this->plot->setHasCaption(true);
+                       }
+                       $this->plot->computeLayout();
+               }
+               
+               /**
+                * Print the axis.
+                */
+               protected function printAxis() {
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       $stepValue = $this->axis->getTics();
+
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+                       
+                       // Vertical axis
+                       for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+                               $y = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+                               imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+                               $text->printText($img, $graphArea->x1 - 5, $y, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+                       }
+
+                       // Get first serie of a list
+                       $pointList = $this->getFirstSerieOfList();
+                       
+                       // Horizontal Axis
+                       $pointCount = count($pointList);
+                       reset($pointList);
+                       $columnWidth = ($graphArea->x2 - $graphArea->x1) / ($pointCount - 1);
+
+                       for ($i = 0; $i < $pointCount; $i++) {
+                               $x = $graphArea->x1 + $i * $columnWidth;
+
+                               imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+                               $point = current($pointList);
+                               next($pointList);
+
+                               $label = $point->getX();
+
+                               $text->printDiagonal($img, $x - 5, $graphArea->y2 + 10, $this->plot->getTextColor(), $label);
+                       }
+               }
+
+               /**
+                * Print the lines.
+                */
+               protected function printLine() {
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       
+                       // Get the data as a list of series for consistency
+                       $serieList = $this->getDataAsSerieList();
+                       
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       $primitive = $this->plot->getPrimitive();
+                       
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+                       
+                       $lineColorSet = $palette->lineColorSet;
+                       $lineColorSet->reset();
+                       for ($j = 0; $j < count($serieList); $j++) {
+                               $serie = $serieList[$j];
+                               $pointList = $serie->getPointList();
+                               $pointCount = count($pointList);
+                               reset($pointList);
+
+                               $columnWidth = ($graphArea->x2 - $graphArea->x1) / ($pointCount - 1);
+
+                               $lineColor = $lineColorSet->currentColor();
+                               $lineColorShadow = $lineColorSet->currentShadowColor();
+                               $lineColorSet->next();
+                               $x1 = null;
+                               $y1 = null;
+                               for ($i = 0; $i < $pointCount; $i++) {
+                                       $x2 = $graphArea->x1 + $i * $columnWidth;
+
+                                       $point = current($pointList);
+                                       next($pointList);
+
+                                       $value = $point->getY();
+                                       
+                                       $y2 = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+                                       // Draw line 
+                                       if ($x1) {
+                                               $primitive->line($x1, $y1, $x2, $y2, $lineColor, 4);
+                                               $primitive->line($x1, $y1 - 1, $x2, $y2 - 1, $lineColorShadow, 2);
+                                       }
+                                       
+                                       $x1 = $x2;
+                                       $y1 = $y2;
+                               }
+                       }
+               }
+               
+               /**
+                * Renders the caption.
+                */
+               protected function printCaption() {
+                       // Get the list of labels
+                       $labelList = $this->dataSet->getTitleList();
+                       
+                       // Create the caption
+                       $caption = new Caption();
+                       $caption->setPlot($this->plot);
+                       $caption->setLabelList($labelList);
+                       
+                       $palette = $this->plot->getPalette();
+                       $lineColorSet = $palette->lineColorSet;
+                       $caption->setColorSet($lineColorSet);
+                       
+                       // Render the caption
+                       $caption->render();
+               }
+
+               /**
+                * Render the chart image.
+                *
+                * @param string name of the file to render the image to (optional)
+                */
+               public function render($fileName = null) {
+                       // Check the data model
+                       $this->checkDataModel();
+
+                       $this->bound->computeBound($this->dataSet);
+                       $this->computeAxis();
+                       $this->computeLayout();
+                       $this->createImage();
+                       $this->plot->printLogo();
+                       $this->plot->printTitle();
+                       if (!$this->isEmptyDataSet(2)) {
+                               $this->printAxis();
+                               $this->printLine();
+                               if ($this->hasSeveralSerie) {
+                                       $this->printCaption();
+                               }
+                       }
+
+                       $this->plot->render($fileName);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/PieChart.php b/WEB-INF/lib/libchart/classes/view/chart/PieChart.php
new file mode 100644 (file)
index 0000000..108b592
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Tr�meaux (jm.tremeaux at gmail.com)
+        *
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        *
+        */
+
+       /**
+        * Pie chart.
+        *
+        * @author Jean-Marc Tr�meaux (jm.tremeaux at gmail.com)
+        */
+       class PieChart extends Chart {
+               protected $pieCenterX;
+               protected $pieCenterY;
+
+               /**
+                * Constructor of a pie chart.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               public function PieChart($width = 600, $height = 250) {
+                       parent::Chart($width, $height);
+                       $this->plot->setGraphPadding(new Padding(15, 10, 30, 30));
+               }
+
+               /**
+                * Computes the layout.
+                */
+               protected function computeLayout($hasCaption = true) {
+                       $this->plot->setHasCaption($hasCaption);
+                       $this->plot->computeLayout();
+
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       // Compute the coordinates of the pie
+                       $this->pieCenterX = $graphArea->x1 + ($graphArea->x2 - $graphArea->x1) / 2;
+                       $this->pieCenterY = $graphArea->y1 + ($graphArea->y2 - $graphArea->y1) / 2;
+
+                       $this->pieWidth = round(($graphArea->x2 - $graphArea->x1) * 4 / 5);
+                       $this->pieHeight = round(($graphArea->y2 - $graphArea->y1) * 3.7 / 5);
+                       $this->pieDepth = round($this->pieWidth * 0.05);
+               }
+
+               /**
+                * Compare two sampling point values, order from biggest to lowest value.
+                *
+                * @param double first value
+                * @param double second value
+                * @return integer result of the comparison
+                */
+               protected function sortPie($v1, $v2) {
+                       return $v1[0] == $v2[0] ? 0 :
+                               $v1[0] > $v2[0] ? -1 :
+                               1;
+               }
+
+               /**
+                * Compute pie values in percentage and sort them.
+                */
+               protected function computePercent() {
+                       $this->total = 0;
+                       $this->percent = array();
+
+                       $pointList = $this->dataSet->getPointList();
+                       foreach ($pointList as $point) {
+                               $this->total += $point->getY();
+                       }
+
+                       foreach ($pointList as $point) {
+                               $percent = $this->total == 0 ? 0 : 100 * $point->getY() / $this->total;
+
+                               array_push($this->percent, array($percent, $point));
+                       }
+
+                       usort($this->percent, array("PieChart", "sortPie"));
+               }
+
+               /**
+                * Creates the pie chart image.
+                */
+               protected function createImage() {
+                       parent::createImage();
+
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $primitive = $this->plot->getPrimitive();
+
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       // Legend box
+                       $primitive->outlinedBox($graphArea->x1, $graphArea->y1, $graphArea->x2, $graphArea->y2, $palette->axisColor[0], $palette->axisColor[1]);
+
+                       // Aqua-like background
+                       for ($i = $graphArea->y1 + 2; $i < $graphArea->y2 - 1; $i++) {
+                               $color = $palette->aquaColor[($i + 3) % 4];
+                               $primitive->line($graphArea->x1 + 2, $i, $graphArea->x2 - 2, $i, $color);
+                       }
+               }
+
+               /**
+                * Renders the caption.
+                */
+               protected function printCaption() {
+                       // Create a list of labels
+                       $labelList = array();
+                       foreach($this->percent as $percent) {
+                               list($percent, $point) = $percent;
+                               $label = $point->getX();
+
+                               array_push($labelList, $label);
+                       }
+
+                       // Create the caption
+                       $caption = new Caption();
+                       $caption->setPlot($this->plot);
+                       $caption->setLabelList($labelList);
+
+                       $palette = $this->plot->getPalette();
+                       $pieColorSet = $palette->pieColorSet;
+                       $caption->setColorSet($pieColorSet);
+
+                       // Render the caption
+                       $caption->render();
+               }
+
+               /**
+                * Draw a 2D disc.
+                *
+                * @param integer Center coordinate (y)
+                * @param array Colors for each portion
+                * @param bitfield Drawing mode
+                */
+               protected function drawDisc($cy, $colorArray, $mode) {
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+
+                       $i = 0;
+                       $angle1 = 0;
+                       $percentTotal = 0;
+
+                       foreach ($this->percent as $a) {
+                               list ($percent, $point) = $a;
+
+                               // If value is null, don't draw this arc
+                               if ($percent <= 0) {
+                                       continue;
+                               }
+
+                               $color = $colorArray[$i % count($colorArray)];
+                               $percentTotal += $percent;
+                               $angle2 = (int)($percentTotal * 360 / 100);
+
+                               if ($angle2 - $angle1 <= 0)
+                                       $angle2 = $angle1 + 1;
+                               imagefilledarc($img, $this->pieCenterX, $cy, $this->pieWidth, $this->pieHeight, $angle1, $angle2, $color->getColor($img), $mode);
+
+                               $angle1 = $angle2;
+
+                               $i++;
+                       }
+               }
+
+               /**
+                * Print the percentage text.
+                */
+               protected function drawPercent() {
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       $primitive = $this->plot->getPrimitive();
+
+                       $angle1 = 0;
+                       $percentTotal = 0;
+
+                       foreach ($this->percent as $a) {
+                               list ($percent, $point) = $a;
+
+                               // If value is null, don't print percentage
+                               if ($percent <= 0) {
+                                       continue;
+                               }
+
+                               $percentTotal += $percent;
+                               $angle2 = $percentTotal * 2 * M_PI / 100;
+
+                               $angle = $angle1 + ($angle2 - $angle1) / 2;
+                               $label = number_format($percent) . "%";
+
+                               $x = cos($angle) * ($this->pieWidth + 35) / 2 + $this->pieCenterX;
+                               $y = sin($angle) * ($this->pieHeight + 35) / 2 + $this->pieCenterY;
+
+                               $text->printText($img, $x, $y, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+
+                               $angle1 = $angle2;
+                       }
+               }
+
+               /**
+                * Print the pie chart.
+                */
+               protected function printPie() {
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       $primitive = $this->plot->getPrimitive();
+
+                       // Get the pie color set
+                       $pieColorSet = $palette->pieColorSet;
+                       $pieColorSet->reset();
+
+                       // Silhouette
+                       for ($cy = $this->pieCenterY + $this->pieDepth / 2; $cy >= $this->pieCenterY - $this->pieDepth / 2; $cy--) {
+                               $this->drawDisc($cy, $palette->pieColorSet->shadowColorList, IMG_ARC_EDGED);
+                       }
+
+                       // Top
+                       $this->drawDisc($this->pieCenterY - $this->pieDepth / 2, $palette->pieColorSet->colorList, IMG_ARC_PIE);
+
+                       // Top Outline
+                       $this->drawPercent();
+               }
+
+               /**
+                * Render the chart image.
+                *
+                * @param string name of the file to render the image to (optional)
+                */
+               public function render($fileName = null) {
+                       $this->computePercent();
+                       $this->computeLayout();
+                       $this->createImage();
+                       $this->plot->printLogo();
+                       $this->plot->printTitle();
+                       $this->printPie();
+                       $this->printCaption();
+
+                       $this->plot->render($fileName);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php b/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php
new file mode 100644 (file)
index 0000000..65adcf2
--- /dev/null
@@ -0,0 +1,217 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Chart composed of vertical bars.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class VerticalBarChart extends BarChart {
+               /**
+                * Ratio of empty space beside the bars.
+                */
+               private $emptyToFullRatio;
+
+               /**
+                * Creates a new vertical bar chart
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               public function VerticalBarChart($width = 600, $height = 250) {
+                       parent::BarChart($width, $height);
+
+                       $this->emptyToFullRatio = 1 / 5;
+                       $this->plot->setGraphPadding(new Padding(5, 30, 50, 50));
+               }
+
+               /**
+                * Computes the layout.
+                */
+               protected function computeLayout() {
+                       if ($this->hasSeveralSerie) {
+                               $this->plot->setHasCaption(true);
+                       }
+                       $this->plot->computeLayout();
+               }
+               
+               /**
+                * Print the horizontal and veritcal axis.
+                */
+               protected function printAxis() {
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       $stepValue = $this->axis->getTics();
+
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+                       
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+                       
+                       // Vertical axis
+                       for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+                               $y = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+                               imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+                               $text->printText($img, $graphArea->x1 - 5, $y, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+                       }
+
+                       // Get first serie of a list
+                       $pointList = $this->getFirstSerieOfList();
+
+                       // Horizontal Axis
+                       $pointCount = count($pointList);
+                       reset($pointList);
+                       $columnWidth = ($graphArea->x2 - $graphArea->x1) / $pointCount;
+                       for ($i = 0; $i <= $pointCount; $i++) {
+                               $x = $graphArea->x1 + $i * $columnWidth;
+
+                               imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+                               imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+                               if ($i < $pointCount) {
+                                       $point = current($pointList);
+                                       next($pointList);
+       
+                                       $label = $point->getX();
+
+                                       $text->printDiagonal($img, $x + $columnWidth * 1 / 3, $graphArea->y2 + 10, $this->plot->getTextColor(), $label);
+                               }
+                       }
+               }
+
+               /**
+                * Print the bars.
+                */
+               protected function printBar() {
+                       // Get the data as a list of series for consistency
+                       $serieList = $this->getDataAsSerieList();
+                       
+                       // Get graphical obects
+                       $img = $this->plot->getImg();
+                       $palette = $this->plot->getPalette();
+                       $text = $this->plot->getText();
+
+                       // Get the graph area
+                       $graphArea = $this->plot->getGraphArea();
+
+                       $barColorSet = $palette->barColorSet;
+                       $barColorSet->reset();
+
+                       $minValue = $this->axis->getLowerBoundary();
+                       $maxValue = $this->axis->getUpperBoundary();
+                       $stepValue = $this->axis->getTics();
+
+                       $serieCount = count($serieList);
+                       for ($j = 0; $j < $serieCount; $j++) {
+                               $serie = $serieList[$j];
+                               $pointList = $serie->getPointList();
+                               $pointCount = count($pointList);
+                               reset($pointList);
+
+                               // Get the next color
+                               $color = $barColorSet->currentColor();
+                               $shadowColor = $barColorSet->currentShadowColor();
+                               $barColorSet->next();
+
+                               $columnWidth = ($graphArea->x2 - $graphArea->x1) / $pointCount;
+                               for ($i = 0; $i < $pointCount; $i++) {
+                                       $x = $graphArea->x1 + $i * $columnWidth;
+
+                                       $point = current($pointList);
+                                       next($pointList);
+
+                                       $value = $point->getY();
+                                       
+                                       $ymin = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+                                       // Bar dimensions
+                                       $xWithMargin = $x + $columnWidth * $this->emptyToFullRatio;
+                                       $columnWidthWithMargin = $columnWidth * (1 - $this->emptyToFullRatio * 2);
+                                       $barWidth = $columnWidthWithMargin / $serieCount;
+                                       $barOffset = $barWidth * $j;
+                                       $x1 = $xWithMargin + $barOffset;
+                                       $x2 = $xWithMargin + $barWidth + $barOffset - 1;
+
+                                       // Text
+                                       $text->printText($img, $x1 + $barWidth / 2 , $ymin - 5, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN | $text->VERTICAL_BOTTOM_ALIGN);
+
+                                       // Vertical bar
+                                       imagefilledrectangle($img, $x1, $ymin, $x2, $graphArea->y2 - 1, $shadowColor->getColor($img));
+
+                                       // Prevents drawing a small box when y = 0
+                                       if ($ymin != $graphArea->y2) {
+                                               imagefilledrectangle($img, $x1 + 1, $ymin + 1, $x2 - 4, $graphArea->y2 - 1, $color->getColor($img));
+                                       }
+                               }
+                       }
+               }
+               
+               /**
+                * Renders the caption.
+                */
+               protected function printCaption() {
+                       // Get the list of labels
+                       $labelList = $this->dataSet->getTitleList();
+                       
+                       // Create the caption
+                       $caption = new Caption();
+                       $caption->setPlot($this->plot);
+                       $caption->setLabelList($labelList);
+                       
+                       $palette = $this->plot->getPalette();
+                       $barColorSet = $palette->barColorSet;
+                       $caption->setColorSet($barColorSet);
+                       
+                       // Render the caption
+                       $caption->render();
+               }
+
+               /**
+                * Render the chart image.
+                *
+                * @param string name of the file to render the image to (optional)
+                */
+               public function render($fileName = null) {
+                       // Check the data model
+                       $this->checkDataModel();
+                       
+                       $this->bound->computeBound($this->dataSet);
+                       $this->computeAxis();
+                       $this->computeLayout();
+                       $this->createImage();
+                       $this->plot->printLogo();
+                       $this->plot->printTitle();
+                       if (!$this->isEmptyDataSet(1)) {
+                               $this->printAxis();
+                               $this->printBar();
+                               if ($this->hasSeveralSerie) {
+                                       $this->printCaption();
+                               }
+                       }
+
+                       $this->plot->render($fileName);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/color/Color.php b/WEB-INF/lib/libchart/classes/view/color/Color.php
new file mode 100644 (file)
index 0000000..d97c700
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Color.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class Color {
+               private $red;
+               private $green;
+               private $blue;
+               private $alpha;
+               private $gdColor;
+       
+               /**
+                * Creates a new color
+                *
+                * @param integer red [0..255]
+                * @param integer green [0..255]
+                * @param integer blue [0..255]
+                * @param integer alpha [0..255]
+                */
+               public function Color($red, $green, $blue, $alpha = 0) {
+                       $this->red = (int) $red;
+                       $this->green = (int) $green;
+                       $this->blue = (int) $blue;
+                       $this->alpha = (int) round($alpha * 127.0 / 255);
+                       
+                       $this->gdColor = null;
+               }
+               
+               /**
+                * Get GD color.
+                *
+                * @param $img GD image resource
+                */
+               public function getColor($img) {
+                       // Checks if color has already been allocated
+                       if (!$this->gdColor) {
+                               if ($this->alpha == 0 || !function_exists('imagecolorallocatealpha')) {
+                                       $this->gdColor = imagecolorallocate($img, $this->red, $this->green, $this->blue);
+                               } else {
+                                       $this->gdColor = imagecolorallocatealpha($img, $this->red, $this->green, $this->blue, $this->alpha);
+                               }
+                       }
+                       
+                       // Returns GD color
+                       return $this->gdColor;
+               }
+               
+               /**
+                * Clip a color component in the interval [0..255]
+                *
+                * @param integer Component
+                * @return Clipped component
+                */
+               public function clip($component) {
+                       if ($component < 0) {
+                               $component = 0;
+                       } else if ($component > 255) {
+                               $component = 255;
+                       }
+                       
+                       return $component;
+               }
+               
+               /**
+                * Return a new color, which is a shadow of this one.
+                *
+                * @param double Multiplication factor
+                * @return Shadow color
+                */
+               public function getShadowColor($shadowFactor) {
+                       $red = $this->clip($this->red * $shadowFactor);
+                       $green = $this->clip($this->green * $shadowFactor);
+                       $blue = $this->clip($this->blue * $shadowFactor);
+                       $shadowColor = new Color($red, $green, $blue);
+                       
+                       return $shadowColor;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/color/ColorSet.php b/WEB-INF/lib/libchart/classes/view/color/ColorSet.php
new file mode 100644 (file)
index 0000000..9876ef0
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * A set of colors, used for drawing series of data.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 26 july 2007
+        */
+       class ColorSet {
+               public $colorList;
+               public $shadowColorList;
+       
+               /**
+                * ColorSet constructor.
+                *
+                * @param $shadowFactor Shadow factor
+                * @param $colorArray Colors as an array
+                */
+               public function ColorSet($colorList, $shadowFactor) {
+                       $this->colorList = $colorList;
+                       $this->shadowColorList = array();
+
+                       // Generate the shadow color set
+                       foreach ($colorList as $color) {
+                               $shadowColor = $color->getShadowColor($shadowFactor);
+
+                               array_push($this->shadowColorList, $shadowColor);
+                       }
+               }
+               
+               /**
+                * Reset the iterator over the collections of colors.
+                */
+               public function reset() {
+                       reset($this->colorList);
+                       reset($this->shadowColorList);
+               }
+
+               /**
+                * Iterate over the colors and shadow colors. When we go after the last one, loop over.
+                *
+                */
+               public function next() {
+                       $value = next($this->colorList);
+                       next($this->shadowColorList);
+                       
+                       // When we go after the last value, loop over.
+                       if ($value == FALSE) {
+                               $this->reset();
+                       }
+               }
+
+               /**
+                * Returns the current color.
+                *
+                * @return Current color
+                */
+               public function currentColor() {
+                       return current($this->colorList);
+               }
+
+               /**
+                * Returns the current shadow color.
+                *
+                * @return Current shadow color
+                */
+               public function currentShadowColor() {
+                       return current($this->shadowColorList);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/color/Palette.php b/WEB-INF/lib/libchart/classes/view/color/Palette.php
new file mode 100644 (file)
index 0000000..48189b9
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Color palette shared by all chart types.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 25 july 2007
+        */
+       class Palette {
+               public $red;
+               public $axisColor;
+               public $aquaColor;
+               
+               public $barColorSet;
+               public $lineColorSet;
+               public $pieColorSet;
+       
+               /**
+                * Palette constructor.
+                */
+               public function Palette() {
+                       $this->red = new Color(255, 0, 0);
+               
+                       // Colors for the horizontal and vertical axis
+                       $this->axisColor = array(
+                                       new Color(201, 201, 201),
+                                       new Color(158, 158, 158)
+                       );
+
+                       // Colors for the background
+                       $this->aquaColor = array(
+                                       new Color(242, 242, 242),
+                                       new Color(231, 231, 231),
+                                       new Color(239, 239, 239),
+                                       new Color(253, 253, 253)
+                       );
+                       
+                       // Colors for the bars
+                       $this->barColorSet = new ColorSet(array(
+                                       new Color(42, 71, 181),
+                                       new Color(243, 198, 118),
+                                       new Color(128, 63, 35),
+                                       new Color(195, 45, 28),
+                                       new Color(224, 198, 165),
+                                       new Color(239, 238, 218),
+                                       new Color(40, 72, 59),
+                                       new Color(71, 112, 132),
+                                       new Color(167, 192, 199),
+                                       new Color(218, 233, 202)
+                       ), 0.75);
+
+                       // Colors for the lines
+                       $this->lineColorSet = new ColorSet(array(
+                                       new Color(172, 172, 210),
+                                       new Color(2, 78, 0),
+                                       new Color(148, 170, 36),
+                                       new Color(233, 191, 49),
+                                       new Color(240, 127, 41),
+                                       new Color(243, 63, 34),
+                                       new Color(190, 71, 47),
+                                       new Color(135, 81, 60),
+                                       new Color(128, 78, 162),
+                                       new Color(121, 75, 255),
+                                       new Color(142, 165, 250),
+                                       new Color(162, 254, 239),
+                                       new Color(137, 240, 166),
+                                       new Color(104, 221, 71),
+                                       new Color(98, 174, 35),
+                                       new Color(93, 129, 1)
+                       ), 0.75);
+
+                       // Colors for the pie
+                       $this->pieColorSet = new ColorSet(array(
+                               new Color(2, 78, 0),
+                               new Color(148, 170, 36),
+                               new Color(233, 191, 49),
+                               new Color(240, 127, 41),
+                               new Color(243, 63, 34),
+                               new Color(190, 71, 47),
+                               new Color(135, 81, 60),
+                               new Color(128, 78, 162),
+                               new Color(121, 75, 255),
+                               new Color(142, 165, 250),
+                               new Color(162, 254, 239),
+                               new Color(137, 240, 166),
+                               new Color(104, 221, 71),
+                               new Color(98, 174, 35),
+                               new Color(93, 129, 1)
+                       ), 0.7);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/plot/Plot.php b/WEB-INF/lib/libchart/classes/view/plot/Plot.php
new file mode 100644 (file)
index 0000000..a57d4ff
--- /dev/null
@@ -0,0 +1,415 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * The plot holds graphical attributes, and is responsible for computing the layout of the graph.
+        * The layout is quite simple right now, with 4 areas laid out like that:
+        * (of course this is subject to change in the future).
+        *
+        * output area------------------------------------------------|
+        * |  (outer padding)                                         |
+        * |  image area--------------------------------------------| |
+        * |  | (title padding)                                     | |
+        * |  | title area----------------------------------------| | |
+        * |  | |-------------------------------------------------| | |
+        * |  |                                                     | |
+        * |  | (graph padding)              (caption padding)      | |
+        * |  | graph area----------------|  caption area---------| | |
+        * |  | |                         |  |                    | | |
+        * |  | |                         |  |                    | | |
+        * |  | |                         |  |                    | | |
+        * |  | |                         |  |                    | | |
+        * |  | |                         |  |                    | | |
+        * |  | |-------------------------|  |--------------------| | |
+        * |  |                                                     | |
+        * |  |-----------------------------------------------------| |
+        * |                                                          |
+        * |----------------------------------------------------------|
+        *
+        * All area dimensions are known in advance , and the optional logo is drawn in absolute coordinates.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * Created on 27 july 2007
+        */
+       class Plot {
+               // Style properties
+               protected $title;
+               protected $logoFileName;
+               
+               // Outer area, whose dimension is the same as the PNG returned
+               protected $outputArea;
+               
+               // Outer padding surrounding the whole image, everything outside is blank
+               protected $outerPadding;
+               
+               // Coordinates of the area inside the outer padding
+               protected $imageArea;
+               
+               // Fixed title height in pixels
+               protected $titleHeight;
+               
+               // Padding of the title area
+               protected $titlePadding;
+               
+               // Coordinates of the title area
+               protected $titleArea;
+               
+               // True if the plot has a caption
+               protected $hasCaption;
+               
+               // Ratio of graph/caption in width
+               protected $graphCaptionRatio;
+               
+               // Padding of the graph area
+               protected $graphPadding;
+               
+               // Coordinates of the graph area
+               protected $graphArea;
+               
+               // Padding of the caption area
+               protected $captionPadding;
+               
+               // Coordinates of the caption area
+               protected $captionArea;
+               
+               /**
+                * Text writer.
+                */
+               protected $text;
+               
+               /**
+                * Color palette.
+                */
+               protected $palette;
+               
+               /**
+                * GD image
+                */
+               protected $img;
+
+               /**
+                * Drawing primitives
+                */
+               protected $primitive;
+
+               protected $backGroundColor;
+               protected $textColor;
+
+               /**
+                * Constructor of Plot.
+                *
+                * @param integer width of the image
+                * @param integer height of the image
+                */
+               public function Plot($width, $height) {
+                       $this->width = $width;
+                       $this->height = $height;
+
+                       $this->text = new Text();
+                       $this->palette = new Palette();
+                       
+                       // Default layout
+                       $this->outputArea = new Rectangle(0, 0, $width - 1, $height - 1);
+                       $this->outerPadding = new Padding(5);
+                       $this->titleHeight = 26;
+                       $this->titlePadding = new Padding(5);
+                       $this->hasCaption = false;
+                       $this->graphCaptionRatio = 0.50;
+                       $this->graphPadding = new Padding(50);
+                       $this->captionPadding = new Padding(15);
+               }
+
+               /**
+                * Compute the area inside the outer padding (outside is white).
+                */
+               private function computeImageArea() {
+                       $this->imageArea = $this->outputArea->getPaddedRectangle($this->outerPadding);
+               }
+               
+               /**
+                * Compute the title area.
+                */
+               private function computeTitleArea() {
+                       $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+                       $titleArea = new Rectangle(
+                                       $this->imageArea->x1,
+                                       $this->imageArea->y1,
+                                       $this->imageArea->x2,
+                                       $titleUnpaddedBottom - 1
+                       );
+                       $this->titleArea = $titleArea->getPaddedRectangle($this->titlePadding);
+               }
+               
+               /**
+                * Compute the graph area.
+                */
+               private function computeGraphArea() {
+                       $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+                       $graphArea = null;
+                       if ($this->hasCaption) {
+                               $graphUnpaddedRight = $this->imageArea->x1 + ($this->imageArea->x2 - $this->imageArea->x1) * $this->graphCaptionRatio
+                                               + $this->graphPadding->left + $this->graphPadding->right;
+                               $graphArea = new Rectangle(
+                                               $this->imageArea->x1,
+                                               $titleUnpaddedBottom,
+                                               $graphUnpaddedRight - 1,
+                                               $this->imageArea->y2
+                               );
+                       } else {
+                               $graphArea = new Rectangle(
+                                               $this->imageArea->x1,
+                                               $titleUnpaddedBottom,
+                                               $this->imageArea->x2,
+                                               $this->imageArea->y2
+                               );
+                       }
+                       $this->graphArea = $graphArea->getPaddedRectangle($this->graphPadding);
+               }
+               
+               /**
+                * Compute the caption area.
+                */
+               private function computeCaptionArea() {
+                       $graphUnpaddedRight = $this->imageArea->x1 + ($this->imageArea->x2 - $this->imageArea->x1) * $this->graphCaptionRatio
+                                       + $this->graphPadding->left + $this->graphPadding->right;
+                       $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+                       $captionArea = new Rectangle(
+                                       $graphUnpaddedRight,
+                                       $titleUnpaddedBottom,
+                                       $this->imageArea->x2,
+                                       $this->imageArea->y2
+                       );
+                       $this->captionArea = $captionArea->getPaddedRectangle($this->captionPadding);
+               }
+               
+               /**
+                * Compute the layout of all areas of the graph.
+                */
+               public function computeLayout() {
+                       $this->computeImageArea();
+                       $this->computeTitleArea();
+                       $this->computeGraphArea();
+                       if ($this->hasCaption) {
+                               $this->computeCaptionArea();
+                       }
+               }
+               
+               /**
+                * Creates and initialize the image.
+                */
+               public function createImage() {
+                       $this->img = imagecreatetruecolor($this->width, $this->height);
+                       
+                       $this->primitive = new Primitive($this->img);
+
+                       $this->backGroundColor = new Color(255, 255, 255);
+                       $this->textColor = new Color(0, 0, 0);
+
+                       // White background
+                       imagefilledrectangle($this->img, 0, 0, $this->width - 1, $this->height - 1, $this->backGroundColor->getColor($this->img));
+                       
+                       //imagerectangle($this->img, $this->imageArea->x1, $this->imageArea->y1, $this->imageArea->x2, $this->imageArea->y2, $this->palette->red->getColor($this->img));
+               }
+
+               /**
+                * Print the title to the image.
+                */
+               public function printTitle() {
+                       $yCenter = $this->titleArea->y1 + ($this->titleArea->y2 - $this->titleArea->y1) / 2;
+                       $this->text->printCentered($this->img, $yCenter, $this->textColor, $this->title, $this->text->fontCondensedBold);
+               }
+
+               /**
+                * Print the logo image to the image.
+                */
+               public function printLogo() {
+                       @$logoImage = imageCreateFromPNG($this->logoFileName);
+
+                       if ($logoImage) {
+                               imagecopymerge($this->img, $logoImage, 2 * $this->outerPadding->left, $this->outerPadding->top, 0, 0, imagesx($logoImage), imagesy($logoImage), 100);
+                       }
+               }
+
+               /**
+                * Renders to a file or to standard output.
+                *
+                * @param fileName File name (optional)
+                */
+               public function render($fileName) {
+                       if (isset($fileName)) {
+                               imagepng($this->img, $fileName);
+                       } else {
+                               imagepng($this->img);
+                       }
+               }
+
+               /**
+                * Sets the title.
+                *
+                * @param string New title
+                */
+               public function setTitle($title) {
+                       $this->title = $title;
+               }
+
+               /**
+                * Sets the logo image file name.
+                *
+                * @param string New logo image file name
+                */
+               public function setLogoFileName($logoFileName) {
+                       $this->logoFileName = $logoFileName;
+               }
+
+               /**
+                * Return the GD image.
+                *
+                * @return GD Image
+                */
+               public function getImg() {
+                       return $this->img;
+               }
+
+               /**
+                * Return the palette.
+                *
+                * @return palette
+                */
+               public function getPalette() {
+                       return $this->palette;
+               }
+
+               /**
+                * Return the text.
+                *
+                * @return text
+                */
+               public function getText() {
+                       return $this->text;
+               }
+
+               /**
+                * Return the primitive.
+                *
+                * @return primitive
+                */
+               public function getPrimitive() {
+                       return $this->primitive;
+               }
+
+               /**
+                * Return the outer padding.
+                *
+                * @param integer Outer padding value in pixels
+                */
+               public function getOuterPadding() {
+                       return $outerPadding;
+               }
+
+               /**
+                * Set the outer padding.
+                *
+                * @param integer Outer padding value in pixels
+                */
+               public function setOuterPadding($outerPadding) {
+                       $this->outerPadding = $outerPadding;
+               }
+
+               /**
+                * Return the title height.
+                *
+                * @param integer title height
+                */
+               public function setTitleHeight($titleHeight) {
+                       $this->titleHeight = $titleHeight;
+               }
+
+               /**
+                * Return the title padding.
+                *
+                * @param integer title padding
+                */
+               public function setTitlePadding($titlePadding) {
+                       $this->titlePadding = $titlePadding;
+               }
+
+               /**
+                * Return the graph padding.
+                *
+                * @param integer graph padding
+                */
+               public function setGraphPadding($graphPadding) {
+                       $this->graphPadding = $graphPadding;
+               }
+
+               /**
+                * Set if the graph has a caption.
+                *
+                * @param boolean graph has a caption
+                */
+               public function setHasCaption($hasCaption) {
+                       $this->hasCaption = $hasCaption;
+               }
+
+               /**
+                * Set the caption padding.
+                *
+                * @param integer caption padding
+                */
+               public function setCaptionPadding($captionPadding) {
+                       $this->captionPadding = $captionPadding;
+               }
+
+               /**
+                * Set the graph/caption ratio.
+                *
+                * @param integer caption padding
+                */
+               public function setGraphCaptionRatio($graphCaptionRatio) {
+                       $this->graphCaptionRatio = $graphCaptionRatio;
+               }
+
+               /**
+                * Return the graph area.
+                *
+                * @return graph area
+                */
+               public function getGraphArea() {
+                       return $this->graphArea;
+               }
+
+               /**
+                * Return the caption area.
+                *
+                * @return caption area
+                */
+               public function getCaptionArea() {
+                       return $this->captionArea;
+               }
+
+               /**
+                * Return the text color.
+                *
+                * @return text color
+                */
+               public function getTextColor() {
+                       return $this->textColor;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Padding.php b/WEB-INF/lib/libchart/classes/view/primitive/Padding.php
new file mode 100644 (file)
index 0000000..9cade66
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Primitive geometric object representing a padding. 
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * @Created on 27 july 2007
+        */
+       class Padding {
+               /**
+                * Top padding.
+                */
+               public $top;
+               
+               /**
+                * Right padding.
+                */
+               public $right;
+               
+               /**
+                * Bottom padding.
+                */
+               public $bottom;
+       
+               /**
+                * Left padding.
+                */
+               public $left;
+
+               /**
+                * Creates a new padding.
+                *
+                * @param integer Top padding
+                * @param integer Right padding
+                * @param integer Bottom padding
+                * @param integer Left padding
+                */
+               public function Padding($top, $right = null, $bottom = null, $left = null) {
+                       $this->top = $top;
+                       if ($right == null) {
+                               $this->right = $top;
+                               $this->bottom = $top;
+                               $this->left = $top;
+                       } else {
+                               $this->right = $right;
+                               $this->bottom = $bottom;
+                               $this->left = $left;
+                       }
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php b/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php
new file mode 100644 (file)
index 0000000..b6b26bd
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Graphic primitives, extends GD with chart related primitives.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class Primitive {
+               private $img;
+       
+               /**
+                * Creates a new primitive object
+                *
+                * @param       resource        GD image resource
+                */
+               public function Primitive($img) {
+                       $this->img = $img;
+               }
+               
+               /**
+                * Draws a straight line.
+                *
+                * @param integer line start (X)
+                * @param integer line start (Y)
+                * @param integer line end (X)
+                * @param integer line end (Y)
+                * @param Color line color
+                */
+               public function line($x1, $y1, $x2, $y2, $color, $width = 1) {
+                       imagefilledpolygon($this->img, array($x1, $y1 - $width / 2, $x1, $y1 + $width / 2, $x2, $y2 + $width / 2, $x2, $y2 - $width / 2), 4, $color->getColor($this->img));
+                       // imageline($this->img, $x1, $y1, $x2, $y2, $color->getColor($this->img));
+               }
+
+               /**
+                * Draw a filled gray box with thick borders and darker corners.
+                *
+                * @param integer top left coordinate (x)
+                * @param integer top left coordinate (y)
+                * @param integer bottom right coordinate (x)
+                * @param integer bottom right coordinate (y)
+                * @param Color edge color
+                * @param Color corner color
+                */
+               public function outlinedBox($x1, $y1, $x2, $y2, $color0, $color1) {
+                       imagefilledrectangle($this->img, $x1, $y1, $x2, $y2, $color0->getColor($this->img));
+                       imagerectangle($this->img, $x1, $y1, $x1 + 1, $y1 + 1, $color1->getColor($this->img));
+                       imagerectangle($this->img, $x2 - 1, $y1, $x2, $y1 + 1, $color1->getColor($this->img));
+                       imagerectangle($this->img, $x1, $y2 - 1, $x1 + 1, $y2, $color1->getColor($this->img));
+                       imagerectangle($this->img, $x2 - 1, $y2 - 1, $x2, $y2, $color1->getColor($this->img));
+               }
+
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php b/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php
new file mode 100644 (file)
index 0000000..402214e
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * A rectangle identified by the top-left and the bottom-right corners.
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * @Created on 27 july 2007
+        */
+       class Rectangle {
+               /**
+                * Top left X.
+                */
+               public $x1;
+
+               /**
+                * Top left Y.
+                */
+               public $y1;
+               
+               /**
+                * Bottom right X.
+                */
+               public $x2;
+               
+               /**
+                * Bottom right Y.
+                */
+               public $y2;
+       
+               /**
+                * Constructor of Rectangle.
+                *
+                * @param x1 Left edge coordinate
+                * @param y1 Upper edge coordinate
+                * @param x2 Right edge coordinate
+                * @param y2 Bottom edge coordinate
+                */
+               public function Rectangle($x1, $y1, $x2, $y2) {
+                       $this->x1 = $x1;
+                       $this->y1 = $y1;
+                       $this->x2 = $x2;
+                       $this->y2 = $y2;
+               }
+               
+               /**
+                * Apply a padding and returns the resulting rectangle.
+                * The result is an enlarged rectangle.
+                *
+                * @return Padded rectangle
+                */
+               public function getPaddedRectangle($padding) {
+                       $rectangle = new Rectangle(
+                                       $this->x1 + $padding->left,
+                                       $this->y1 + $padding->top,
+                                       $this->x2 - $padding->right,
+                                       $this->y2 - $padding->bottom
+                       );
+                       
+                       //echo "(" . $this->x1 . "," . $this->y1 . ") (" . $this->x2 . "," . $this->y2 . ")<br>";
+                       return $rectangle;
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/classes/view/text/Text.php b/WEB-INF/lib/libchart/classes/view/text/Text.php
new file mode 100644 (file)
index 0000000..eeef1cf
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+       /* Libchart - PHP chart library
+        * Copyright (C) 2005-2008 Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        * 
+        * 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 3 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, see <http://www.gnu.org/licenses/>.
+        * 
+        */
+       
+       /**
+        * Text drawing helper
+        *
+        * @author Jean-Marc Trémeaux (jm.tremeaux at gmail.com)
+        */
+       class Text {
+               public $HORIZONTAL_LEFT_ALIGN = 1;
+               public $HORIZONTAL_CENTER_ALIGN = 2;
+               public $HORIZONTAL_RIGHT_ALIGN = 4;
+               public $VERTICAL_TOP_ALIGN = 8;
+               public $VERTICAL_CENTER_ALIGN = 16;
+               public $VERTICAL_BOTTOM_ALIGN = 32;
+
+               /**
+                * Creates a new text drawing helper.
+                */
+               public function Text() {
+                       $baseDir = dirname(__FILE__) . "/../../../";
+               
+                       // Free low-res fonts based on Bitstream Vera <http://dejavu.sourceforge.net/wiki/>
+                       $this->fontCondensed = $baseDir . "fonts/DejaVuSansCondensed.ttf";
+                       $this->fontCondensedBold = $baseDir . "fonts/DejaVuSansCondensed-Bold.ttf";
+               }
+
+               /**
+                * Print text.
+                *
+                * @param Image GD image
+                * @param integer text coordinate (x)
+                * @param integer text coordinate (y)
+                * @param Color text color
+                * @param string text value
+                * @param string font file name
+                * @param bitfield text alignment
+                */
+               public function printText($img, $px, $py, $color, $text, $fontFileName, $align = 0) {
+                       if (!($align & $this->HORIZONTAL_CENTER_ALIGN) && !($align & $this->HORIZONTAL_RIGHT_ALIGN)) {
+                               $align |= $this->HORIZONTAL_LEFT_ALIGN;
+                       }
+
+                       if (!($align & $this->VERTICAL_CENTER_ALIGN) && !($align & $this->VERTICAL_BOTTOM_ALIGN)) {
+                               $align |= $this->VERTICAL_TOP_ALIGN;
+                       }
+
+                       $fontSize = 8;
+                       $lineSpacing = 1;
+
+                       list ($llx, $lly, $lrx, $lry, $urx, $ury, $ulx, $uly) = imageftbbox($fontSize, 0, $fontFileName, $text, array("linespacing" => $lineSpacing));
+
+                       $textWidth = $lrx - $llx;
+                       $textHeight = $lry - $ury;
+
+                       $angle = 0;
+
+                       if ($align & $this->HORIZONTAL_CENTER_ALIGN) {
+                               $px -= $textWidth / 2;
+                       }
+
+                       if ($align & $this->HORIZONTAL_RIGHT_ALIGN) {
+                               $px -= $textWidth;
+                       }
+
+                       if ($align & $this->VERTICAL_CENTER_ALIGN) {
+                               $py += $textHeight / 2;
+                       }
+
+                       if ($align & $this->VERTICAL_TOP_ALIGN) {
+                               $py += $textHeight;
+                       }
+
+                       imagettftext($img, $fontSize, $angle, $px, $py, $color->getColor($img), $fontFileName, $text);
+               }
+               
+               /**
+                * Print text centered horizontally on the image.
+                *
+                * @param Image GD image
+                * @param integer text coordinate (y)
+                * @param Color text color
+                * @param string text value
+                * @param string font file name
+                */
+               public function printCentered($img, $py, $color, $text, $fontFileName) {
+                       $this->printText($img, imagesx($img) / 2, $py, $color, $text, $fontFileName, $this->HORIZONTAL_CENTER_ALIGN | $this->VERTICAL_CENTER_ALIGN);
+               }
+
+               /**
+                * Print text in diagonal.
+                *
+                * @param Image GD image
+                * @param integer text coordinate (x)
+                * @param integer text coordinate (y)
+                * @param Color text color
+                * @param string text value
+                */
+               public function printDiagonal($img, $px, $py, $color, $text) {
+                       $fontSize = 8;
+                       $fontFileName = $this->fontCondensed;
+
+                       $lineSpacing = 1;
+
+                       list ($lx, $ly, $rx, $ry) = imageftbbox($fontSize, 0, $fontFileName, $text, array("linespacing" => $lineSpacing));
+                       $textWidth = $rx - $lx;
+
+                       $angle = -45;
+
+                       imagettftext($img, $fontSize, $angle, $px, $py, $color->getColor($img), $fontFileName, $text);
+               }
+       }
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed-Bold.ttf b/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed-Bold.ttf
new file mode 100644 (file)
index 0000000..6f4e42e
Binary files /dev/null and b/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed-Bold.ttf differ
diff --git a/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed.ttf b/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed.ttf
new file mode 100644 (file)
index 0000000..12af357
Binary files /dev/null and b/WEB-INF/lib/libchart/fonts/DejaVuSansCondensed.ttf differ
diff --git a/WEB-INF/lib/mail/Mailer.class.php b/WEB-INF/lib/mail/Mailer.class.php
new file mode 100644 (file)
index 0000000..1b84e74
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+// License here
+
+class Mailer {
+       var $mSendType;
+       var $mCharSet = "iso-8859-1";
+       var $mContentType = "text/plain";
+       var $mSender;
+       var $mReceiver;
+       var $mReceiverCC;
+
+    function Mailer($type='standard') {
+       $this->mSendType = $type;
+    }
+
+    function setSendType($value) {
+       $this->mSendType = $value;
+    }
+
+    function setCharSet($value) {
+       $this->mCharSet = $value;
+    }
+
+    function setContentType($value) {
+       $this->mContentType = $value;
+    }
+
+    function setReceiver($value) {
+       $this->mReceiver = $value;
+    }
+
+    function setReceiverCC($value) {
+       $this->mReceiverCC = $value;
+    }
+
+    function setSender($value) {
+       $this->mSender = $value;
+    }
+
+    function send($subject, $data) {
+       $data = chunk_split(base64_encode($data));
+       $subject = Mailer::mimeEncode($subject, $this->mCharSet);
+
+       $headers = array(
+               'From' => $this->mSender,
+               'To' => $this->mReceiver);
+       if (isset($this->mReceiverCC)) $headers = array_merge($headers, array(
+               'CC' => $this->mReceiverCC));
+       $headers = array_merge($headers, array(
+               'Subject' => $subject,
+               'MIME-Version' => '1.0',
+               'Content-Type' => $this->mContentType.'; charset='.$this->mCharSet,
+               'Content-Transfer-Encoding' => 'BASE64',
+       ));
+
+       // PEAR::Mail
+       require_once('Mail.php');
+
+       $recipients = $this->mReceiver;
+       switch ($this->mSendType) {
+               case 'mail':
+                                       $mail = Mail::factory('mail');
+                       break;
+
+               case "smtp":
+                       // Mail_smtp does not do CC -> recipients conversion
+                       if (!empty($this->mReceiverCC)) {
+                               // make exactly one space after a comma
+                               $recipients .= ', ' . preg_replace('/,[[:space:]]+/', ', ', $this->mReceiverCC);;
+                       }
+                       
+                       $host = defined('MAIL_SMTP_HOST') ? MAIL_SMTP_HOST : 'localhost';
+                       $port = defined('MAIL_SMTP_PORT') ? MAIL_SMTP_PORT : '25';
+                       $username = defined('MAIL_SMTP_USER') ? MAIL_SMTP_USER : null;
+                       $password = defined('MAIL_SMTP_PASSWORD') ? MAIL_SMTP_PASSWORD : null;
+                       $auth = (defined('MAIL_SMTP_AUTH') && isTrue(MAIL_SMTP_AUTH)) ? true : false;
+                       $debug = (defined('MAIL_SMTP_DEBUG') && isTrue(MAIL_SMTP_DEBUG)) ? true : false;
+                                               
+                       $mail = Mail::factory('smtp', array ('host' => $host,
+                         'port' => $port,
+                         'username' => $username,
+                         'password' => $password,
+                         'auth' => $auth,
+                         'debug' => $debug));
+                       break;
+       }
+       
+       if (defined('MAIL_SMTP_DEBUG') && isTrue(MAIL_SMTP_DEBUG))
+          PEAR::setErrorHandling(PEAR_ERROR_PRINT);
+       $res = $mail->send($recipients, $headers, $data);
+       return (!is_a($res, 'PEAR_Error'));
+    }
+
+    /**
+     * convert to base64-string
+     *
+     * @param string $in_str
+     * @param string $charset
+     * @return string
+     */
+    function mimeEncode($in_str, $charset) {
+          $out_str = $in_str;
+          if ($out_str && $charset) {
+
+              $end = "?=";
+              $start = "=?" . strtoupper($charset) . "?B?";
+              $spacer = $end . "\r\n " . $start;
+
+              $length = 75 - strlen($start) - strlen($end);
+              $length = floor($length/2) * 2;
+
+              $out_str = base64_encode($out_str);
+              //$out_str = Mail::encodemime($out_str,"base64");
+              //$out_str = chunk_split($out_str, $length, $spacer);
+
+              //$spacer = preg_quote($spacer);
+              //$out_str = preg_replace("/" . $spacer . "$/", "", $out_str);
+              $out_str = $start . $out_str . $end;
+          }
+          return $out_str;
+       }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/INSTALL b/WEB-INF/lib/pear/INSTALL
new file mode 100644 (file)
index 0000000..08f4c0b
--- /dev/null
@@ -0,0 +1,54 @@
+PEAR - The PEAR Installer
+=========================
+Installing the PEAR Installer.
+
+You should install PEAR on a local development machine first.  Installing
+PEAR on a remote production machine should only be done after you are
+familiar with PEAR and have tested code using PEAR on your development
+machine.
+
+There are two methods of installing PEAR
+ - PEAR bundled in PHP
+ - go-pear
+
+We will first examine how to install PEAR that is bundled with PHP.
+
+Microsoft Windows
+=================
+If you are running PHP 5.2.0 or newer, simply download and
+run the windows installer (.msi) and PEAR can be automatically
+installed.
+
+Otherwise, for older PHP versions, download the .zip of windows,
+there is a script included with your PHP distribution that is called
+"go-pear".  You must open a command box in order to run it.  Click
+"start" then click "Run..." and type "cmd.exe" to open a command box.
+Use "cd" to change directory to the location of PHP where you unzipped it,
+and run the go-pear command.
+
+Unix
+====
+When compiling PHP from source, you simply need to include the
+--with-pear directive on the "./configure" command.  This is "on"
+by default in most PHP versions, but it doesn't hurt to list it
+explicitly.  You should also consider enabling the zlib extension via
+--enable-zlib, so that the PEAR installer will be able to handle gzipped
+files (i.e. smaller package files for faster downloads).  Later, when you
+run "make install" to install PHP itself, part of the process will be
+prompts that ask you where you want PEAR to be installed.
+
+go-pear
+=======
+For users who cannot perform the above steps, or who wish to obtain the
+latest PEAR with a slightly higher risk of failure, use go-pear.  go-pear
+is obtained by downloading http://pear.php.net/go-pear and saving it as go-pear.php.
+After downloading, simply run "php go-pear.php" or open it in a web browser
+(windows only) to download and install PEAR.
+
+You can always ask general installation questions on pear-general@lists.php.net,
+a public mailing list devoted to support for PEAR packages and installation-
+related issues.
+
+Happy PHPing, we hope PEAR will be a great tool for your development work!
+
+$Id: INSTALL 313023 2011-07-06 19:17:11Z dufuz $
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/LICENSE b/WEB-INF/lib/pear/LICENSE
new file mode 100644 (file)
index 0000000..a00a242
--- /dev/null
@@ -0,0 +1,27 @@
+Copyright (c) 1997-2009,
+ Stig Bakken <ssb@php.net>,
+ Gregory Beaver <cellog@php.net>,
+ Helgi Þormar Þorbjörnsson <helgi@php.net>,
+ Tomas V.V.Cox <cox@idecnet.com>,
+ Martin Jansen <mj@php.net>.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/WEB-INF/lib/pear/MDB2.php b/WEB-INF/lib/pear/MDB2.php
new file mode 100644 (file)
index 0000000..45c0802
--- /dev/null
@@ -0,0 +1,4607 @@
+<?php
+// vim: set et ts=4 sw=4 fdm=marker:
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: MDB2.php 328183 2012-10-29 15:10:42Z danielc $
+//
+
+/**
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+
+require_once 'PEAR.php';
+
+// {{{ Error constants
+
+/**
+ * The method mapErrorCode in each MDB2_dbtype implementation maps
+ * native error codes to one of these.
+ *
+ * If you add an error code here, make sure you also add a textual
+ * version of it in MDB2::errorMessage().
+ */
+
+define('MDB2_OK',                      true);
+define('MDB2_ERROR',                     -1);
+define('MDB2_ERROR_SYNTAX',              -2);
+define('MDB2_ERROR_CONSTRAINT',          -3);
+define('MDB2_ERROR_NOT_FOUND',           -4);
+define('MDB2_ERROR_ALREADY_EXISTS',      -5);
+define('MDB2_ERROR_UNSUPPORTED',         -6);
+define('MDB2_ERROR_MISMATCH',            -7);
+define('MDB2_ERROR_INVALID',             -8);
+define('MDB2_ERROR_NOT_CAPABLE',         -9);
+define('MDB2_ERROR_TRUNCATED',          -10);
+define('MDB2_ERROR_INVALID_NUMBER',     -11);
+define('MDB2_ERROR_INVALID_DATE',       -12);
+define('MDB2_ERROR_DIVZERO',            -13);
+define('MDB2_ERROR_NODBSELECTED',       -14);
+define('MDB2_ERROR_CANNOT_CREATE',      -15);
+define('MDB2_ERROR_CANNOT_DELETE',      -16);
+define('MDB2_ERROR_CANNOT_DROP',        -17);
+define('MDB2_ERROR_NOSUCHTABLE',        -18);
+define('MDB2_ERROR_NOSUCHFIELD',        -19);
+define('MDB2_ERROR_NEED_MORE_DATA',     -20);
+define('MDB2_ERROR_NOT_LOCKED',         -21);
+define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22);
+define('MDB2_ERROR_INVALID_DSN',        -23);
+define('MDB2_ERROR_CONNECT_FAILED',     -24);
+define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25);
+define('MDB2_ERROR_NOSUCHDB',           -26);
+define('MDB2_ERROR_ACCESS_VIOLATION',   -27);
+define('MDB2_ERROR_CANNOT_REPLACE',     -28);
+define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29);
+define('MDB2_ERROR_DEADLOCK',           -30);
+define('MDB2_ERROR_CANNOT_ALTER',       -31);
+define('MDB2_ERROR_MANAGER',            -32);
+define('MDB2_ERROR_MANAGER_PARSE',      -33);
+define('MDB2_ERROR_LOADMODULE',         -34);
+define('MDB2_ERROR_INSUFFICIENT_DATA',  -35);
+define('MDB2_ERROR_NO_PERMISSION',      -36);
+define('MDB2_ERROR_DISCONNECT_FAILED',  -37);
+
+// }}}
+// {{{ Verbose constants
+/**
+ * These are just helper constants to more verbosely express parameters to prepare()
+ */
+
+define('MDB2_PREPARE_MANIP', false);
+define('MDB2_PREPARE_RESULT', null);
+
+// }}}
+// {{{ Fetchmode constants
+
+/**
+ * This is a special constant that tells MDB2 the user hasn't specified
+ * any particular get mode, so the default should be used.
+ */
+define('MDB2_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+define('MDB2_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+define('MDB2_FETCHMODE_ASSOC', 2);
+
+/**
+ * Column data as object properties
+ */
+define('MDB2_FETCHMODE_OBJECT', 3);
+
+/**
+ * For multi-dimensional results: normally the first level of arrays
+ * is the row number, and the second level indexed by column number or name.
+ * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays
+ * is the column name, and the second level the row number.
+ */
+define('MDB2_FETCHMODE_FLIPPED', 4);
+
+// }}}
+// {{{ Portability mode constants
+
+/**
+ * Portability: turn off all portability features.
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_NONE', 0);
+
+/**
+ * Portability: convert names of tables and fields to case defined in the
+ * "field_case" option when using the query*(), fetch*() and tableInfo() methods.
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_FIX_CASE', 1);
+
+/**
+ * Portability: right trim the data output by query*() and fetch*().
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_RTRIM', 2);
+
+/**
+ * Portability: force reporting the number of rows deleted.
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_DELETE_COUNT', 4);
+
+/**
+ * Portability: not needed in MDB2 (just left here for compatibility to DB)
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_NUMROWS', 8);
+
+/**
+ * Portability: makes certain error messages in certain drivers compatible
+ * with those from other DBMS's.
+ *
+ * + mysql, mysqli:  change unique/primary key constraints
+ *   MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT
+ *
+ * + odbc(access):  MS's ODBC driver reports 'no such field' as code
+ *   07001, which means 'too few parameters.'  When this option is on
+ *   that code gets mapped to MDB2_ERROR_NOSUCHFIELD.
+ *
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_ERRORS', 16);
+
+/**
+ * Portability: convert empty values to null strings in data output by
+ * query*() and fetch*().
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32);
+
+/**
+ * Portability: removes database/table qualifiers from associative indexes
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64);
+
+/**
+ * Portability: turn on all portability features.
+ * @see MDB2_Driver_Common::setOption()
+ */
+define('MDB2_PORTABILITY_ALL', 127);
+
+// }}}
+// {{{ Globals for class instance tracking
+
+/**
+ * These are global variables that are used to track the various class instances
+ */
+
+$GLOBALS['_MDB2_databases'] = array();
+$GLOBALS['_MDB2_dsninfo_default'] = array(
+    'phptype'  => false,
+    'dbsyntax' => false,
+    'username' => false,
+    'password' => false,
+    'protocol' => false,
+    'hostspec' => false,
+    'port'     => false,
+    'socket'   => false,
+    'database' => false,
+    'mode'     => false,
+);
+
+// }}}
+// {{{ class MDB2
+
+/**
+ * The main 'MDB2' class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ * The object model of MDB2 is as follows (indentation means inheritance):
+ *
+ * MDB2          The main MDB2 class.  This is simply a utility class
+ *              with some 'static' methods for creating MDB2 objects as
+ *              well as common utility functions for other MDB2 classes.
+ *
+ * MDB2_Driver_Common   The base for each MDB2 implementation.  Provides default
+ * |            implementations (in OO lingo virtual methods) for
+ * |            the actual DB implementations as well as a bunch of
+ * |            query utility functions.
+ * |
+ * +-MDB2_Driver_mysql  The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common.
+ *              When calling MDB2::factory or MDB2::connect for MySQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ * +-MDB2_Driver_pgsql  The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common.
+ *              When calling MDB2::factory or MDB2::connect for PostGreSQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2
+{
+    // {{{ function setOptions($db, $options)
+
+    /**
+     * set option array   in an exiting database object
+     *
+     * @param   MDB2_Driver_Common  MDB2 object
+     * @param   array   An associative array of option names and their values.
+     *
+     * @return mixed   MDB2_OK or a PEAR Error object
+     *
+     * @access  public
+     */
+    static function setOptions($db, $options)
+    {
+        if (is_array($options)) {
+            foreach ($options as $option => $value) {
+                $test = $db->setOption($option, $value);
+                if (MDB2::isError($test)) {
+                    return $test;
+                }
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function classExists($classname)
+
+    /**
+     * Checks if a class exists without triggering __autoload
+     *
+     * @param   string  classname
+     *
+     * @return  bool    true success and false on error
+     * @static
+     * @access  public
+     */
+    static function classExists($classname)
+    {
+        return class_exists($classname, false);
+    }
+
+    // }}}
+    // {{{ function loadClass($class_name, $debug)
+
+    /**
+     * Loads a PEAR class.
+     *
+     * @param   string  classname to load
+     * @param   bool    if errors should be suppressed
+     *
+     * @return  mixed   true success or PEAR_Error on failure
+     *
+     * @access  public
+     */
+    static function loadClass($class_name, $debug)
+    {
+        if (!MDB2::classExists($class_name)) {
+            $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
+            if ($debug) {
+                $include = include_once($file_name);
+            } else {
+                $include = @include_once($file_name);
+            }
+            if (!$include) {
+                if (!MDB2::fileExists($file_name)) {
+                    $msg = "unable to find package '$class_name' file '$file_name'";
+                } else {
+                    $msg = "unable to load class '$class_name' from file '$file_name'";
+                }
+                $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg);
+                return $err;
+            }
+            if (!MDB2::classExists($class_name)) {
+                $msg = "unable to load class '$class_name' from file '$file_name'";
+                $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg);
+                return $err;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function factory($dsn, $options = false)
+
+    /**
+     * Create a new MDB2 object for the specified database type
+     *
+     * @param   mixed   'data source name', see the MDB2::parseDSN
+     *                      method for a description of the dsn format.
+     *                      Can also be specified as an array of the
+     *                      format returned by MDB2::parseDSN.
+     * @param   array   An associative array of option names and
+     *                            their values.
+     *
+     * @return  mixed   a newly created MDB2 object, or false on error
+     *
+     * @access  public
+     */
+    static function factory($dsn, $options = false)
+    {
+        $dsninfo = MDB2::parseDSN($dsn);
+        if (empty($dsninfo['phptype'])) {
+            $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND,
+                null, null, 'no RDBMS driver specified');
+            return $err;
+        }
+        $class_name = 'MDB2_Driver_'.$dsninfo['phptype'];
+
+        $debug = (!empty($options['debug']));
+        $err = MDB2::loadClass($class_name, $debug);
+        if (MDB2::isError($err)) {
+            return $err;
+        }
+
+        $db = new $class_name();
+        $db->setDSN($dsninfo);
+        $err = MDB2::setOptions($db, $options);
+        if (MDB2::isError($err)) {
+            return $err;
+        }
+
+        return $db;
+    }
+
+    // }}}
+    // {{{ function connect($dsn, $options = false)
+
+    /**
+     * Create a new MDB2_Driver_* connection object and connect to the specified
+     * database
+     *
+     * @param mixed $dsn     'data source name', see the MDB2::parseDSN
+     *                       method for a description of the dsn format.
+     *                       Can also be specified as an array of the
+     *                       format returned by MDB2::parseDSN.
+     * @param array $options An associative array of option names and
+     *                       their values.
+     *
+     * @return mixed a newly created MDB2 connection object, or a MDB2
+     *               error object on error
+     *
+     * @access  public
+     * @see     MDB2::parseDSN
+     */
+    static function connect($dsn, $options = false)
+    {
+        $db = MDB2::factory($dsn, $options);
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $err = $db->connect();
+        if (MDB2::isError($err)) {
+            $dsn = $db->getDSN('string', 'xxx');
+            $db->disconnect();
+            $err->addUserInfo($dsn);
+            return $err;
+        }
+
+        return $db;
+    }
+
+    // }}}
+    // {{{ function singleton($dsn = null, $options = false)
+
+    /**
+     * Returns a MDB2 connection with the requested DSN.
+     * A new MDB2 connection object is only created if no object with the
+     * requested DSN exists yet.
+     *
+     * @param   mixed   'data source name', see the MDB2::parseDSN
+     *                            method for a description of the dsn format.
+     *                            Can also be specified as an array of the
+     *                            format returned by MDB2::parseDSN.
+     * @param   array   An associative array of option names and
+     *                            their values.
+     *
+     * @return  mixed   a newly created MDB2 connection object, or a MDB2
+     *                  error object on error
+     *
+     * @access  public
+     * @see     MDB2::parseDSN
+     */
+    static function singleton($dsn = null, $options = false)
+    {
+        if ($dsn) {
+            $dsninfo = MDB2::parseDSN($dsn);
+            $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo);
+            $keys = array_keys($GLOBALS['_MDB2_databases']);
+            for ($i=0, $j=count($keys); $i<$j; ++$i) {
+                if (isset($GLOBALS['_MDB2_databases'][$keys[$i]])) {
+                    $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array');
+                    if (count(array_diff_assoc($tmp_dsn, $dsninfo)) == 0) {
+                        MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options);
+                        return $GLOBALS['_MDB2_databases'][$keys[$i]];
+                    }
+                }
+            }
+        } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) {
+            return $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])];
+        }
+        $db = MDB2::factory($dsn, $options);
+        return $db;
+    }
+
+    // }}}
+    // {{{ function areEquals()
+
+    /**
+     * It looks like there's a memory leak in array_diff() in PHP 5.1.x,
+     * so use this method instead.
+     * @see http://pear.php.net/bugs/bug.php?id=11790
+     *
+     * @param array $arr1
+     * @param array $arr2
+     * @return boolean
+     */
+    static function areEquals($arr1, $arr2)
+    {
+        if (count($arr1) != count($arr2)) {
+            return false;
+        }
+        foreach (array_keys($arr1) as $k) {
+            if (!array_key_exists($k, $arr2) || $arr1[$k] != $arr2[$k]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ function loadFile($file)
+
+    /**
+     * load a file (like 'Date')
+     *
+     * @param string $file name of the file in the MDB2 directory (without '.php')
+     *
+     * @return string name of the file that was included
+     *
+     * @access  public
+     */
+    static function loadFile($file)
+    {
+        $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php';
+        if (!MDB2::fileExists($file_name)) {
+            return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'unable to find: '.$file_name);
+        }
+        if (!include_once($file_name)) {
+            return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'unable to load driver class: '.$file_name);
+        }
+        return $file_name;
+    }
+
+    // }}}
+    // {{{ function apiVersion()
+
+    /**
+     * Return the MDB2 API version
+     *
+     * @return  string  the MDB2 API version number
+     *
+     * @access  public
+     */
+    static function apiVersion()
+    {
+        return '2.5.0b5';
+    }
+
+    // }}}
+    // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
+
+    /**
+     * This method is used to communicate an error and invoke error
+     * callbacks etc.  Basically a wrapper for PEAR::raiseError
+     * without the message string.
+     *
+     * @param   mixed  int error code
+     *
+     * @param   int    error mode, see PEAR_Error docs
+     *
+     * @param   mixed  If error mode is PEAR_ERROR_TRIGGER, this is the
+     *                 error level (E_USER_NOTICE etc).  If error mode is
+     *                 PEAR_ERROR_CALLBACK, this is the callback function,
+     *                 either as a function name, or as an array of an
+     *                 object and method name.  For other error modes this
+     *                 parameter is ignored.
+     *
+     * @param   string Extra debug information.  Defaults to the last
+     *                 query and native error code.
+     *
+     * @return PEAR_Error instance of a PEAR Error object
+     *
+     * @access  private
+     * @see     PEAR_Error
+     */
+    public static function &raiseError($code = null,
+                         $mode = null,
+                         $options = null,
+                         $userinfo = null,
+                         $dummy1 = null,
+                         $dummy2 = null,
+                         $dummy3 = false)
+    {
+        $pear = new PEAR;
+        $err =& $pear->raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true);
+        return $err;
+    }
+
+    // }}}
+    // {{{ function isError($data, $code = null)
+
+    /**
+     * Tell whether a value is a MDB2 error.
+     *
+     * @param   mixed   the value to test
+     * @param   int     if is an error object, return true
+     *                        only if $code is a string and
+     *                        $db->getMessage() == $code or
+     *                        $code is an integer and $db->getCode() == $code
+     *
+     * @return  bool    true if parameter is an error
+     *
+     * @access  public
+     */
+    static function isError($data, $code = null)
+    {
+        if ($data instanceof MDB2_Error) {
+            if (null === $code) {
+                return true;
+            }
+            if (is_string($code)) {
+                return $data->getMessage() === $code;
+            }
+            return in_array($data->getCode(), (array)$code);
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ function isConnection($value)
+
+    /**
+     * Tell whether a value is a MDB2 connection
+     *
+     * @param   mixed   value to test
+     *
+     * @return  bool    whether $value is a MDB2 connection
+     * @access  public
+     */
+    static function isConnection($value)
+    {
+        return ($value instanceof MDB2_Driver_Common);
+    }
+
+    // }}}
+    // {{{ function isResult($value)
+
+    /**
+     * Tell whether a value is a MDB2 result
+     *
+     * @param mixed $value value to test
+     *
+     * @return bool whether $value is a MDB2 result
+     *
+     * @access public
+     */
+    static function isResult($value)
+    {
+        return ($value instanceof MDB2_Result);
+    }
+
+    // }}}
+    // {{{ function isResultCommon($value)
+
+    /**
+     * Tell whether a value is a MDB2 result implementing the common interface
+     *
+     * @param mixed $value value to test
+     *
+     * @return bool whether $value is a MDB2 result implementing the common interface
+     *
+     * @access  public
+     */
+    static function isResultCommon($value)
+    {
+        return ($value instanceof MDB2_Result_Common);
+    }
+
+    // }}}
+    // {{{ function isStatement($value)
+
+    /**
+     * Tell whether a value is a MDB2 statement interface
+     *
+     * @param   mixed   value to test
+     *
+     * @return  bool    whether $value is a MDB2 statement interface
+     *
+     * @access  public
+     */
+    static function isStatement($value)
+    {
+        return ($value instanceof MDB2_Statement_Common);
+    }
+
+    // }}}
+    // {{{ function errorMessage($value = null)
+
+    /**
+     * Return a textual error message for a MDB2 error code
+     *
+     * @param   int|array   integer error code,
+                                null to get the current error code-message map,
+                                or an array with a new error code-message map
+     *
+     * @return  string  error message, or false if the error code was
+     *                  not recognized
+     *
+     * @access  public
+     */
+    static function errorMessage($value = null)
+    {
+        static $errorMessages;
+
+        if (is_array($value)) {
+            $errorMessages = $value;
+            return MDB2_OK;
+        }
+
+        if (!isset($errorMessages)) {
+            $errorMessages = array(
+                MDB2_OK                       => 'no error',
+                MDB2_ERROR                    => 'unknown error',
+                MDB2_ERROR_ALREADY_EXISTS     => 'already exists',
+                MDB2_ERROR_CANNOT_CREATE      => 'can not create',
+                MDB2_ERROR_CANNOT_ALTER       => 'can not alter',
+                MDB2_ERROR_CANNOT_REPLACE     => 'can not replace',
+                MDB2_ERROR_CANNOT_DELETE      => 'can not delete',
+                MDB2_ERROR_CANNOT_DROP        => 'can not drop',
+                MDB2_ERROR_CONSTRAINT         => 'constraint violation',
+                MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+                MDB2_ERROR_DIVZERO            => 'division by zero',
+                MDB2_ERROR_INVALID            => 'invalid',
+                MDB2_ERROR_INVALID_DATE       => 'invalid date or time',
+                MDB2_ERROR_INVALID_NUMBER     => 'invalid number',
+                MDB2_ERROR_MISMATCH           => 'mismatch',
+                MDB2_ERROR_NODBSELECTED       => 'no database selected',
+                MDB2_ERROR_NOSUCHFIELD        => 'no such field',
+                MDB2_ERROR_NOSUCHTABLE        => 'no such table',
+                MDB2_ERROR_NOT_CAPABLE        => 'MDB2 backend not capable',
+                MDB2_ERROR_NOT_FOUND          => 'not found',
+                MDB2_ERROR_NOT_LOCKED         => 'not locked',
+                MDB2_ERROR_SYNTAX             => 'syntax error',
+                MDB2_ERROR_UNSUPPORTED        => 'not supported',
+                MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+                MDB2_ERROR_INVALID_DSN        => 'invalid DSN',
+                MDB2_ERROR_CONNECT_FAILED     => 'connect failed',
+                MDB2_ERROR_NEED_MORE_DATA     => 'insufficient data supplied',
+                MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+                MDB2_ERROR_NOSUCHDB           => 'no such database',
+                MDB2_ERROR_ACCESS_VIOLATION   => 'insufficient permissions',
+                MDB2_ERROR_LOADMODULE         => 'error while including on demand module',
+                MDB2_ERROR_TRUNCATED          => 'truncated',
+                MDB2_ERROR_DEADLOCK           => 'deadlock detected',
+                MDB2_ERROR_NO_PERMISSION      => 'no permission',
+                MDB2_ERROR_DISCONNECT_FAILED  => 'disconnect failed',
+            );
+        }
+
+        if (null === $value) {
+            return $errorMessages;
+        }
+
+        if (MDB2::isError($value)) {
+            $value = $value->getCode();
+        }
+
+        return isset($errorMessages[$value]) ?
+           $errorMessages[$value] : $errorMessages[MDB2_ERROR];
+    }
+
+    // }}}
+    // {{{ function parseDSN($dsn)
+
+    /**
+     * Parse a data source name.
+     *
+     * Additional keys can be added by appending a URI query string to the
+     * end of the DSN.
+     *
+     * The format of the supplied DSN is in its fullest form:
+     * <code>
+     *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+     * </code>
+     *
+     * Most variations are allowed:
+     * <code>
+     *  phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+     *  phptype://username:password@hostspec/database_name
+     *  phptype://username:password@hostspec
+     *  phptype://username@hostspec
+     *  phptype://hostspec/database
+     *  phptype://hostspec
+     *  phptype(dbsyntax)
+     *  phptype
+     * </code>
+     *
+     * @param   string  Data Source Name to be parsed
+     *
+     * @return  array   an associative array with the following keys:
+     *  + phptype:  Database backend used in PHP (mysql, odbc etc.)
+     *  + dbsyntax: Database used with regards to SQL syntax etc.
+     *  + protocol: Communication protocol to use (tcp, unix etc.)
+     *  + hostspec: Host specification (hostname[:port])
+     *  + database: Database to use on the DBMS server
+     *  + username: User name for login
+     *  + password: Password for login
+     *
+     * @access  public
+     * @author  Tomas V.V.Cox <cox@idecnet.com>
+     */
+    static function parseDSN($dsn)
+    {
+        $parsed = $GLOBALS['_MDB2_dsninfo_default'];
+
+        if (is_array($dsn)) {
+            $dsn = array_merge($parsed, $dsn);
+            if (!$dsn['dbsyntax']) {
+                $dsn['dbsyntax'] = $dsn['phptype'];
+            }
+            return $dsn;
+        }
+
+        // Find phptype and dbsyntax
+        if (($pos = strpos($dsn, '://')) !== false) {
+            $str = substr($dsn, 0, $pos);
+            $dsn = substr($dsn, $pos + 3);
+        } else {
+            $str = $dsn;
+            $dsn = null;
+        }
+
+        // Get phptype and dbsyntax
+        // $str => phptype(dbsyntax)
+        if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+            $parsed['phptype']  = $arr[1];
+            $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+        } else {
+            $parsed['phptype']  = $str;
+            $parsed['dbsyntax'] = $str;
+        }
+
+        if (!count($dsn)) {
+            return $parsed;
+        }
+
+        // Get (if found): username and password
+        // $dsn => username:password@protocol+hostspec/database
+        if (($at = strrpos($dsn,'@')) !== false) {
+            $str = substr($dsn, 0, $at);
+            $dsn = substr($dsn, $at + 1);
+            if (($pos = strpos($str, ':')) !== false) {
+                $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+                $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+            } else {
+                $parsed['username'] = rawurldecode($str);
+            }
+        }
+
+        // Find protocol and hostspec
+
+        // $dsn => proto(proto_opts)/database
+        if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+            $proto       = $match[1];
+            $proto_opts  = $match[2] ? $match[2] : false;
+            $dsn         = $match[3];
+
+        // $dsn => protocol+hostspec/database (old format)
+        } else {
+            if (strpos($dsn, '+') !== false) {
+                list($proto, $dsn) = explode('+', $dsn, 2);
+            }
+            if (   strpos($dsn, '//') === 0
+                && strpos($dsn, '/', 2) !== false
+                && $parsed['phptype'] == 'oci8'
+            ) {
+                //oracle's "Easy Connect" syntax:
+                //"username/password@[//]host[:port][/service_name]"
+                //e.g. "scott/tiger@//mymachine:1521/oracle"
+                $proto_opts = $dsn;
+                $pos = strrpos($proto_opts, '/');
+                $dsn = substr($proto_opts, $pos + 1);
+                $proto_opts = substr($proto_opts, 0, $pos);
+            } elseif (strpos($dsn, '/') !== false) {
+                list($proto_opts, $dsn) = explode('/', $dsn, 2);
+            } else {
+                $proto_opts = $dsn;
+                $dsn = null;
+            }
+        }
+
+        // process the different protocol options
+        $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+        $proto_opts = rawurldecode($proto_opts);
+        if (strpos($proto_opts, ':') !== false) {
+            list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
+        }
+        if ($parsed['protocol'] == 'tcp') {
+            $parsed['hostspec'] = $proto_opts;
+        } elseif ($parsed['protocol'] == 'unix') {
+            $parsed['socket'] = $proto_opts;
+        }
+
+        // Get dabase if any
+        // $dsn => database
+        if ($dsn) {
+            // /database
+            if (($pos = strpos($dsn, '?')) === false) {
+                $parsed['database'] = rawurldecode($dsn);
+            // /database?param1=value1&param2=value2
+            } else {
+                $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+                $dsn = substr($dsn, $pos + 1);
+                if (strpos($dsn, '&') !== false) {
+                    $opts = explode('&', $dsn);
+                } else { // database?param1=value1
+                    $opts = array($dsn);
+                }
+                foreach ($opts as $opt) {
+                    list($key, $value) = explode('=', $opt);
+                    if (!array_key_exists($key, $parsed) || false === $parsed[$key]) {
+                        // don't allow params overwrite
+                        $parsed[$key] = rawurldecode($value);
+                    }
+                }
+            }
+        }
+
+        return $parsed;
+    }
+
+    // }}}
+    // {{{ function fileExists($file)
+
+    /**
+     * Checks if a file exists in the include path
+     *
+     * @param   string  filename
+     *
+     * @return  bool    true success and false on error
+     *
+     * @access  public
+     */
+    static function fileExists($file)
+    {
+        // safe_mode does notwork with is_readable()
+        if (!@ini_get('safe_mode')) {
+             $dirs = explode(PATH_SEPARATOR, ini_get('include_path'));
+             foreach ($dirs as $dir) {
+                 if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
+                     return true;
+                 }
+            }
+        } else {
+            $fp = @fopen($file, 'r', true);
+            if (is_resource($fp)) {
+                @fclose($fp);
+                return true;
+            }
+        }
+        return false;
+    }
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Error extends PEAR_Error
+
+/**
+ * MDB2_Error implements a class for reporting portable database error
+ * messages.
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author Stig Bakken <ssb@fast.no>
+ */
+class MDB2_Error extends PEAR_Error
+{
+    // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null)
+
+    /**
+     * MDB2_Error constructor.
+     *
+     * @param   mixed   MDB2 error code, or string with error message.
+     * @param   int     what 'error mode' to operate in
+     * @param   int     what error level to use for $mode & PEAR_ERROR_TRIGGER
+     * @param   mixed   additional debug info, such as the last query
+     */
+    function __construct($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN,
+              $level = E_USER_NOTICE, $debuginfo = null, $dummy = null)
+    {
+        if (null === $code) {
+            $code = MDB2_ERROR;
+        }
+        $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code,
+            $mode, $level, $debuginfo);
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Driver_Common extends PEAR
+
+/**
+ * MDB2_Driver_Common: Base class that is extended by each MDB2 driver
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Common
+{
+    // {{{ Variables (Properties)
+
+    /**
+     * @var MDB2_Driver_Datatype_Common
+     */
+    public $datatype;
+
+    /**
+     * @var MDB2_Extended
+     */
+    public $extended;
+
+    /**
+     * @var MDB2_Driver_Function_Common
+     */
+    public $function;
+
+    /**
+     * @var MDB2_Driver_Manager_Common
+     */
+    public $manager;
+
+    /**
+     * @var MDB2_Driver_Native_Commonn
+     */
+    public $native;
+
+    /**
+     * @var MDB2_Driver_Reverse_Common
+     */
+    public $reverse;
+
+    /**
+     * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array
+     * @var     int
+     * @access  public
+     */
+    public $db_index = 0;
+
+    /**
+     * DSN used for the next query
+     * @var     array
+     * @access  protected
+     */
+    public $dsn = array();
+
+    /**
+     * DSN that was used to create the current connection
+     * @var     array
+     * @access  protected
+     */
+    public $connected_dsn = array();
+
+    /**
+     * connection resource
+     * @var     mixed
+     * @access  protected
+     */
+    public $connection = 0;
+
+    /**
+     * if the current opened connection is a persistent connection
+     * @var     bool
+     * @access  protected
+     */
+    public $opened_persistent;
+
+    /**
+     * the name of the database for the next query
+     * @var     string
+     * @access  public
+     */
+    public $database_name = '';
+
+    /**
+     * the name of the database currently selected
+     * @var     string
+     * @access  protected
+     */
+    public $connected_database_name = '';
+
+    /**
+     * server version information
+     * @var     string
+     * @access  protected
+     */
+    public $connected_server_info = '';
+
+    /**
+     * list of all supported features of the given driver
+     * @var     array
+     * @access  public
+     */
+    public $supported = array(
+        'sequences' => false,
+        'indexes' => false,
+        'affected_rows' => false,
+        'summary_functions' => false,
+        'order_by_text' => false,
+        'transactions' => false,
+        'savepoints' => false,
+        'current_id' => false,
+        'limit_queries' => false,
+        'LOBs' => false,
+        'replace' => false,
+        'sub_selects' => false,
+        'triggers' => false,
+        'auto_increment' => false,
+        'primary_key' => false,
+        'result_introspection' => false,
+        'prepared_statements' => false,
+        'identifier_quoting' => false,
+        'pattern_escaping' => false,
+        'new_link' => false,
+    );
+
+    /**
+     * Array of supported options that can be passed to the MDB2 instance.
+     *
+     * The options can be set during object creation, using
+     * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can
+     * also be set after the object is created, using MDB2::setOptions() or
+     * MDB2_Driver_Common::setOption().
+     * The list of available option includes:
+     * <ul>
+     *  <li>$options['ssl'] -> boolean: determines if ssl should be used for connections</li>
+     *  <li>$options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names</li>
+     *  <li>$options['disable_query'] -> boolean: determines if queries should be executed</li>
+     *  <li>$options['result_class'] -> string: class used for result sets</li>
+     *  <li>$options['buffered_result_class'] -> string: class used for buffered result sets</li>
+     *  <li>$options['result_wrap_class'] -> string: class used to wrap result sets into</li>
+     *  <li>$options['result_buffering'] -> boolean should results be buffered or not?</li>
+     *  <li>$options['fetch_class'] -> string: class to use when fetch mode object is used</li>
+     *  <li>$options['persistent'] -> boolean: persistent connection?</li>
+     *  <li>$options['debug'] -> integer: numeric debug level</li>
+     *  <li>$options['debug_handler'] -> string: function/method that captures debug messages</li>
+     *  <li>$options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler</li>
+     *  <li>$options['default_text_field_length'] -> integer: default text field length to use</li>
+     *  <li>$options['lob_buffer_length'] -> integer: LOB buffer length</li>
+     *  <li>$options['log_line_break'] -> string: line-break format</li>
+     *  <li>$options['idxname_format'] -> string: pattern for index name</li>
+     *  <li>$options['seqname_format'] -> string: pattern for sequence name</li>
+     *  <li>$options['savepoint_format'] -> string: pattern for auto generated savepoint names</li>
+     *  <li>$options['statement_format'] -> string: pattern for prepared statement names</li>
+     *  <li>$options['seqcol_name'] -> string: sequence column name</li>
+     *  <li>$options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used</li>
+     *  <li>$options['use_transactions'] -> boolean: if transaction use should be enabled</li>
+     *  <li>$options['decimal_places'] -> integer: number of decimal places to handle</li>
+     *  <li>$options['portability'] -> integer: portability constant</li>
+     *  <li>$options['modules'] -> array: short to long module name mapping for __call()</li>
+     *  <li>$options['emulate_prepared'] -> boolean: force prepared statements to be emulated</li>
+     *  <li>$options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes</li>
+     *  <li>$options['datatype_map_callback'] -> array: callback function/method that should be called</li>
+     *  <li>$options['bindname_format'] -> string: regular expression pattern for named parameters</li>
+     *  <li>$options['multi_query'] -> boolean: determines if queries returning multiple result sets should be executed</li>
+     *  <li>$options['max_identifiers_length'] -> integer: max identifier length</li>
+     *  <li>$options['default_fk_action_onupdate'] -> string: default FOREIGN KEY ON UPDATE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']</li>
+     *  <li>$options['default_fk_action_ondelete'] -> string: default FOREIGN KEY ON DELETE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']</li>
+     * </ul>
+     *
+     * @var     array
+     * @access  public
+     * @see     MDB2::connect()
+     * @see     MDB2::factory()
+     * @see     MDB2::singleton()
+     * @see     MDB2_Driver_Common::setOption()
+     */
+    public $options = array(
+        'ssl' => false,
+        'field_case' => CASE_LOWER,
+        'disable_query' => false,
+        'result_class' => 'MDB2_Result_%s',
+        'buffered_result_class' => 'MDB2_BufferedResult_%s',
+        'result_wrap_class' => false,
+        'result_buffering' => true,
+        'fetch_class' => 'stdClass',
+        'persistent' => false,
+        'debug' => 0,
+        'debug_handler' => 'MDB2_defaultDebugOutput',
+        'debug_expanded_output' => false,
+        'default_text_field_length' => 4096,
+        'lob_buffer_length' => 8192,
+        'log_line_break' => "\n",
+        'idxname_format' => '%s_idx',
+        'seqname_format' => '%s_seq',
+        'savepoint_format' => 'MDB2_SAVEPOINT_%s',
+        'statement_format' => 'MDB2_STATEMENT_%1$s_%2$s',
+        'seqcol_name' => 'sequence',
+        'quote_identifier' => false,
+        'use_transactions' => true,
+        'decimal_places' => 2,
+        'portability' => MDB2_PORTABILITY_ALL,
+        'modules' => array(
+            'ex' => 'Extended',
+            'dt' => 'Datatype',
+            'mg' => 'Manager',
+            'rv' => 'Reverse',
+            'na' => 'Native',
+            'fc' => 'Function',
+        ),
+        'emulate_prepared' => false,
+        'datatype_map' => array(),
+        'datatype_map_callback' => array(),
+        'nativetype_map_callback' => array(),
+        'lob_allow_url_include' => false,
+        'bindname_format' => '(?:\d+)|(?:[a-zA-Z][a-zA-Z0-9_]*)',
+        'max_identifiers_length' => 30,
+        'default_fk_action_onupdate' => 'RESTRICT',
+        'default_fk_action_ondelete' => 'RESTRICT',
+    );
+
+    /**
+     * string array
+     * @var     string
+     * @access  public
+     */
+    public $string_quoting = array(
+        'start'  => "'",
+        'end'    => "'",
+        'escape' => false,
+        'escape_pattern' => false,
+    );
+
+    /**
+     * identifier quoting
+     * @var     array
+     * @access  public
+     */
+    public $identifier_quoting = array(
+        'start'  => '"',
+        'end'    => '"',
+        'escape' => '"',
+    );
+
+    /**
+     * sql comments
+     * @var     array
+     * @access  protected
+     */
+    public $sql_comments = array(
+        array('start' => '--', 'end' => "\n", 'escape' => false),
+        array('start' => '/*', 'end' => '*/', 'escape' => false),
+    );
+
+    /**
+     * comparision wildcards
+     * @var     array
+     * @access  protected
+     */
+    protected $wildcards = array('%', '_');
+
+    /**
+     * column alias keyword
+     * @var     string
+     * @access  protected
+     */
+    public $as_keyword = ' AS ';
+
+    /**
+     * warnings
+     * @var     array
+     * @access  protected
+     */
+    public $warnings = array();
+
+    /**
+     * string with the debugging information
+     * @var     string
+     * @access  public
+     */
+    public $debug_output = '';
+
+    /**
+     * determine if there is an open transaction
+     * @var     bool
+     * @access  protected
+     */
+    public $in_transaction = false;
+
+    /**
+     * the smart transaction nesting depth
+     * @var     int
+     * @access  protected
+     */
+    public $nested_transaction_counter = null;
+
+    /**
+     * the first error that occured inside a nested transaction
+     * @var     MDB2_Error|bool
+     * @access  protected
+     */
+    protected $has_transaction_error = false;
+
+    /**
+     * result offset used in the next query
+     * @var     int
+     * @access  public
+     */
+    public $offset = 0;
+
+    /**
+     * result limit used in the next query
+     * @var     int
+     * @access  public
+     */
+    public $limit = 0;
+
+    /**
+     * Database backend used in PHP (mysql, odbc etc.)
+     * @var     string
+     * @access  public
+     */
+    public $phptype;
+
+    /**
+     * Database used with regards to SQL syntax etc.
+     * @var     string
+     * @access  public
+     */
+    public $dbsyntax;
+
+    /**
+     * the last query sent to the driver
+     * @var     string
+     * @access  public
+     */
+    public $last_query;
+
+    /**
+     * the default fetchmode used
+     * @var     int
+     * @access  public
+     */
+    public $fetchmode = MDB2_FETCHMODE_ORDERED;
+
+    /**
+     * array of module instances
+     * @var     array
+     * @access  protected
+     */
+    protected $modules = array();
+
+    /**
+     * determines of the PHP4 destructor emulation has been enabled yet
+     * @var     array
+     * @access  protected
+     */
+    protected $destructor_registered = true;
+
+    /**
+     * @var PEAR 
+     */
+    protected $pear;
+
+    // }}}
+    // {{{ constructor: function __construct()
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+        end($GLOBALS['_MDB2_databases']);
+        $db_index = key($GLOBALS['_MDB2_databases']) + 1;
+        $GLOBALS['_MDB2_databases'][$db_index] = &$this;
+        $this->db_index = $db_index;
+        $this->pear = new PEAR;
+    }
+
+    // }}}
+    // {{{ destructor: function __destruct()
+
+    /**
+     *  Destructor
+     */
+    function __destruct()
+    {
+        $this->disconnect(false);
+    }
+
+    // }}}
+    // {{{ function free()
+
+    /**
+     * Free the internal references so that the instance can be destroyed
+     *
+     * @return  bool    true on success, false if result is invalid
+     *
+     * @access  public
+     */
+    function free()
+    {
+        unset($GLOBALS['_MDB2_databases'][$this->db_index]);
+        unset($this->db_index);
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function __toString()
+
+    /**
+     * String conversation
+     *
+     * @return  string representation of the object
+     *
+     * @access  public
+     */
+    function __toString()
+    {
+        $info = get_class($this);
+        $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')';
+        if ($this->connection) {
+            $info.= ' [connected]';
+        }
+        return $info;
+    }
+
+    // }}}
+    // {{{ function errorInfo($error = null)
+
+    /**
+     * This method is used to collect information about an error
+     *
+     * @param   mixed   error code or resource
+     *
+     * @return  array   with MDB2 errorcode, native error code, native message
+     *
+     * @access  public
+     */
+    function errorInfo($error = null)
+    {
+        return array($error, null, null);
+    }
+
+    // }}}
+    // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
+
+    /**
+     * This method is used to communicate an error and invoke error
+     * callbacks etc.  Basically a wrapper for PEAR::raiseError
+     * without the message string.
+     *
+     * @param mixed  $code     integer error code, or a PEAR error object (all
+     *                         other parameters are ignored if this parameter is
+     *                         an object
+     * @param int    $mode     error mode, see PEAR_Error docs
+     * @param mixed  $options  If error mode is PEAR_ERROR_TRIGGER, this is the
+     *                         error level (E_USER_NOTICE etc). If error mode is
+     *                         PEAR_ERROR_CALLBACK, this is the callback function,
+     *                         either as a function name, or as an array of an
+     *                         object and method name. For other error modes this
+     *                         parameter is ignored.
+     * @param string $userinfo Extra debug information. Defaults to the last
+     *                         query and native error code.
+     * @param string $method   name of the method that triggered the error
+     * @param string $dummy1   not used
+     * @param bool   $dummy2   not used
+     *
+     * @return PEAR_Error instance of a PEAR Error object
+     * @access public
+     * @see    PEAR_Error
+     */
+    function &raiseError($code = null,
+                         $mode = null,
+                         $options = null,
+                         $userinfo = null,
+                         $method = null,
+                         $dummy1 = null,
+                         $dummy2 = false
+    ) {
+        $userinfo = "[Error message: $userinfo]\n";
+        // The error is yet a MDB2 error object
+        if (MDB2::isError($code)) {
+            // because we use the static PEAR::raiseError, our global
+            // handler should be used if it is set
+            if ((null === $mode) && !empty($this->_default_error_mode)) {
+                $mode    = $this->_default_error_mode;
+                $options = $this->_default_error_options;
+            }
+            if (null === $userinfo) {
+                $userinfo = $code->getUserinfo();
+            }
+            $code = $code->getCode();
+        } elseif ($code == MDB2_ERROR_NOT_FOUND) {
+            // extension not loaded: don't call $this->errorInfo() or the script
+            // will die
+        } elseif (isset($this->connection)) {
+            if (!empty($this->last_query)) {
+                $userinfo.= "[Last executed query: {$this->last_query}]\n";
+            }
+            $native_errno = $native_msg = null;
+            list($code, $native_errno, $native_msg) = $this->errorInfo($code);
+            if ((null !== $native_errno) && $native_errno !== '') {
+                $userinfo.= "[Native code: $native_errno]\n";
+            }
+            if ((null !== $native_msg) && $native_msg !== '') {
+                $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n";
+            }
+            if (null !== $method) {
+                $userinfo = $method.': '.$userinfo;
+            }
+        }
+
+        $err = $this->pear->raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true);
+        if ($err->getMode() !== PEAR_ERROR_RETURN
+            && isset($this->nested_transaction_counter) && !$this->has_transaction_error) {
+            $this->has_transaction_error = $err;
+        }
+        return $err;
+    }
+
+    // }}}
+    // {{{ function resetWarnings()
+
+    /**
+     * reset the warning array
+     *
+     * @return void
+     *
+     * @access  public
+     */
+    function resetWarnings()
+    {
+        $this->warnings = array();
+    }
+
+    // }}}
+    // {{{ function getWarnings()
+
+    /**
+     * Get all warnings in reverse order.
+     * This means that the last warning is the first element in the array
+     *
+     * @return  array   with warnings
+     *
+     * @access  public
+     * @see     resetWarnings()
+     */
+    function getWarnings()
+    {
+        return array_reverse($this->warnings);
+    }
+
+    // }}}
+    // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass')
+
+    /**
+     * Sets which fetch mode should be used by default on queries
+     * on this connection
+     *
+     * @param   int     MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC
+     *                               or MDB2_FETCHMODE_OBJECT
+     * @param   string  the class name of the object to be returned
+     *                               by the fetch methods when the
+     *                               MDB2_FETCHMODE_OBJECT mode is selected.
+     *                               If no class is specified by default a cast
+     *                               to object from the assoc array row will be
+     *                               done.  There is also the possibility to use
+     *                               and extend the 'MDB2_row' class.
+     *
+     * @return  mixed   MDB2_OK or MDB2 Error Object
+     *
+     * @access  public
+     * @see     MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT
+     */
+    function setFetchMode($fetchmode, $object_class = 'stdClass')
+    {
+        switch ($fetchmode) {
+        case MDB2_FETCHMODE_OBJECT:
+            $this->options['fetch_class'] = $object_class;
+        case MDB2_FETCHMODE_ORDERED:
+        case MDB2_FETCHMODE_ASSOC:
+            $this->fetchmode = $fetchmode;
+            break;
+        default:
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'invalid fetchmode mode', __FUNCTION__);
+        }
+
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function setOption($option, $value)
+
+    /**
+     * set the option for the db class
+     *
+     * @param   string  option name
+     * @param   mixed   value for the option
+     *
+     * @return  mixed   MDB2_OK or MDB2 Error Object
+     *
+     * @access  public
+     */
+    function setOption($option, $value)
+    {
+        if (array_key_exists($option, $this->options)) {
+            $this->options[$option] = $value;
+            return MDB2_OK;
+        }
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            "unknown option $option", __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function getOption($option)
+
+    /**
+     * Returns the value of an option
+     *
+     * @param   string  option name
+     *
+     * @return  mixed   the option value or error object
+     *
+     * @access  public
+     */
+    function getOption($option)
+    {
+        if (array_key_exists($option, $this->options)) {
+            return $this->options[$option];
+        }
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            "unknown option $option", __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function debug($message, $scope = '', $is_manip = null)
+
+    /**
+     * set a debug message
+     *
+     * @param   string  message that should be appended to the debug variable
+     * @param   string  usually the method name that triggered the debug call:
+     *                  for example 'query', 'prepare', 'execute', 'parameters',
+     *                  'beginTransaction', 'commit', 'rollback'
+     * @param   array   contains context information about the debug() call
+     *                  common keys are: is_manip, time, result etc.
+     *
+     * @return void
+     *
+     * @access  public
+     */
+    function debug($message, $scope = '', $context = array())
+    {
+        if ($this->options['debug'] && $this->options['debug_handler']) {
+            if (!$this->options['debug_expanded_output']) {
+                if (!empty($context['when']) && $context['when'] !== 'pre') {
+                    return null;
+                }
+                $context = empty($context['is_manip']) ? false : $context['is_manip'];
+            }
+            return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context));
+        }
+        return null;
+    }
+
+    // }}}
+    // {{{ function getDebugOutput()
+
+    /**
+     * output debug info
+     *
+     * @return  string  content of the debug_output class variable
+     *
+     * @access  public
+     */
+    function getDebugOutput()
+    {
+        return $this->debug_output;
+    }
+
+    // }}}
+    // {{{ function escape($text)
+
+    /**
+     * Quotes a string so it can be safely used in a query. It will quote
+     * the text so it can safely be used within a query.
+     *
+     * @param   string  the input string to quote
+     * @param   bool    escape wildcards
+     *
+     * @return  string  quoted string
+     *
+     * @access  public
+     */
+    function escape($text, $escape_wildcards = false)
+    {
+        if ($escape_wildcards) {
+            $text = $this->escapePattern($text);
+        }
+
+        $text = str_replace($this->string_quoting['end'], $this->string_quoting['escape'] . $this->string_quoting['end'], $text);
+        return $text;
+    }
+
+    // }}}
+    // {{{ function escapePattern($text)
+
+    /**
+     * Quotes pattern (% and _) characters in a string)
+     *
+     * @param   string  the input string to quote
+     *
+     * @return  string  quoted string
+     *
+     * @access  public
+     */
+    function escapePattern($text)
+    {
+        if ($this->string_quoting['escape_pattern']) {
+            $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text);
+            foreach ($this->wildcards as $wildcard) {
+                $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text);
+            }
+        }
+        return $text;
+    }
+
+    // }}}
+    // {{{ function quoteIdentifier($str, $check_option = false)
+
+    /**
+     * Quote a string so it can be safely used as a table or column name
+     *
+     * Delimiting style depends on which database driver is being used.
+     *
+     * NOTE: just because you CAN use delimited identifiers doesn't mean
+     * you SHOULD use them.  In general, they end up causing way more
+     * problems than they solve.
+     *
+     * NOTE: if you have table names containing periods, don't use this method
+     * (@see bug #11906)
+     *
+     * Portability is broken by using the following characters inside
+     * delimited identifiers:
+     *   + backtick (<kbd>`</kbd>) -- due to MySQL
+     *   + double quote (<kbd>"</kbd>) -- due to Oracle
+     *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
+     *
+     * Delimited identifiers are known to generally work correctly under
+     * the following drivers:
+     *   + mssql
+     *   + mysql
+     *   + mysqli
+     *   + oci8
+     *   + pgsql
+     *   + sqlite
+     *
+     * InterBase doesn't seem to be able to use delimited identifiers
+     * via PHP 4.  They work fine under PHP 5.
+     *
+     * @param   string  identifier name to be quoted
+     * @param   bool    check the 'quote_identifier' option
+     *
+     * @return  string  quoted identifier string
+     *
+     * @access  public
+     */
+    function quoteIdentifier($str, $check_option = false)
+    {
+        if ($check_option && !$this->options['quote_identifier']) {
+            return $str;
+        }
+        $str = str_replace($this->identifier_quoting['end'], $this->identifier_quoting['escape'] . $this->identifier_quoting['end'], $str);
+        $parts = explode('.', $str);
+        foreach (array_keys($parts) as $k) {
+            $parts[$k] = $this->identifier_quoting['start'] . $parts[$k] . $this->identifier_quoting['end'];
+        }
+        return implode('.', $parts);
+    }
+
+    // }}}
+    // {{{ function getAsKeyword()
+
+    /**
+     * Gets the string to alias column
+     *
+     * @return string to use when aliasing a column
+     */
+    function getAsKeyword()
+    {
+        return $this->as_keyword;
+    }
+
+    // }}}
+    // {{{ function getConnection()
+
+    /**
+     * Returns a native connection
+     *
+     * @return  mixed   a valid MDB2 connection object,
+     *                  or a MDB2 error object on error
+     *
+     * @access  public
+     */
+    function getConnection()
+    {
+        $result = $this->connect();
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $this->connection;
+    }
+
+     // }}}
+    // {{{ function _fixResultArrayValues(&$row, $mode)
+
+    /**
+     * Do all necessary conversions on result arrays to fix DBMS quirks
+     *
+     * @param   array   the array to be fixed (passed by reference)
+     * @param   array   bit-wise addition of the required portability modes
+     *
+     * @return  void
+     *
+     * @access  protected
+     */
+    function _fixResultArrayValues(&$row, $mode)
+    {
+        switch ($mode) {
+        case MDB2_PORTABILITY_EMPTY_TO_NULL:
+            foreach ($row as $key => $value) {
+                if ($value === '') {
+                    $row[$key] = null;
+                }
+            }
+            break;
+        case MDB2_PORTABILITY_RTRIM:
+            foreach ($row as $key => $value) {
+                if (is_string($value)) {
+                    $row[$key] = rtrim($value);
+                }
+            }
+            break;
+        case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES:
+            $tmp_row = array();
+            foreach ($row as $key => $value) {
+                $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
+            }
+            $row = $tmp_row;
+            break;
+        case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL):
+            foreach ($row as $key => $value) {
+                if ($value === '') {
+                    $row[$key] = null;
+                } elseif (is_string($value)) {
+                    $row[$key] = rtrim($value);
+                }
+            }
+            break;
+        case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
+            $tmp_row = array();
+            foreach ($row as $key => $value) {
+                if (is_string($value)) {
+                    $value = rtrim($value);
+                }
+                $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
+            }
+            $row = $tmp_row;
+            break;
+        case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
+            $tmp_row = array();
+            foreach ($row as $key => $value) {
+                if ($value === '') {
+                    $value = null;
+                }
+                $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
+            }
+            $row = $tmp_row;
+            break;
+        case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
+            $tmp_row = array();
+            foreach ($row as $key => $value) {
+                if ($value === '') {
+                    $value = null;
+                } elseif (is_string($value)) {
+                    $value = rtrim($value);
+                }
+                $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
+            }
+            $row = $tmp_row;
+            break;
+        }
+    }
+
+    // }}}
+    // {{{ function loadModule($module, $property = null, $phptype_specific = null)
+
+    /**
+     * loads a module
+     *
+     * @param   string  name of the module that should be loaded
+     *                  (only used for error messages)
+     * @param   string  name of the property into which the class will be loaded
+     * @param   bool    if the class to load for the module is specific to the
+     *                  phptype
+     *
+     * @return  object  on success a reference to the given module is returned
+     *                  and on failure a PEAR error
+     *
+     * @access  public
+     */
+    function loadModule($module, $property = null, $phptype_specific = null)
+    {
+        if (!$property) {
+            $property = strtolower($module);
+        }
+
+        if (!isset($this->{$property})) {
+            $version = $phptype_specific;
+            if ($phptype_specific !== false) {
+                $version = true;
+                $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype;
+                $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
+            }
+            if ($phptype_specific === false
+                || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name))
+            ) {
+                $version = false;
+                $class_name = 'MDB2_'.$module;
+                $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
+            }
+
+            $err = MDB2::loadClass($class_name, $this->getOption('debug'));
+            if (MDB2::isError($err)) {
+                return $err;
+            }
+
+            // load module in a specific version
+            if ($version) {
+                if (method_exists($class_name, 'getClassName')) {
+                    $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index);
+                    if ($class_name != $class_name_new) {
+                        $class_name = $class_name_new;
+                        $err = MDB2::loadClass($class_name, $this->getOption('debug'));
+                        if (MDB2::isError($err)) {
+                            return $err;
+                        }
+                    }
+                }
+            }
+
+            if (!MDB2::classExists($class_name)) {
+                $err = $this->raiseError(MDB2_ERROR_LOADMODULE, null, null,
+                    "unable to load module '$module' into property '$property'", __FUNCTION__);
+                return $err;
+            }
+            $this->{$property} = new $class_name($this->db_index);
+            $this->modules[$module] = $this->{$property};
+            if ($version) {
+                // this will be used in the connect method to determine if the module
+                // needs to be loaded with a different version if the server
+                // version changed in between connects
+                $this->loaded_version_modules[] = $property;
+            }
+        }
+
+        return $this->{$property};
+    }
+
+    // }}}
+    // {{{ function __call($method, $params)
+
+    /**
+     * Calls a module method using the __call magic method
+     *
+     * @param   string  Method name.
+     * @param   array   Arguments.
+     *
+     * @return  mixed   Returned value.
+     */
+    function __call($method, $params)
+    {
+        $module = null;
+        if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match)
+            && isset($this->options['modules'][$match[1]])
+        ) {
+            $module = $this->options['modules'][$match[1]];
+            $method = strtolower($match[2]).$match[3];
+            if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) {
+                $result = $this->loadModule($module);
+                if (MDB2::isError($result)) {
+                    return $result;
+                }
+            }
+        } else {
+            foreach ($this->modules as $key => $foo) {
+                if (is_object($this->modules[$key])
+                    && method_exists($this->modules[$key], $method)
+                ) {
+                    $module = $key;
+                    break;
+                }
+            }
+        }
+        if (null !== $module) {
+            return call_user_func_array(array(&$this->modules[$module], $method), $params);
+        }
+
+        $class = get_class($this);
+        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+        $loc = 'in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'];
+        if ($method == 'isError') {
+            trigger_error("Deprecated: $class::isError() is deprecated, use MDB2::isError() $loc", E_USER_DEPRECATED);
+            if (!array_key_exists(0, $params)) {
+                trigger_error("Missing argument 1 for $class::$method, called $loc", E_USER_ERROR);
+            }
+            return MDB2::isError($params[0]);
+        }
+        trigger_error("Call to undefined function: $class::$method() $loc.", E_USER_ERROR);
+    }
+
+    // }}}
+    // {{{ function __callStatic($method, $params)
+
+    /**
+     * Calls a module method using the __callStatic magic method
+     *
+     * @param   string  Method name.
+     * @param   array   Arguments.
+     *
+     * @return  mixed   Returned value.
+     */
+    public static function __callStatic($method, $params)
+    {
+        $class = get_called_class();
+        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+        $loc = 'in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'];
+        if ($method == 'isError') {
+            trigger_error("Deprecated: $class::isError() is deprecated, use MDB2::isError() $loc", E_USER_DEPRECATED);
+            if (!array_key_exists(0, $params)) {
+                trigger_error("Missing argument 1 for $class::$method, called $loc", E_USER_ERROR);
+            }
+            return MDB2::isError($params[0]);
+        }
+        trigger_error("Call to undefined function: $class::$method() $loc.", E_USER_ERROR);
+    }
+
+    // }}}
+    // {{{ function beginTransaction($savepoint = null)
+
+    /**
+     * Start a transaction or set a savepoint.
+     *
+     * @param   string  name of a savepoint to set
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function beginTransaction($savepoint = null)
+    {
+        $this->debug('Starting transaction', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'transactions are not supported', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function commit($savepoint = null)
+
+    /**
+     * Commit the database changes done during a transaction that is in
+     * progress or release a savepoint. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after committing the pending changes.
+     *
+     * @param   string  name of a savepoint to release
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function commit($savepoint = null)
+    {
+        $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'commiting transactions is not supported', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function rollback($savepoint = null)
+
+    /**
+     * Cancel any database changes done during a transaction or since a specific
+     * savepoint that is in progress. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after canceling the pending changes.
+     *
+     * @param   string  name of a savepoint to rollback to
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function rollback($savepoint = null)
+    {
+        $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'rolling back transactions is not supported', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function inTransaction($ignore_nested = false)
+
+    /**
+     * If a transaction is currently open.
+     *
+     * @param   bool    if the nested transaction count should be ignored
+     * @return  int|bool    - an integer with the nesting depth is returned if a
+     *                      nested transaction is open
+     *                      - true is returned for a normal open transaction
+     *                      - false is returned if no transaction is open
+     *
+     * @access  public
+     */
+    function inTransaction($ignore_nested = false)
+    {
+        if (!$ignore_nested && isset($this->nested_transaction_counter)) {
+            return $this->nested_transaction_counter;
+        }
+        return $this->in_transaction;
+    }
+
+    // }}}
+    // {{{ function setTransactionIsolation($isolation)
+
+    /**
+     * Set the transacton isolation level.
+     *
+     * @param   string  standard isolation level
+     *                  READ UNCOMMITTED (allows dirty reads)
+     *                  READ COMMITTED (prevents dirty reads)
+     *                  REPEATABLE READ (prevents nonrepeatable reads)
+     *                  SERIALIZABLE (prevents phantom reads)
+     * @param   array some transaction options:
+     *                  'wait' => 'WAIT' | 'NO WAIT'
+     *                  'rw'   => 'READ WRITE' | 'READ ONLY'
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function setTransactionIsolation($isolation, $options = array())
+    {
+        $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'isolation level setting is not supported', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function beginNestedTransaction($savepoint = false)
+
+    /**
+     * Start a nested transaction.
+     *
+     * @return  mixed   MDB2_OK on success/savepoint name, a MDB2 error on failure
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function beginNestedTransaction()
+    {
+        if ($this->in_transaction) {
+            ++$this->nested_transaction_counter;
+            $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter);
+            if ($this->supports('savepoints') && $savepoint) {
+                return $this->beginTransaction($savepoint);
+            }
+            return MDB2_OK;
+        }
+        $this->has_transaction_error = false;
+        $result = $this->beginTransaction();
+        $this->nested_transaction_counter = 1;
+        return $result;
+    }
+
+    // }}}
+    // {{{ function completeNestedTransaction($force_rollback = false, $release = false)
+
+    /**
+     * Finish a nested transaction by rolling back if an error occured or
+     * committing otherwise.
+     *
+     * @param   bool    if the transaction should be rolled back regardless
+     *                  even if no error was set within the nested transaction
+     * @return  mixed   MDB_OK on commit/counter decrementing, false on rollback
+     *                  and a MDB2 error on failure
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function completeNestedTransaction($force_rollback = false)
+    {
+        if ($this->nested_transaction_counter > 1) {
+            $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter);
+            if ($this->supports('savepoints') && $savepoint) {
+                if ($force_rollback || $this->has_transaction_error) {
+                    $result = $this->rollback($savepoint);
+                    if (!MDB2::isError($result)) {
+                        $result = false;
+                        $this->has_transaction_error = false;
+                    }
+                } else {
+                    $result = $this->commit($savepoint);
+                }
+            } else {
+                $result = MDB2_OK;
+            }
+            --$this->nested_transaction_counter;
+            return $result;
+        }
+
+        $this->nested_transaction_counter = null;
+        $result = MDB2_OK;
+
+        // transaction has not yet been rolled back
+        if ($this->in_transaction) {
+            if ($force_rollback || $this->has_transaction_error) {
+                $result = $this->rollback();
+                if (!MDB2::isError($result)) {
+                    $result = false;
+                }
+            } else {
+                $result = $this->commit();
+            }
+        }
+        $this->has_transaction_error = false;
+        return $result;
+    }
+
+    // }}}
+    // {{{ function failNestedTransaction($error = null, $immediately = false)
+
+    /**
+     * Force setting nested transaction to failed.
+     *
+     * @param   mixed   value to return in getNestededTransactionError()
+     * @param   bool    if the transaction should be rolled back immediately
+     * @return  bool    MDB2_OK
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function failNestedTransaction($error = null, $immediately = false)
+    {
+        if (null !== $error) {
+            $error = $this->has_transaction_error ? $this->has_transaction_error : true;
+        } elseif (!$error) {
+            $error = true;
+        }
+        $this->has_transaction_error = $error;
+        if (!$immediately) {
+            return MDB2_OK;
+        }
+        return $this->rollback();
+    }
+
+    // }}}
+    // {{{ function getNestedTransactionError()
+
+    /**
+     * The first error that occured since the transaction start.
+     *
+     * @return  MDB2_Error|bool     MDB2 error object if an error occured or false.
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function getNestedTransactionError()
+    {
+        return $this->has_transaction_error;
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database
+     *
+     * @return true on success, MDB2 Error Object on failure
+     */
+    function connect()
+    {
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ databaseExists()
+
+    /**
+     * check if given database name is exists?
+     *
+     * @param string $name    name of the database that should be checked
+     *
+     * @return mixed true/false on success, a MDB2 error on failure
+     * @access public
+     */
+    function databaseExists($name)
+    {
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ setCharset($charset, $connection = null)
+
+    /**
+     * Set the charset on the current connection
+     *
+     * @param string    charset
+     * @param resource  connection handle
+     *
+     * @return true on success, MDB2 Error Object on failure
+     */
+    function setCharset($charset, $connection = null)
+    {
+        return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function disconnect($force = true)
+
+    /**
+     * Log out and disconnect from the database.
+     *
+     * @param boolean $force whether the disconnect should be forced even if the
+     *                       connection is opened persistently
+     *
+     * @return mixed true on success, false if not connected and error object on error
+     *
+     * @access  public
+     */
+    function disconnect($force = true)
+    {
+        $this->connection = 0;
+        $this->connected_dsn = array();
+        $this->connected_database_name = '';
+        $this->opened_persistent = null;
+        $this->connected_server_info = '';
+        $this->in_transaction = null;
+        $this->nested_transaction_counter = null;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function setDatabase($name)
+
+    /**
+     * Select a different database
+     *
+     * @param   string  name of the database that should be selected
+     *
+     * @return  string  name of the database previously connected to
+     *
+     * @access  public
+     */
+    function setDatabase($name)
+    {
+        $previous_database_name = (isset($this->database_name)) ? $this->database_name : '';
+        $this->database_name = $name;
+        if (!empty($this->connected_database_name) && ($this->connected_database_name != $this->database_name)) {
+            $this->disconnect(false);
+        }
+        return $previous_database_name;
+    }
+
+    // }}}
+    // {{{ function getDatabase()
+
+    /**
+     * Get the current database
+     *
+     * @return  string  name of the database
+     *
+     * @access  public
+     */
+    function getDatabase()
+    {
+        return $this->database_name;
+    }
+
+    // }}}
+    // {{{ function setDSN($dsn)
+
+    /**
+     * set the DSN
+     *
+     * @param   mixed   DSN string or array
+     *
+     * @return  MDB2_OK
+     *
+     * @access  public
+     */
+    function setDSN($dsn)
+    {
+        $dsn_default = $GLOBALS['_MDB2_dsninfo_default'];
+        $dsn = MDB2::parseDSN($dsn);
+        if (array_key_exists('database', $dsn)) {
+            $this->database_name = $dsn['database'];
+            unset($dsn['database']);
+        }
+        $this->dsn = array_merge($dsn_default, $dsn);
+        return $this->disconnect(false);
+    }
+
+    // }}}
+    // {{{ function getDSN($type = 'string', $hidepw = false)
+
+    /**
+     * return the DSN as a string
+     *
+     * @param   string  format to return ("array", "string")
+     * @param   string  string to hide the password with
+     *
+     * @return  mixed   DSN in the chosen type
+     *
+     * @access  public
+     */
+    function getDSN($type = 'string', $hidepw = false)
+    {
+        $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn);
+        $dsn['phptype'] = $this->phptype;
+        $dsn['database'] = $this->database_name;
+        if ($hidepw) {
+            $dsn['password'] = $hidepw;
+        }
+        switch ($type) {
+        // expand to include all possible options
+        case 'string':
+           $dsn = $dsn['phptype'].
+               ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : '').
+               '://'.$dsn['username'].':'.
+                $dsn['password'].'@'.$dsn['hostspec'].
+                ($dsn['port'] ? (':'.$dsn['port']) : '').
+                '/'.$dsn['database'];
+            break;
+        case 'array':
+        default:
+            break;
+        }
+        return $dsn;
+    }
+
+    // }}}
+    // {{{ _isNewLinkSet()
+
+    /**
+     * Check if the 'new_link' option is set
+     *
+     * @return boolean
+     *
+     * @access protected
+     */
+    function _isNewLinkSet()
+    {
+        return (isset($this->dsn['new_link'])
+            && ($this->dsn['new_link'] === true
+             || (is_string($this->dsn['new_link']) && preg_match('/^true$/i', $this->dsn['new_link']))
+             || (is_numeric($this->dsn['new_link']) && 0 != (int)$this->dsn['new_link'])
+            )
+        );
+    }
+
+    // }}}
+    // {{{ function &standaloneQuery($query, $types = null, $is_manip = false)
+
+   /**
+     * execute a query as database administrator
+     *
+     * @param   string  the SQL query
+     * @param   mixed   array that contains the types of the columns in
+     *                        the result set
+     * @param   bool    if the query is a manipulation query
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function standaloneQuery($query, $types = null, $is_manip = false)
+    {
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $result = $this->_doQuery($query, $is_manip, $connection, false);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        if ($is_manip) {
+            $affected_rows =  $this->_affectedRows($connection, $result);
+            return $affected_rows;
+        }
+        $result = $this->_wrapResult($result, $types, true, true, $limit, $offset);
+        return $result;
+    }
+
+    // }}}
+    // {{{ function _modifyQuery($query, $is_manip, $limit, $offset)
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * @param   string  query to modify
+     * @param   bool    if it is a DML query
+     * @param   int  limit the number of rows
+     * @param   int  start reading from given offset
+     *
+     * @return  string  modified query
+     *
+     * @access  protected
+     */
+    function _modifyQuery($query, $is_manip, $limit, $offset)
+    {
+        return $query;
+    }
+
+    // }}}
+    // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
+
+    /**
+     * Execute a query
+     * @param   string  query
+     * @param   bool    if the query is a manipulation query
+     * @param   resource connection handle
+     * @param   string  database name
+     *
+     * @return  result or error object
+     *
+     * @access  protected
+     */
+    function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
+    {
+        $this->last_query = $query;
+        $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+        return $err;
+    }
+
+    // }}}
+    // {{{ function _affectedRows($connection, $result = null)
+
+    /**
+     * Returns the number of rows affected
+     *
+     * @param   resource result handle
+     * @param   resource connection handle
+     *
+     * @return  mixed   MDB2 Error Object or the number of rows affected
+     *
+     * @access  private
+     */
+    function _affectedRows($connection, $result = null)
+    {
+        return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function &exec($query)
+
+    /**
+     * Execute a manipulation query to the database and return the number of affected rows
+     *
+     * @param   string  the SQL query
+     *
+     * @return  mixed   number of affected rows on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function exec($query)
+    {
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, true, $limit, $offset);
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $result = $this->_doQuery($query, true, $connection, $this->database_name);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        $affectedRows = $this->_affectedRows($connection, $result);
+        return $affectedRows;
+    }
+
+    // }}}
+    // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false)
+
+    /**
+     * Send a query to the database and return any results
+     *
+     * @param   string  the SQL query
+     * @param   mixed   array that contains the types of the columns in
+     *                        the result set
+     * @param   mixed   string which specifies which result class to use
+     * @param   mixed   string which specifies which class to wrap results in
+     *
+     * @return mixed   an MDB2_Result handle on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function query($query, $types = null, $result_class = true, $result_wrap_class = true)
+    {
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, false, $limit, $offset);
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $result = $this->_doQuery($query, false, $connection, $this->database_name);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        $result = $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset);
+        return $result;
+    }
+
+    // }}}
+    // {{{ function _wrapResult($result_resource, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null)
+
+    /**
+     * wrap a result set into the correct class
+     *
+     * @param   resource result handle
+     * @param   mixed   array that contains the types of the columns in
+     *                        the result set
+     * @param   mixed   string which specifies which result class to use
+     * @param   mixed   string which specifies which class to wrap results in
+     * @param   string  number of rows to select
+     * @param   string  first row to select
+     *
+     * @return mixed   an MDB2_Result, a MDB2 error on failure
+     *
+     * @access  protected
+     */
+    function _wrapResult($result_resource, $types = array(), $result_class = true,
+        $result_wrap_class = true, $limit = null, $offset = null)
+    {
+        if ($types === true) {
+            if ($this->supports('result_introspection')) {
+                $this->loadModule('Reverse', null, true);
+                $tableInfo = $this->reverse->tableInfo($result_resource);
+                if (MDB2::isError($tableInfo)) {
+                    return $tableInfo;
+                }
+                $types = array();
+                $types_assoc = array();
+                foreach ($tableInfo as $field) {
+                    $types[] = $field['mdb2type'];
+                    $types_assoc[$field['name']] = $field['mdb2type'];
+                }
+            } else {
+                $types = null;
+            }
+        }
+
+        if ($result_class === true) {
+            $result_class = $this->options['result_buffering']
+                ? $this->options['buffered_result_class'] : $this->options['result_class'];
+        }
+
+        if ($result_class) {
+            $class_name = sprintf($result_class, $this->phptype);
+            if (!MDB2::classExists($class_name)) {
+                $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                    'result class does not exist '.$class_name, __FUNCTION__);
+                return $err;
+            }
+            $result = new $class_name($this, $result_resource, $limit, $offset);
+            if (!MDB2::isResultCommon($result)) {
+                $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                    'result class is not extended from MDB2_Result_Common', __FUNCTION__);
+                return $err;
+            }
+
+            if (!empty($types)) {
+                $err = $result->setResultTypes($types);
+                if (MDB2::isError($err)) {
+                    $result->free();
+                    return $err;
+                }
+            }
+            if (!empty($types_assoc)) {
+                $err = $result->setResultTypes($types_assoc);
+                if (MDB2::isError($err)) {
+                    $result->free();
+                    return $err;
+                }
+            }
+
+            if ($result_wrap_class === true) {
+                $result_wrap_class = $this->options['result_wrap_class'];
+            }
+            if ($result_wrap_class) {
+                if (!MDB2::classExists($result_wrap_class)) {
+                    $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                        'result wrap class does not exist '.$result_wrap_class, __FUNCTION__);
+                    return $err;
+                }
+                $result = new $result_wrap_class($result, $this->fetchmode);
+            }
+
+            return $result;
+        }
+
+        return $result_resource;
+    }
+
+    // }}}
+    // {{{ function getServerVersion($native = false)
+
+    /**
+     * return version information about the server
+     *
+     * @param   bool    determines if the raw version string should be returned
+     *
+     * @return  mixed   array with version information or row string
+     *
+     * @access  public
+     */
+    function getServerVersion($native = false)
+    {
+        return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function setLimit($limit, $offset = null)
+
+    /**
+     * set the range of the next query
+     *
+     * @param   string  number of rows to select
+     * @param   string  first row to select
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function setLimit($limit, $offset = null)
+    {
+        if (!$this->supports('limit_queries')) {
+            return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'limit is not supported by this driver', __FUNCTION__);
+        }
+        $limit = (int)$limit;
+        if ($limit < 0) {
+            return MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null,
+                'it was not specified a valid selected range row limit', __FUNCTION__);
+        }
+        $this->limit = $limit;
+        if (null !== $offset) {
+            $offset = (int)$offset;
+            if ($offset < 0) {
+                return MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null,
+                    'it was not specified a valid first selected range row', __FUNCTION__);
+            }
+            $this->offset = $offset;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function subSelect($query, $type = false)
+
+    /**
+     * simple subselect emulation: leaves the query untouched for all RDBMS
+     * that support subselects
+     *
+     * @param   string  the SQL query for the subselect that may only
+     *                      return a column
+     * @param   string  determines type of the field
+     *
+     * @return  string  the query
+     *
+     * @access  public
+     */
+    function subSelect($query, $type = false)
+    {
+        if ($this->supports('sub_selects') === true) {
+            return $query;
+        }
+
+        if (!$this->supports('sub_selects')) {
+            return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'method not implemented', __FUNCTION__);
+        }
+
+        $col = $this->queryCol($query, $type);
+        if (MDB2::isError($col)) {
+            return $col;
+        }
+        if (!is_array($col) || count($col) == 0) {
+            return 'NULL';
+        }
+        if ($type) {
+            $this->loadModule('Datatype', null, true);
+            return $this->datatype->implodeArray($col, $type);
+        }
+        return implode(', ', $col);
+    }
+
+    // }}}
+    // {{{ function replace($table, $fields)
+
+    /**
+     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
+     * query, except that if there is already a row in the table with the same
+     * key field values, the old row is deleted before the new row is inserted.
+     *
+     * The REPLACE type of query does not make part of the SQL standards. Since
+     * practically only MySQL and SQLite implement it natively, this type of
+     * query isemulated through this method for other DBMS using standard types
+     * of queries inside a transaction to assure the atomicity of the operation.
+     *
+     * @param   string  name of the table on which the REPLACE query will
+     *       be executed.
+     * @param   array   associative array   that describes the fields and the
+     *       values that will be inserted or updated in the specified table. The
+     *       indexes of the array are the names of all the fields of the table.
+     *       The values of the array are also associative arrays that describe
+     *       the values and other properties of the table fields.
+     *
+     *       Here follows a list of field properties that need to be specified:
+     *
+     *       value
+     *           Value to be assigned to the specified field. This value may be
+     *           of specified in database independent type format as this
+     *           function can perform the necessary datatype conversions.
+     *
+     *           Default: this property is required unless the Null property is
+     *           set to 1.
+     *
+     *       type
+     *           Name of the type of the field. Currently, all types MDB2
+     *           are supported except for clob and blob.
+     *
+     *           Default: no type conversion
+     *
+     *       null
+     *           bool    property that indicates that the value for this field
+     *           should be set to null.
+     *
+     *           The default value for fields missing in INSERT queries may be
+     *           specified the definition of a table. Often, the default value
+     *           is already null, but since the REPLACE may be emulated using
+     *           an UPDATE query, make sure that all fields of the table are
+     *           listed in this function argument array.
+     *
+     *           Default: 0
+     *
+     *       key
+     *           bool    property that indicates that this field should be
+     *           handled as a primary key or at least as part of the compound
+     *           unique index of the table that will determine the row that will
+     *           updated if it exists or inserted a new row otherwise.
+     *
+     *           This function will fail if no key field is specified or if the
+     *           value of a key field is set to null because fields that are
+     *           part of unique index they may not be null.
+     *
+     *           Default: 0
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function replace($table, $fields)
+    {
+        if (!$this->supports('replace')) {
+            return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'replace query is not supported', __FUNCTION__);
+        }
+        $count = count($fields);
+        $condition = $values = array();
+        for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) {
+            $name = key($fields);
+            if (isset($fields[$name]['null']) && $fields[$name]['null']) {
+                $value = 'NULL';
+            } else {
+                $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
+                $value = $this->quote($fields[$name]['value'], $type);
+            }
+            $values[$name] = $value;
+            if (isset($fields[$name]['key']) && $fields[$name]['key']) {
+                if ($value === 'NULL') {
+                    return MDB2_Driver_Common::raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                        'key value '.$name.' may not be NULL', __FUNCTION__);
+                }
+                $condition[] = $this->quoteIdentifier($name, true) . '=' . $value;
+            }
+        }
+        if (empty($condition)) {
+            return MDB2_Driver_Common::raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                'not specified which fields are keys', __FUNCTION__);
+        }
+
+        $result = null;
+        $in_transaction = $this->in_transaction;
+        if (!$in_transaction && MDB2::isError($result = $this->beginTransaction())) {
+            return $result;
+        }
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $condition = ' WHERE '.implode(' AND ', $condition);
+        $query = 'DELETE FROM ' . $this->quoteIdentifier($table, true) . $condition;
+        $result = $this->_doQuery($query, true, $connection);
+        if (!MDB2::isError($result)) {
+            $affected_rows = $this->_affectedRows($connection, $result);
+            $insert = '';
+            foreach ($values as $key => $value) {
+                $insert .= ($insert?', ':'') . $this->quoteIdentifier($key, true);
+            }
+            $values = implode(', ', $values);
+            $query = 'INSERT INTO '. $this->quoteIdentifier($table, true) . "($insert) VALUES ($values)";
+            $result = $this->_doQuery($query, true, $connection);
+            if (!MDB2::isError($result)) {
+                $affected_rows += $this->_affectedRows($connection, $result);;
+            }
+        }
+
+        if (!$in_transaction) {
+            if (MDB2::isError($result)) {
+                $this->rollback();
+            } else {
+                $result = $this->commit();
+            }
+        }
+
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        return $affected_rows;
+    }
+
+    // }}}
+    // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array())
+
+    /**
+     * Prepares a query for multiple execution with execute().
+     * With some database backends, this is emulated.
+     * prepare() requires a generic query as string like
+     * 'INSERT INTO numbers VALUES(?,?)' or
+     * 'INSERT INTO numbers VALUES(:foo,:bar)'.
+     * The ? and :name and are placeholders which can be set using
+     * bindParam() and the query can be sent off using the execute() method.
+     * The allowed format for :name can be set with the 'bindname_format' option.
+     *
+     * @param   string  the query to prepare
+     * @param   mixed   array that contains the types of the placeholders
+     * @param   mixed   array that contains the types of the columns in
+     *                        the result set or MDB2_PREPARE_RESULT, if set to
+     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
+     * @param   mixed   key (field) value (parameter) pair for all lob placeholders
+     *
+     * @return  mixed   resource handle for the prepared query on success,
+     *                  a MDB2 error on failure
+     *
+     * @access  public
+     * @see     bindParam, execute
+     */
+    function prepare($query, $types = null, $result_types = null, $lobs = array())
+    {
+        $is_manip = ($result_types === MDB2_PREPARE_MANIP);
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        $placeholder_type_guess = $placeholder_type = null;
+        $question  = '?';
+        $colon     = ':';
+        $positions = array();
+        $position  = 0;
+        while ($position < strlen($query)) {
+            $q_position = strpos($query, $question, $position);
+            $c_position = strpos($query, $colon, $position);
+            if ($q_position && $c_position) {
+                $p_position = min($q_position, $c_position);
+            } elseif ($q_position) {
+                $p_position = $q_position;
+            } elseif ($c_position) {
+                $p_position = $c_position;
+            } else {
+                break;
+            }
+            if (null === $placeholder_type) {
+                $placeholder_type_guess = $query[$p_position];
+            }
+
+            $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
+            if (MDB2::isError($new_pos)) {
+                return $new_pos;
+            }
+            if ($new_pos != $position) {
+                $position = $new_pos;
+                continue; //evaluate again starting from the new position
+            }
+
+            if ($query[$position] == $placeholder_type_guess) {
+                if (null === $placeholder_type) {
+                    $placeholder_type = $query[$p_position];
+                    $question = $colon = $placeholder_type;
+                    if (!empty($types) && is_array($types)) {
+                        if ($placeholder_type == ':') {
+                            if (is_int(key($types))) {
+                                $types_tmp = $types;
+                                $types = array();
+                                $count = -1;
+                            }
+                        } else {
+                            $types = array_values($types);
+                        }
+                    }
+                }
+                if ($placeholder_type == ':') {
+                    $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
+                    $parameter = preg_replace($regexp, '\\1', $query);
+                    if ($parameter === '') {
+                        $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null,
+                            'named parameter name must match "bindname_format" option', __FUNCTION__);
+                        return $err;
+                    }
+                    $positions[$p_position] = $parameter;
+                    $query = substr_replace($query, '?', $position, strlen($parameter)+1);
+                    // use parameter name in type array
+                    if (isset($count) && isset($types_tmp[++$count])) {
+                        $types[$parameter] = $types_tmp[$count];
+                    }
+                } else {
+                    $positions[$p_position] = count($positions);
+                }
+                $position = $p_position + 1;
+            } else {
+                $position = $p_position;
+            }
+        }
+        $class_name = 'MDB2_Statement_'.$this->phptype;
+        $statement = null;
+        $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
+        $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
+        return $obj;
+    }
+
+    // }}}
+    // {{{ function _skipDelimitedStrings($query, $position, $p_position)
+
+    /**
+     * Utility method, used by prepare() to avoid replacing placeholders within delimited strings.
+     * Check if the placeholder is contained within a delimited string.
+     * If so, skip it and advance the position, otherwise return the current position,
+     * which is valid
+     *
+     * @param string $query
+     * @param integer $position current string cursor position
+     * @param integer $p_position placeholder position
+     *
+     * @return mixed integer $new_position on success
+     *               MDB2_Error on failure
+     *
+     * @access  protected
+     */
+    function _skipDelimitedStrings($query, $position, $p_position)
+    {
+        $ignores = array();
+        $ignores[] = $this->string_quoting;
+        $ignores[] = $this->identifier_quoting;
+        $ignores = array_merge($ignores, $this->sql_comments);
+
+        foreach ($ignores as $ignore) {
+            if (!empty($ignore['start'])) {
+                if (is_int($start_quote = strpos($query, $ignore['start'], $position)) && $start_quote < $p_position) {
+                    $end_quote = $start_quote;
+                    do {
+                        if (!is_int($end_quote = strpos($query, $ignore['end'], $end_quote + 1))) {
+                            if ($ignore['end'] === "\n") {
+                                $end_quote = strlen($query) - 1;
+                            } else {
+                                $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null,
+                                    'query with an unterminated text string specified', __FUNCTION__);
+                                return $err;
+                            }
+                        }
+                    } while ($ignore['escape']
+                        && $end_quote-1 != $start_quote
+                        && $query[($end_quote - 1)] == $ignore['escape']
+                        && (   $ignore['escape_pattern'] !== $ignore['escape']
+                            || $query[($end_quote - 2)] != $ignore['escape'])
+                    );
+
+                    $position = $end_quote + 1;
+                    return $position;
+                }
+            }
+        }
+        return $position;
+    }
+
+    // }}}
+    // {{{ function quote($value, $type = null, $quote = true)
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param   string  text string value that is intended to be converted.
+     * @param   string  type to which the value should be converted to
+     * @param   bool    quote
+     * @param   bool    escape wildcards
+     *
+     * @return  string  text string that represents the given argument value in
+     *       a DBMS specific format.
+     *
+     * @access  public
+     */
+    function quote($value, $type = null, $quote = true, $escape_wildcards = false)
+    {
+        $result = $this->loadModule('Datatype', null, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        return $this->datatype->quote($value, $type, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ function getDeclaration($type, $name, $field)
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare
+     * of the given type
+     *
+     * @param   string  type to which the value should be converted to
+     * @param   string  name the field to be declared.
+     * @param   string  definition of the field
+     *
+     * @return  string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     *
+     * @access  public
+     */
+    function getDeclaration($type, $name, $field)
+    {
+        $result = $this->loadModule('Datatype', null, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $this->datatype->getDeclaration($type, $name, $field);
+    }
+
+    // }}}
+    // {{{ function compareDefinition($current, $previous)
+
+    /**
+     * Obtain an array of changes that may need to applied
+     *
+     * @param   array   new definition
+     * @param   array   old definition
+     *
+     * @return  array   containing all changes that will need to be applied
+     *
+     * @access  public
+     */
+    function compareDefinition($current, $previous)
+    {
+        $result = $this->loadModule('Datatype', null, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $this->datatype->compareDefinition($current, $previous);
+    }
+
+    // }}}
+    // {{{ function supports($feature)
+
+    /**
+     * Tell whether a DB implementation or its backend extension
+     * supports a given feature.
+     *
+     * @param   string  name of the feature (see the MDB2 class doc)
+     *
+     * @return  bool|string if this DB implementation supports a given feature
+     *                      false means no, true means native,
+     *                      'emulated' means emulated
+     *
+     * @access  public
+     */
+    function supports($feature)
+    {
+        if (array_key_exists($feature, $this->supported)) {
+            return $this->supported[$feature];
+        }
+        return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            "unknown support feature $feature", __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function getSequenceName($sqn)
+
+    /**
+     * adds sequence name formatting to a sequence name
+     *
+     * @param   string  name of the sequence
+     *
+     * @return  string  formatted sequence name
+     *
+     * @access  public
+     */
+    function getSequenceName($sqn)
+    {
+        return sprintf($this->options['seqname_format'],
+            preg_replace('/[^a-z0-9_\-\$.]/i', '_', $sqn));
+    }
+
+    // }}}
+    // {{{ function getIndexName($idx)
+
+    /**
+     * adds index name formatting to a index name
+     *
+     * @param   string  name of the index
+     *
+     * @return  string  formatted index name
+     *
+     * @access  public
+     */
+    function getIndexName($idx)
+    {
+        return sprintf($this->options['idxname_format'],
+            preg_replace('/[^a-z0-9_\-\$.]/i', '_', $idx));
+    }
+
+    // }}}
+    // {{{ function nextID($seq_name, $ondemand = true)
+
+    /**
+     * Returns the next free id of a sequence
+     *
+     * @param   string  name of the sequence
+     * @param   bool    when true missing sequences are automatic created
+     *
+     * @return  mixed   MDB2 Error Object or id
+     *
+     * @access  public
+     */
+    function nextID($seq_name, $ondemand = true)
+    {
+        return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function lastInsertID($table = null, $field = null)
+
+    /**
+     * Returns the autoincrement ID if supported or $id or fetches the current
+     * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
+     *
+     * @param   string  name of the table into which a new row was inserted
+     * @param   string  name of the field into which a new row was inserted
+     *
+     * @return  mixed   MDB2 Error Object or id
+     *
+     * @access  public
+     */
+    function lastInsertID($table = null, $field = null)
+    {
+        return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function currID($seq_name)
+
+    /**
+     * Returns the current id of a sequence
+     *
+     * @param   string  name of the sequence
+     *
+     * @return  mixed   MDB2 Error Object or id
+     *
+     * @access  public
+     */
+    function currID($seq_name)
+    {
+        $this->warnings[] = 'database does not support getting current
+            sequence value, the sequence value was incremented';
+        return $this->nextID($seq_name);
+    }
+
+    // }}}
+    // {{{ function queryOne($query, $type = null, $colnum = 0)
+
+    /**
+     * Execute the specified query, fetch the value from the first column of
+     * the first row of the result set and then frees
+     * the result set.
+     *
+     * @param string $query  the SELECT query statement to be executed.
+     * @param string $type   optional argument that specifies the expected
+     *                       datatype of the result set field, so that an eventual
+     *                       conversion may be performed. The default datatype is
+     *                       text, meaning that no conversion is performed
+     * @param mixed  $colnum the column number (or name) to fetch
+     *
+     * @return  mixed   MDB2_OK or field value on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function queryOne($query, $type = null, $colnum = 0)
+    {
+        $result = $this->query($query, $type);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $one = $result->fetchOne($colnum);
+        $result->free();
+        return $one;
+    }
+
+    // }}}
+    // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT)
+
+    /**
+     * Execute the specified query, fetch the values from the first
+     * row of the result set into an array and then frees
+     * the result set.
+     *
+     * @param   string  the SELECT query statement to be executed.
+     * @param   array   optional array argument that specifies a list of
+     *       expected datatypes of the result set columns, so that the eventual
+     *       conversions may be performed. The default list of datatypes is
+     *       empty, meaning that no conversion is performed.
+     * @param   int     how the array data should be indexed
+     *
+     * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT)
+    {
+        $result = $this->query($query, $types);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $row = $result->fetchRow($fetchmode);
+        $result->free();
+        return $row;
+    }
+
+    // }}}
+    // {{{ function queryCol($query, $type = null, $colnum = 0)
+
+    /**
+     * Execute the specified query, fetch the value from the first column of
+     * each row of the result set into an array and then frees the result set.
+     *
+     * @param string $query  the SELECT query statement to be executed.
+     * @param string $type   optional argument that specifies the expected
+     *                       datatype of the result set field, so that an eventual
+     *                       conversion may be performed. The default datatype is text,
+     *                       meaning that no conversion is performed
+     * @param mixed  $colnum the column number (or name) to fetch
+     *
+     * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
+     * @access  public
+     */
+    function queryCol($query, $type = null, $colnum = 0)
+    {
+        $result = $this->query($query, $type);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $col = $result->fetchCol($colnum);
+        $result->free();
+        return $col;
+    }
+
+    // }}}
+    // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false)
+
+    /**
+     * Execute the specified query, fetch all the rows of the result set into
+     * a two dimensional array and then frees the result set.
+     *
+     * @param   string  the SELECT query statement to be executed.
+     * @param   array   optional array argument that specifies a list of
+     *       expected datatypes of the result set columns, so that the eventual
+     *       conversions may be performed. The default list of datatypes is
+     *       empty, meaning that no conversion is performed.
+     * @param   int     how the array data should be indexed
+     * @param   bool    if set to true, the $all will have the first
+     *       column as its first dimension
+     * @param   bool    used only when the query returns exactly
+     *       two columns. If true, the values of the returned array will be
+     *       one-element arrays instead of scalars.
+     * @param   bool    if true, the values of the returned array is
+     *       wrapped in another array.  If the same key value (in the first
+     *       column) repeats itself, the values will be appended to this array
+     *       instead of overwriting the existing values.
+     *
+     * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT,
+        $rekey = false, $force_array = false, $group = false)
+    {
+        $result = $this->query($query, $types);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group);
+        $result->free();
+        return $all;
+    }
+
+    // }}}
+    // {{{ function delExpect($error_code)
+
+    /**
+     * This method deletes all occurences of the specified element from
+     * the expected error codes stack.
+     *
+     * @param  mixed $error_code error code that should be deleted
+     * @return mixed list of error codes that were deleted or error
+     *
+     * @uses PEAR::delExpect()
+     */
+    public function delExpect($error_code)
+    {
+        return $this->pear->delExpect($error_code);
+    }
+
+    // }}}
+    // {{{ function expectError($code)
+
+    /**
+     * This method is used to tell which errors you expect to get.
+     * Expected errors are always returned with error mode
+     * PEAR_ERROR_RETURN.  Expected error codes are stored in a stack,
+     * and this method pushes a new element onto it.  The list of
+     * expected errors are in effect until they are popped off the
+     * stack with the popExpect() method.
+     *
+     * Note that this method can not be called statically
+     *
+     * @param mixed $code a single error code or an array of error codes to expect
+     *
+     * @return int     the new depth of the "expected errors" stack
+     *
+     * @uses PEAR::expectError()
+     */
+    public function expectError($code = '*')
+    {
+        return $this->pear->expectError($code);
+    }
+
+    // }}}
+    // {{{ function getStaticProperty($class, $var)
+
+    /**
+     * If you have a class that's mostly/entirely static, and you need static
+     * properties, you can use this method to simulate them. Eg. in your method(s)
+     * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+     * You MUST use a reference, or they will not persist!
+     *
+     * @param  string $class  The calling classname, to prevent clashes
+     * @param  string $var    The variable to retrieve.
+     * @return mixed   A reference to the variable. If not set it will be
+     *                 auto initialised to NULL.
+     *
+     * @uses PEAR::getStaticProperty()
+     */
+    public function &getStaticProperty($class, $var)
+    {
+        $tmp =& $this->pear->getStaticProperty($class, $var);
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ function popErrorHandling()
+
+    /**
+     * Pop the last error handler used
+     *
+     * @return bool Always true
+     *
+     * @see PEAR::pushErrorHandling
+     * @uses PEAR::popErrorHandling()
+     */
+    public function popErrorHandling()
+    {
+        return $this->pear->popErrorHandling();
+    }
+
+    // }}}
+    // {{{ function popExpect()
+
+    /**
+     * This method pops one element off the expected error codes
+     * stack.
+     *
+     * @return array   the list of error codes that were popped
+     *
+     * @uses PEAR::popExpect()
+     */
+    public function popExpect()
+    {
+        return $this->pear->popExpect();
+    }
+
+    // }}}
+    // {{{ function pushErrorHandling($mode, $options = null)
+
+    /**
+     * Push a new error handler on top of the error handler options stack. With this
+     * you can easily override the actual error handler for some code and restore
+     * it later with popErrorHandling.
+     *
+     * @param mixed $mode (same as setErrorHandling)
+     * @param mixed $options (same as setErrorHandling)
+     *
+     * @return bool Always true
+     *
+     * @see PEAR::setErrorHandling
+     * @uses PEAR::pushErrorHandling()
+     */
+    public function pushErrorHandling($mode, $options = null)
+    {
+        return $this->pear->pushErrorHandling($mode, $options);
+    }
+
+    // }}}
+    // {{{ function registerShutdownFunc($func, $args = array())
+
+    /**
+     * Use this function to register a shutdown method for static
+     * classes.
+     *
+     * @param  mixed $func  The function name (or array of class/method) to call
+     * @param  mixed $args  The arguments to pass to the function
+     * @return void
+     *
+     * @uses PEAR::registerShutdownFunc()
+     */
+    public function registerShutdownFunc($func, $args = array())
+    {
+        return $this->pear->registerShutdownFunc($func, $args);
+    }
+
+    // }}}
+    // {{{ function setErrorHandling($mode = null, $options = null)
+
+    /**
+     * Sets how errors generated by this object should be handled.
+     * Can be invoked both in objects and statically.  If called
+     * statically, setErrorHandling sets the default behaviour for all
+     * PEAR objects.  If called in an object, setErrorHandling sets
+     * the default behaviour for that object.
+     *
+     * @param int $mode
+     *        One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+     *        PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+     *        PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+     *
+     * @param mixed $options
+     *        When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+     *        of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+     *
+     *        When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+     *        to be the callback function or method.  A callback
+     *        function is a string with the name of the function, a
+     *        callback method is an array of two elements: the element
+     *        at index 0 is the object, and the element at index 1 is
+     *        the name of the method to call in the object.
+     *
+     *        When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+     *        a printf format string used when printing the error
+     *        message.
+     *
+     * @access public
+     * @return void
+     * @see PEAR_ERROR_RETURN
+     * @see PEAR_ERROR_PRINT
+     * @see PEAR_ERROR_TRIGGER
+     * @see PEAR_ERROR_DIE
+     * @see PEAR_ERROR_CALLBACK
+     * @see PEAR_ERROR_EXCEPTION
+     *
+     * @since PHP 4.0.5
+     * @uses PEAR::setErrorHandling($mode, $options)
+     */
+    public function setErrorHandling($mode = null, $options = null)
+    {
+        return $this->pear->setErrorHandling($mode, $options);
+    }
+
+    /**
+     * @uses PEAR::staticPopErrorHandling() 
+     */
+    public function staticPopErrorHandling()
+    {
+        return $this->pear->staticPopErrorHandling();
+    }
+
+    // }}}
+    // {{{ function staticPushErrorHandling($mode, $options = null)
+
+    /**
+     * @uses PEAR::staticPushErrorHandling($mode, $options)
+     */
+    public function staticPushErrorHandling($mode, $options = null)
+    {
+        return $this->pear->staticPushErrorHandling($mode, $options);
+    }
+
+    // }}}
+    // {{{ function &throwError($message = null, $code = null, $userinfo = null)
+
+    /**
+     * Simpler form of raiseError with fewer options.  In most cases
+     * message, code and userinfo are enough.
+     *
+     * @param mixed $message a text error message or a PEAR error object
+     *
+     * @param int $code      a numeric error code (it is up to your class
+     *                  to define these if you want to use codes)
+     *
+     * @param string $userinfo If you need to pass along for example debug
+     *                  information, this parameter is meant for that.
+     *
+     * @return object   a PEAR error object
+     * @see PEAR::raiseError
+     * @uses PEAR::&throwError()
+     */
+    public function &throwError($message = null, $code = null, $userinfo = null)
+    {
+        $tmp =& $this->pear->throwError($message, $code, $userinfo);
+        return $tmp;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Result
+
+/**
+ * The dummy class that all user space result classes should extend from
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Result
+{
+}
+
+// }}}
+// {{{ class MDB2_Result_Common extends MDB2_Result
+
+/**
+ * The common result class for MDB2 result objects
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Result_Common extends MDB2_Result
+{
+    // {{{ Variables (Properties)
+
+    public $db;
+    public $result;
+    public $rownum = -1;
+    public $types = array();
+    public $types_assoc = array();
+    public $values = array();
+    public $offset;
+    public $offset_count = 0;
+    public $limit;
+    public $column_names;
+
+    // }}}
+    // {{{ constructor: function __construct($db, &$result, $limit = 0, $offset = 0)
+
+    /**
+     * Constructor
+     */
+    function __construct($db, &$result, $limit = 0, $offset = 0)
+    {
+        $this->db = $db;
+        $this->result = $result;
+        $this->offset = $offset;
+        $this->limit = max(0, $limit - 1);
+    }
+
+    // }}}
+    // {{{ function setResultTypes($types)
+
+    /**
+     * Define the list of types to be associated with the columns of a given
+     * result set.
+     *
+     * This function may be called before invoking fetchRow(), fetchOne(),
+     * fetchCol() and fetchAll() so that the necessary data type
+     * conversions are performed on the data to be retrieved by them. If this
+     * function is not called, the type of all result set columns is assumed
+     * to be text, thus leading to not perform any conversions.
+     *
+     * @param   array   variable that lists the
+     *       data types to be expected in the result set columns. If this array
+     *       contains less types than the number of columns that are returned
+     *       in the result set, the remaining columns are assumed to be of the
+     *       type text. Currently, the types clob and blob are not fully
+     *       supported.
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function setResultTypes($types)
+    {
+        $load = $this->db->loadModule('Datatype', null, true);
+        if (MDB2::isError($load)) {
+            return $load;
+        }
+        $types = $this->db->datatype->checkResultTypes($types);
+        if (MDB2::isError($types)) {
+            return $types;
+        }
+        foreach ($types as $key => $value) {
+            if (is_numeric($key)) {
+                $this->types[$key] = $value;
+            } else {
+                $this->types_assoc[$key] = $value;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function seek($rownum = 0)
+
+    /**
+     * Seek to a specific row in a result set
+     *
+     * @param   int     number of the row where the data can be found
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function seek($rownum = 0)
+    {
+        $target_rownum = $rownum - 1;
+        if ($this->rownum > $target_rownum) {
+            return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'seeking to previous rows not implemented', __FUNCTION__);
+        }
+        while ($this->rownum < $target_rownum) {
+            $this->fetchRow();
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
+
+    /**
+     * Fetch and return a row of data
+     *
+     * @param   int     how the array data should be indexed
+     * @param   int     number of the row where the data can be found
+     *
+     * @return  int     data array on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        $err = MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+        return $err;
+    }
+
+    // }}}
+    // {{{ function fetchOne($colnum = 0)
+
+    /**
+     * fetch single column from the next row from a result set
+     *
+     * @param int|string the column number (or name) to fetch
+     * @param int        number of the row where the data can be found
+     *
+     * @return string data on success, a MDB2 error on failure
+     * @access  public
+     */
+    function fetchOne($colnum = 0, $rownum = null)
+    {
+        $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC;
+        $row = $this->fetchRow($fetchmode, $rownum);
+        if (!is_array($row) || MDB2::isError($row)) {
+            return $row;
+        }
+        if (!array_key_exists($colnum, $row)) {
+            return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null,
+                'column is not defined in the result set: '.$colnum, __FUNCTION__);
+        }
+        return $row[$colnum];
+    }
+
+    // }}}
+    // {{{ function fetchCol($colnum = 0)
+
+    /**
+     * Fetch and return a column from the current row pointer position
+     *
+     * @param int|string the column number (or name) to fetch
+     *
+     * @return  mixed data array on success, a MDB2 error on failure
+     * @access  public
+     */
+    function fetchCol($colnum = 0)
+    {
+        $column = array();
+        $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC;
+        $row = $this->fetchRow($fetchmode);
+        if (is_array($row)) {
+            if (!array_key_exists($colnum, $row)) {
+                return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null,
+                    'column is not defined in the result set: '.$colnum, __FUNCTION__);
+            }
+            do {
+                $column[] = $row[$colnum];
+            } while (is_array($row = $this->fetchRow($fetchmode)));
+        }
+        if (MDB2::isError($row)) {
+            return $row;
+        }
+        return $column;
+    }
+
+    // }}}
+    // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false)
+
+    /**
+     * Fetch and return all rows from the current row pointer position
+     *
+     * @param   int     $fetchmode  the fetch mode to use:
+     *                            + MDB2_FETCHMODE_ORDERED
+     *                            + MDB2_FETCHMODE_ASSOC
+     *                            + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED
+     *                            + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED
+     * @param   bool    if set to true, the $all will have the first
+     *       column as its first dimension
+     * @param   bool    used only when the query returns exactly
+     *       two columns. If true, the values of the returned array will be
+     *       one-element arrays instead of scalars.
+     * @param   bool    if true, the values of the returned array is
+     *       wrapped in another array.  If the same key value (in the first
+     *       column) repeats itself, the values will be appended to this array
+     *       instead of overwriting the existing values.
+     *
+     * @return  mixed   data array on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @see     getAssoc()
+     */
+    function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false,
+        $force_array = false, $group = false)
+    {
+        $all = array();
+        $row = $this->fetchRow($fetchmode);
+        if (MDB2::isError($row)) {
+            return $row;
+        } elseif (!$row) {
+            return $all;
+        }
+
+        $shift_array = $rekey ? false : null;
+        if (null !== $shift_array) {
+            if (is_object($row)) {
+                $colnum = count(get_object_vars($row));
+            } else {
+                $colnum = count($row);
+            }
+            if ($colnum < 2) {
+                return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null,
+                    'rekey feature requires atleast 2 column', __FUNCTION__);
+            }
+            $shift_array = (!$force_array && $colnum == 2);
+        }
+
+        if ($rekey) {
+            do {
+                if (is_object($row)) {
+                    $arr = get_object_vars($row);
+                    $key = reset($arr);
+                    unset($row->{$key});
+                } else {
+                    if (   $fetchmode == MDB2_FETCHMODE_ASSOC
+                        || $fetchmode == MDB2_FETCHMODE_OBJECT
+                    ) {
+                        $key = reset($row);
+                        unset($row[key($row)]);
+                    } else {
+                        $key = array_shift($row);
+                    }
+                    if ($shift_array) {
+                        $row = array_shift($row);
+                    }
+                }
+                if ($group) {
+                    $all[$key][] = $row;
+                } else {
+                    $all[$key] = $row;
+                }
+            } while (($row = $this->fetchRow($fetchmode)));
+        } elseif ($fetchmode == MDB2_FETCHMODE_FLIPPED) {
+            do {
+                foreach ($row as $key => $val) {
+                    $all[$key][] = $val;
+                }
+            } while (($row = $this->fetchRow($fetchmode)));
+        } else {
+            do {
+                $all[] = $row;
+            } while (($row = $this->fetchRow($fetchmode)));
+        }
+
+        return $all;
+    }
+
+    // }}}
+    // {{{ function rowCount()
+    /**
+     * Returns the actual row number that was last fetched (count from 0)
+     * @return  int
+     *
+     * @access  public
+     */
+    function rowCount()
+    {
+        return $this->rownum + 1;
+    }
+
+    // }}}
+    // {{{ function numRows()
+
+    /**
+     * Returns the number of rows in a result object
+     *
+     * @return  mixed   MDB2 Error Object or the number of rows
+     *
+     * @access  public
+     */
+    function numRows()
+    {
+        return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function nextResult()
+
+    /**
+     * Move the internal result pointer to the next available result
+     *
+     * @return  true on success, false if there is no more result set or an error object on failure
+     *
+     * @access  public
+     */
+    function nextResult()
+    {
+        return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function getColumnNames()
+
+    /**
+     * Retrieve the names of columns returned by the DBMS in a query result or
+     * from the cache.
+     *
+     * @param   bool    If set to true the values are the column names,
+     *                  otherwise the names of the columns are the keys.
+     * @return  mixed   Array variable that holds the names of columns or an
+     *                  MDB2 error on failure.
+     *                  Some DBMS may not return any columns when the result set
+     *                  does not contain any rows.
+     *
+     * @access  public
+     */
+    function getColumnNames($flip = false)
+    {
+        if (!isset($this->column_names)) {
+            $result = $this->_getColumnNames();
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $this->column_names = $result;
+        }
+        if ($flip) {
+            return array_flip($this->column_names);
+        }
+        return $this->column_names;
+    }
+
+    // }}}
+    // {{{ function _getColumnNames()
+
+    /**
+     * Retrieve the names of columns returned by the DBMS in a query result.
+     *
+     * @return  mixed   Array variable that holds the names of columns as keys
+     *                  or an MDB2 error on failure.
+     *                  Some DBMS may not return any columns when the result set
+     *                  does not contain any rows.
+     *
+     * @access  private
+     */
+    function _getColumnNames()
+    {
+        return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function numCols()
+
+    /**
+     * Count the number of columns returned by the DBMS in a query result.
+     *
+     * @return  mixed   integer value with the number of columns, a MDB2 error
+     *       on failure
+     *
+     * @access  public
+     */
+    function numCols()
+    {
+        return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ function getResource()
+
+    /**
+     * return the resource associated with the result object
+     *
+     * @return  resource
+     *
+     * @access  public
+     */
+    function getResource()
+    {
+        return $this->result;
+    }
+
+    // }}}
+    // {{{ function bindColumn($column, &$value, $type = null)
+
+    /**
+     * Set bind variable to a column.
+     *
+     * @param   int     column number or name
+     * @param   mixed   variable reference
+     * @param   string  specifies the type of the field
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function bindColumn($column, &$value, $type = null)
+    {
+        if (!is_numeric($column)) {
+            $column_names = $this->getColumnNames();
+            if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($this->db->options['field_case'] == CASE_LOWER) {
+                    $column = strtolower($column);
+                } else {
+                    $column = strtoupper($column);
+                }
+            }
+            $column = $column_names[$column];
+        }
+        $this->values[$column] =& $value;
+        if (null !== $type) {
+            $this->types[$column] = $type;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function _assignBindColumns($row)
+
+    /**
+     * Bind a variable to a value in the result row.
+     *
+     * @param   array   row data
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  private
+     */
+    function _assignBindColumns($row)
+    {
+        $row = array_values($row);
+        foreach ($row as $column => $value) {
+            if (array_key_exists($column, $this->values)) {
+                $this->values[$column] = $value;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function free()
+
+    /**
+     * Free the internal resources associated with result.
+     *
+     * @return  bool    true on success, false if result is invalid
+     *
+     * @access  public
+     */
+    function free()
+    {
+        $this->result = false;
+        return MDB2_OK;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Row
+
+/**
+ * The simple class that accepts row data as an array
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Row
+{
+    // {{{ constructor: function __construct(&$row)
+
+    /**
+     * constructor
+     *
+     * @param   resource    row data as array
+     */
+    function __construct(&$row)
+    {
+        foreach ($row as $key => $value) {
+            $this->$key = &$row[$key];
+        }
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Statement_Common
+
+/**
+ * The common statement class for MDB2 statement objects
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Statement_Common
+{
+    // {{{ Variables (Properties)
+
+    var $db;
+    var $statement;
+    var $query;
+    var $result_types;
+    var $types;
+    var $values = array();
+    var $limit;
+    var $offset;
+    var $is_manip;
+
+    // }}}
+    // {{{ constructor: function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
+
+    /**
+     * Constructor
+     */
+    function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
+    {
+        $this->db = $db;
+        $this->statement = $statement;
+        $this->positions = $positions;
+        $this->query = $query;
+        $this->types = (array)$types;
+        $this->result_types = (array)$result_types;
+        $this->limit = $limit;
+        $this->is_manip = $is_manip;
+        $this->offset = $offset;
+    }
+
+    // }}}
+    // {{{ function bindValue($parameter, &$value, $type = null)
+
+    /**
+     * Set the value of a parameter of a prepared query.
+     *
+     * @param   int     the order number of the parameter in the query
+     *       statement. The order number of the first parameter is 1.
+     * @param   mixed   value that is meant to be assigned to specified
+     *       parameter. The type of the value depends on the $type argument.
+     * @param   string  specifies the type of the field
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function bindValue($parameter, $value, $type = null)
+    {
+        if (!is_numeric($parameter)) {
+            if (strpos($parameter, ':') === 0) {
+                $parameter = substr($parameter, 1);
+            }
+        }
+        if (!in_array($parameter, $this->positions)) {
+            return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
+        }
+        $this->values[$parameter] = $value;
+        if (null !== $type) {
+            $this->types[$parameter] = $type;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function bindValueArray($values, $types = null)
+
+    /**
+     * Set the values of multiple a parameter of a prepared query in bulk.
+     *
+     * @param   array   specifies all necessary information
+     *       for bindValue() the array elements must use keys corresponding to
+     *       the number of the position of the parameter.
+     * @param   array   specifies the types of the fields
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @see     bindParam()
+     */
+    function bindValueArray($values, $types = null)
+    {
+        $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
+        $parameters = array_keys($values);
+        $this->db->pushErrorHandling(PEAR_ERROR_RETURN);
+        $this->db->expectError(MDB2_ERROR_NOT_FOUND);
+        foreach ($parameters as $key => $parameter) {
+            $err = $this->bindValue($parameter, $values[$parameter], $types[$key]);
+            if (MDB2::isError($err)) {
+                if ($err->getCode() == MDB2_ERROR_NOT_FOUND) {
+                    //ignore (extra value for missing placeholder)
+                    continue;
+                }
+                $this->db->popExpect();
+                $this->db->popErrorHandling();
+                return $err;
+            }
+        }
+        $this->db->popExpect();
+        $this->db->popErrorHandling();
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function bindParam($parameter, &$value, $type = null)
+
+    /**
+     * Bind a variable to a parameter of a prepared query.
+     *
+     * @param   int     the order number of the parameter in the query
+     *       statement. The order number of the first parameter is 1.
+     * @param   mixed   variable that is meant to be bound to specified
+     *       parameter. The type of the value depends on the $type argument.
+     * @param   string  specifies the type of the field
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function bindParam($parameter, &$value, $type = null)
+    {
+        if (!is_numeric($parameter)) {
+            if (strpos($parameter, ':') === 0) {
+                $parameter = substr($parameter, 1);
+            }
+        }
+        if (!in_array($parameter, $this->positions)) {
+            return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
+        }
+        $this->values[$parameter] =& $value;
+        if (null !== $type) {
+            $this->types[$parameter] = $type;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function bindParamArray(&$values, $types = null)
+
+    /**
+     * Bind the variables of multiple a parameter of a prepared query in bulk.
+     *
+     * @param   array   specifies all necessary information
+     *       for bindParam() the array elements must use keys corresponding to
+     *       the number of the position of the parameter.
+     * @param   array   specifies the types of the fields
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @see     bindParam()
+     */
+    function bindParamArray(&$values, $types = null)
+    {
+        $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
+        $parameters = array_keys($values);
+        foreach ($parameters as $key => $parameter) {
+            $err = $this->bindParam($parameter, $values[$parameter], $types[$key]);
+            if (MDB2::isError($err)) {
+                return $err;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false)
+
+    /**
+     * Execute a prepared query statement.
+     *
+     * @param array specifies all necessary information
+     *              for bindParam() the array elements must use keys corresponding
+     *              to the number of the position of the parameter.
+     * @param mixed specifies which result class to use
+     * @param mixed specifies which class to wrap results in
+     *
+     * @return mixed MDB2_Result or integer (affected rows) on success,
+     *               a MDB2 error on failure
+     * @access public
+     */
+    function execute($values = null, $result_class = true, $result_wrap_class = false)
+    {
+        if (null === $this->positions) {
+            return MDB2::raiseError(MDB2_ERROR, null, null,
+                'Prepared statement has already been freed', __FUNCTION__);
+        }
+
+        $values = (array)$values;
+        if (!empty($values)) {
+            $err = $this->bindValueArray($values);
+            if (MDB2::isError($err)) {
+                return MDB2::raiseError(MDB2_ERROR, null, null,
+                                            'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__);
+            }
+        }
+        $result = $this->_execute($result_class, $result_wrap_class);
+        return $result;
+    }
+
+    // }}}
+    // {{{ function _execute($result_class = true, $result_wrap_class = false)
+
+    /**
+     * Execute a prepared query statement helper method.
+     *
+     * @param   mixed   specifies which result class to use
+     * @param   mixed   specifies which class to wrap results in
+     *
+     * @return mixed MDB2_Result or integer (affected rows) on success,
+     *               a MDB2 error on failure
+     * @access  private
+     */
+    function _execute($result_class = true, $result_wrap_class = false)
+    {
+        $this->last_query = $this->query;
+        $query = '';
+        $last_position = 0;
+        foreach ($this->positions as $current_position => $parameter) {
+            if (!array_key_exists($parameter, $this->values)) {
+                return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                    'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
+            }
+            $value = $this->values[$parameter];
+            $query.= substr($this->query, $last_position, $current_position - $last_position);
+            if (!isset($value)) {
+                $value_quoted = 'NULL';
+            } else {
+                $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null;
+                $value_quoted = $this->db->quote($value, $type);
+                if (MDB2::isError($value_quoted)) {
+                    return $value_quoted;
+                }
+            }
+            $query.= $value_quoted;
+            $last_position = $current_position + 1;
+        }
+        $query.= substr($this->query, $last_position);
+
+        $this->db->offset = $this->offset;
+        $this->db->limit = $this->limit;
+        if ($this->is_manip) {
+            $result = $this->db->exec($query);
+        } else {
+            $result = $this->db->query($query, $this->result_types, $result_class, $result_wrap_class);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ function free()
+
+    /**
+     * Release resources allocated for the specified prepared query.
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function free()
+    {
+        if (null === $this->positions) {
+            return MDB2::raiseError(MDB2_ERROR, null, null,
+                'Prepared statement has already been freed', __FUNCTION__);
+        }
+
+        $this->statement = null;
+        $this->positions = null;
+        $this->query = null;
+        $this->types = null;
+        $this->result_types = null;
+        $this->limit = null;
+        $this->is_manip = null;
+        $this->offset = null;
+        $this->values = null;
+
+        return MDB2_OK;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class MDB2_Module_Common
+
+/**
+ * The common modules class for MDB2 module objects
+ *
+ * @package     MDB2
+ * @category    Database
+ * @author      Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Module_Common
+{
+    // {{{ Variables (Properties)
+
+    /**
+     * contains the key to the global MDB2 instance array of the associated
+     * MDB2 instance
+     *
+     * @var     int
+     * @access  protected
+     */
+    protected $db_index;
+
+    // }}}
+    // {{{ constructor: function __construct($db_index)
+
+    /**
+     * Constructor
+     */
+    function __construct($db_index)
+    {
+        $this->db_index = $db_index;
+    }
+
+    // }}}
+    // {{{ function getDBInstance()
+
+    /**
+     * Get the instance of MDB2 associated with the module instance
+     *
+     * @return  object  MDB2 instance or a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function getDBInstance()
+    {
+        if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            $result = $GLOBALS['_MDB2_databases'][$this->db_index];
+        } else {
+            $result = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'could not find MDB2 instance');
+        }
+        return $result;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ function MDB2_closeOpenTransactions()
+
+/**
+ * Close any open transactions form persistent connections
+ *
+ * @return  void
+ *
+ * @access  public
+ */
+
+function MDB2_closeOpenTransactions()
+{
+    reset($GLOBALS['_MDB2_databases']);
+    while (next($GLOBALS['_MDB2_databases'])) {
+        $key = key($GLOBALS['_MDB2_databases']);
+        if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent
+            && $GLOBALS['_MDB2_databases'][$key]->in_transaction
+        ) {
+            $GLOBALS['_MDB2_databases'][$key]->rollback();
+        }
+    }
+}
+
+// }}}
+// {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null)
+
+/**
+ * default debug output handler
+ *
+ * @param   object  reference to an MDB2 database object
+ * @param   string  usually the method name that triggered the debug call:
+ *                  for example 'query', 'prepare', 'execute', 'parameters',
+ *                  'beginTransaction', 'commit', 'rollback'
+ * @param   string  message that should be appended to the debug variable
+ * @param   array   contains context information about the debug() call
+ *                  common keys are: is_manip, time, result etc.
+ *
+ * @return  void|string optionally return a modified message, this allows
+ *                      rewriting a query before being issued or prepared
+ *
+ * @access  public
+ */
+function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array())
+{
+    $db->debug_output.= $scope.'('.$db->db_index.'): ';
+    $db->debug_output.= $message.$db->getOption('log_line_break');
+    return $message;
+}
+
+// }}}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Date.php b/WEB-INF/lib/pear/MDB2/Date.php
new file mode 100644 (file)
index 0000000..e867e48
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Date.php 327316 2012-08-27 15:17:02Z danielc $
+//
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+
+/**
+ * Several methods to convert the MDB2 native timestamp format (ISO based)
+ * to and from data structures that are convenient to worth with in side of php.
+ * For more complex date arithmetic please take a look at the Date package in PEAR
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Date
+{
+    // {{{ mdbNow()
+
+    /**
+     * return the current datetime
+     *
+     * @return string current datetime in the MDB2 format
+     * @access public
+     */
+    public static function mdbNow()
+    {
+        return date('Y-m-d H:i:s');
+    }
+    // }}}
+
+    // {{{ mdbToday()
+
+    /**
+     * return the current date
+     *
+     * @return string current date in the MDB2 format
+     * @access public
+     */
+    public static function mdbToday()
+    {
+        return date('Y-m-d');
+    }
+    // }}}
+
+    // {{{ mdbTime()
+
+    /**
+     * return the current time
+     *
+     * @return string current time in the MDB2 format
+     * @access public
+     */
+    public static function mdbTime()
+    {
+        return date('H:i:s');
+    }
+    // }}}
+
+    // {{{ date2Mdbstamp()
+
+    /**
+     * convert a date into a MDB2 timestamp
+     *
+     * @param int hour of the date
+     * @param int minute of the date
+     * @param int second of the date
+     * @param int month of the date
+     * @param int day of the date
+     * @param int year of the date
+     *
+     * @return string a valid MDB2 timestamp
+     * @access public
+     */
+    public static function date2Mdbstamp($hour = null, $minute = null, $second = null,
+        $month = null, $day = null, $year = null)
+    {
+        return MDB2_Date::unix2Mdbstamp(mktime($hour, $minute, $second, $month, $day, $year, -1));
+    }
+    // }}}
+
+    // {{{ unix2Mdbstamp()
+
+    /**
+     * convert a unix timestamp into a MDB2 timestamp
+     *
+     * @param int a valid unix timestamp
+     *
+     * @return string a valid MDB2 timestamp
+     * @access public
+     */
+    public static function unix2Mdbstamp($unix_timestamp)
+    {
+        return date('Y-m-d H:i:s', $unix_timestamp);
+    }
+    // }}}
+
+    // {{{ mdbstamp2Unix()
+
+    /**
+     * convert a MDB2 timestamp into a unix timestamp
+     *
+     * @param int a valid MDB2 timestamp
+     * @return string unix timestamp with the time stored in the MDB2 format
+     *
+     * @access public
+     */
+    public static function mdbstamp2Unix($mdb_timestamp)
+    {
+        $arr = MDB2_Date::mdbstamp2Date($mdb_timestamp);
+
+        return mktime($arr['hour'], $arr['minute'], $arr['second'], $arr['month'], $arr['day'], $arr['year'], -1);
+    }
+    // }}}
+
+    // {{{ mdbstamp2Date()
+
+    /**
+     * convert a MDB2 timestamp into an array containing all
+     * values necessary to pass to php's date() function
+     *
+     * @param int a valid MDB2 timestamp
+     *
+     * @return array with the time split
+     * @access public
+     */
+    public static function mdbstamp2Date($mdb_timestamp)
+    {
+        list($arr['year'], $arr['month'], $arr['day'], $arr['hour'], $arr['minute'], $arr['second']) =
+            sscanf($mdb_timestamp, "%04u-%02u-%02u %02u:%02u:%02u");
+        return $arr;
+    }
+    // }}}
+}
+
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Datatype/Common.php b/WEB-INF/lib/pear/MDB2/Driver/Datatype/Common.php
new file mode 100644 (file)
index 0000000..a06e37c
--- /dev/null
@@ -0,0 +1,1847 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Common.php 328137 2012-10-25 02:26:35Z danielc $
+
+require_once 'MDB2/LOB.php';
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+
+/**
+ * MDB2_Driver_Common: Base class that is extended by each MDB2 driver
+ *
+ * To load this module in the MDB2 object:
+ * $mdb->loadModule('Datatype');
+ *
+ * @package MDB2
+ * @category Database
+ * @author Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Datatype_Common extends MDB2_Module_Common
+{
+    var $valid_default_values = array(
+        'text'      => '',
+        'boolean'   => true,
+        'integer'   => 0,
+        'decimal'   => 0.0,
+        'float'     => 0.0,
+        'timestamp' => '1970-01-01 00:00:00',
+        'time'      => '00:00:00',
+        'date'      => '1970-01-01',
+        'clob'      => '',
+        'blob'      => '',
+    );
+
+    /**
+     * contains all LOB objects created with this MDB2 instance
+     * @var array
+     * @access protected
+     */
+    var $lobs = array();
+
+    // }}}
+    // {{{ getValidTypes()
+
+    /**
+     * Get the list of valid types
+     *
+     * This function returns an array of valid types as keys with the values
+     * being possible default values for all native datatypes and mapped types
+     * for custom datatypes.
+     *
+     * @return mixed array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getValidTypes()
+    {
+        $types = $this->valid_default_values;
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        if (!empty($db->options['datatype_map'])) {
+            foreach ($db->options['datatype_map'] as $type => $mapped_type) {
+                if (array_key_exists($mapped_type, $types)) {
+                    $types[$type] = $types[$mapped_type];
+                } elseif (!empty($db->options['datatype_map_callback'][$type])) {
+                    $parameter = array('type' => $type, 'mapped_type' => $mapped_type);
+                    $default =  call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+                    $types[$type] = $default;
+                }
+            }
+        }
+        return $types;
+    }
+
+    // }}}
+    // {{{ checkResultTypes()
+
+    /**
+     * Define the list of types to be associated with the columns of a given
+     * result set.
+     *
+     * This function may be called before invoking fetchRow(), fetchOne()
+     * fetchCole() and fetchAll() so that the necessary data type
+     * conversions are performed on the data to be retrieved by them. If this
+     * function is not called, the type of all result set columns is assumed
+     * to be text, thus leading to not perform any conversions.
+     *
+     * @param array $types array variable that lists the
+     *       data types to be expected in the result set columns. If this array
+     *       contains less types than the number of columns that are returned
+     *       in the result set, the remaining columns are assumed to be of the
+     *       type text. Currently, the types clob and blob are not fully
+     *       supported.
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function checkResultTypes($types)
+    {
+        $types = is_array($types) ? $types : array($types);
+        foreach ($types as $key => $type) {
+            if (!isset($this->valid_default_values[$type])) {
+                $db = $this->getDBInstance();
+                if (MDB2::isError($db)) {
+                    return $db;
+                }
+                if (empty($db->options['datatype_map'][$type])) {
+                    return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                        $type.' for '.$key.' is not a supported column type', __FUNCTION__);
+                }
+            }
+        }
+        return $types;
+    }
+
+    // }}}
+    // {{{ _baseConvertResult()
+
+    /**
+     * General type conversion method
+     *
+     * @param mixed   $value reference to a value to be converted
+     * @param string  $type  specifies which type to convert to
+     * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text
+     * @return object an MDB2 error on failure
+     * @access protected
+     */
+    function _baseConvertResult($value, $type, $rtrim = true)
+    {
+        switch ($type) {
+        case 'text':
+            if ($rtrim) {
+                $value = rtrim($value);
+            }
+            return $value;
+        case 'integer':
+            return intval($value);
+        case 'boolean':
+            return !empty($value);
+        case 'decimal':
+            return $value;
+        case 'float':
+            return doubleval($value);
+        case 'date':
+            return $value;
+        case 'time':
+            return $value;
+        case 'timestamp':
+            return $value;
+        case 'clob':
+        case 'blob':
+            $this->lobs[] = array(
+                'buffer' => null,
+                'position' => 0,
+                'lob_index' => null,
+                'endOfLOB' => false,
+                'resource' => $value,
+                'value' => null,
+                'loaded' => false,
+            );
+            end($this->lobs);
+            $lob_index = key($this->lobs);
+            $this->lobs[$lob_index]['lob_index'] = $lob_index;
+            return fopen('MDB2LOB://'.$lob_index.'@'.$this->db_index, 'r+');
+        }
+
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_INVALID, null, null,
+            'attempt to convert result value to an unknown type :' . $type, __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ convertResult()
+
+    /**
+     * Convert a value to a RDBMS indipendent MDB2 type
+     *
+     * @param mixed   $value value to be converted
+     * @param string  $type  specifies which type to convert to
+     * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text
+     * @return mixed converted value
+     * @access public
+     */
+    function convertResult($value, $type, $rtrim = true)
+    {
+        if (null === $value) {
+            return null;
+        }
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        if (!empty($db->options['datatype_map'][$type])) {
+            $type = $db->options['datatype_map'][$type];
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('type' => $type, 'value' => $value, 'rtrim' => $rtrim);
+                return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+            }
+        }
+        return $this->_baseConvertResult($value, $type, $rtrim);
+    }
+
+    // }}}
+    // {{{ convertResultRow()
+
+    /**
+     * Convert a result row
+     *
+     * @param array   $types
+     * @param array   $row   specifies the types to convert to
+     * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text
+     * @return mixed MDB2_OK on success, an MDB2 error on failure
+     * @access public
+     */
+    function convertResultRow($types, $row, $rtrim = true)
+    {
+        //$types = $this->_sortResultFieldTypes(array_keys($row), $types);
+        $keys = array_keys($row);
+        if (is_int($keys[0])) {
+            $types = $this->_sortResultFieldTypes($keys, $types);
+        }
+        foreach ($row as $key => $value) {
+            if (empty($types[$key])) {
+                continue;
+            }
+            $value = $this->convertResult($row[$key], $types[$key], $rtrim);
+            if (MDB2::isError($value)) {
+                return $value;
+            }
+            $row[$key] = $value;
+        }
+        return $row;
+    }
+
+    // }}}
+    // {{{ _sortResultFieldTypes()
+
+    /**
+     * convert a result row
+     *
+     * @param array $types
+     * @param array $row specifies the types to convert to
+     * @param bool   $rtrim   if to rtrim text values or not
+     * @return mixed MDB2_OK on success,  a MDB2 error on failure
+     * @access public
+     */
+    function _sortResultFieldTypes($columns, $types)
+    {
+        $n_cols = count($columns);
+        $n_types = count($types);
+        if ($n_cols > $n_types) {
+            for ($i= $n_cols - $n_types; $i >= 0; $i--) {
+                $types[] = null;
+            }
+        }
+        $sorted_types = array();
+        foreach ($columns as $col) {
+            $sorted_types[$col] = null;
+        }
+        foreach ($types as $name => $type) {
+            if (array_key_exists($name, $sorted_types)) {
+                $sorted_types[$name] = $type;
+                unset($types[$name]);
+            }
+        }
+        // if there are left types in the array, fill the null values of the
+        // sorted array with them, in order.
+        if (count($types)) {
+            reset($types);
+            foreach (array_keys($sorted_types) as $k) {
+                if (null === $sorted_types[$k]) {
+                    $sorted_types[$k] = current($types);
+                    next($types);
+                }
+            }
+        }
+        return $sorted_types;
+    }
+
+    // }}}
+    // {{{ getDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare
+     * of the given type
+     *
+     * @param string $type type to which the value should be converted to
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  definition of the field
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access public
+     */
+    function getDeclaration($type, $name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!empty($db->options['datatype_map'][$type])) {
+            $type = $db->options['datatype_map'][$type];
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('type' => $type, 'name' => $name, 'field' => $field);
+                return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+            }
+            $field['type'] = $type;
+        }
+
+        if (!method_exists($this, "_get{$type}Declaration")) {
+            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'type not defined: '.$type, __FUNCTION__);
+        }
+        return $this->{"_get{$type}Declaration"}($name, $field);
+    }
+
+    // }}}
+    // {{{ getTypeDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an text type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param array $field  associative array with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      length
+     *          Integer value that determines the maximum length of the text
+     *          field. If this argument is missing the field should be
+     *          declared to have the longest length allowed by the DBMS.
+     *
+     *      default
+     *          Text value to be used as default for this field.
+     *
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *      declare the specified field.
+     * @access public
+     */
+    function getTypeDeclaration($field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        switch ($field['type']) {
+        case 'text':
+            $length = !empty($field['length']) ? $field['length'] : $db->options['default_text_field_length'];
+            $fixed = !empty($field['fixed']) ? $field['fixed'] : false;
+            return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')')
+                : ($length ? 'VARCHAR('.$length.')' : 'TEXT');
+        case 'clob':
+            return 'TEXT';
+        case 'blob':
+            return 'TEXT';
+        case 'integer':
+            return 'INT';
+        case 'boolean':
+            return 'INT';
+        case 'date':
+            return 'CHAR ('.strlen('YYYY-MM-DD').')';
+        case 'time':
+            return 'CHAR ('.strlen('HH:MM:SS').')';
+        case 'timestamp':
+            return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')';
+        case 'float':
+            return 'TEXT';
+        case 'decimal':
+            return 'TEXT';
+        }
+        return '';
+    }
+
+    // }}}
+    // {{{ _getDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a generic type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name   name the field to be declared.
+     * @param array  $field  associative array with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      length
+     *          Integer value that determines the maximum length of the text
+     *          field. If this argument is missing the field should be
+     *          declared to have the longest length allowed by the DBMS.
+     *
+     *      default
+     *          Text value to be used as default for this field.
+     *
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     *      charset
+     *          Text value with the default CHARACTER SET for this field.
+     *      collation
+     *          Text value with the default COLLATION for this field.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *      declare the specified field, or a MDB2_Error on failure
+     * @access protected
+     */
+    function _getDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $declaration_options = $db->datatype->_getDeclarationOptions($field);
+        if (MDB2::isError($declaration_options)) {
+            return $declaration_options;
+        }
+        return $name.' '.$this->getTypeDeclaration($field).$declaration_options;
+    }
+
+    // }}}
+    // {{{ _getDeclarationOptions()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a generic type
+     * field to be used in statement like CREATE TABLE, without the field name
+     * and type values (ie. just the character set, default value, if the
+     * field is permitted to be NULL or not, and the collation options).
+     *
+     * @param array  $field  associative array with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      default
+     *          Text value to be used as default for this field.
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     *      charset
+     *          Text value with the default CHARACTER SET for this field.
+     *      collation
+     *          Text value with the default COLLATION for this field.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *      declare the specified field's options.
+     * @access protected
+     */
+    function _getDeclarationOptions($field)
+    {
+        $charset = empty($field['charset']) ? '' :
+            ' '.$this->_getCharsetFieldDeclaration($field['charset']);
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $default = '';
+        if (array_key_exists('default', $field)) {
+            if ($field['default'] === '') {
+                $db = $this->getDBInstance();
+                if (MDB2::isError($db)) {
+                    return $db;
+                }
+                $valid_default_values = $this->getValidTypes();
+                $field['default'] = $valid_default_values[$field['type']];
+                if ($field['default'] === '' && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)) {
+                    $field['default'] = ' ';
+                }
+            }
+            if (null !== $field['default']) {
+                $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']);
+            }
+        }
+
+        $collation = empty($field['collation']) ? '' :
+            ' '.$this->_getCollationFieldDeclaration($field['collation']);
+
+        return $charset.$default.$notnull.$collation;
+    }
+
+    // }}}
+    // {{{ _getCharsetFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $charset   name of the charset
+     * @return string  DBMS specific SQL code portion needed to set the CHARACTER SET
+     *                 of a field declaration.
+     */
+    function _getCharsetFieldDeclaration($charset)
+    {
+        return '';
+    }
+
+    // }}}
+    // {{{ _getCollationFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the COLLATION
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $collation   name of the collation
+     * @return string  DBMS specific SQL code portion needed to set the COLLATION
+     *                 of a field declaration.
+     */
+    function _getCollationFieldDeclaration($collation)
+    {
+        return '';
+    }
+
+    // }}}
+    // {{{ _getIntegerDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an integer type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       unsigned
+     *           Boolean flag that indicates whether the field should be
+     *           declared as unsigned integer if possible.
+     *
+     *       default
+     *           Integer value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getIntegerDeclaration($name, $field)
+    {
+        if (!empty($field['unsigned'])) {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+
+            $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer";
+        }
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getTextDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an text type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       length
+     *           Integer value that determines the maximum length of the text
+     *           field. If this argument is missing the field should be
+     *           declared to have the longest length allowed by the DBMS.
+     *
+     *       default
+     *           Text value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getTextDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getCLOBDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an character
+     * large object type field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *        of the field being declared as array indexes. Currently, the types
+     *        of supported field properties are as follows:
+     *
+     *        length
+     *            Integer value that determines the maximum length of the large
+     *            object field. If this argument is missing the field should be
+     *            declared to have the longest length allowed by the DBMS.
+     *
+     *        notnull
+     *            Boolean flag that indicates whether this field is constrained
+     *            to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *        declare the specified field.
+     * @access public
+     */
+    function _getCLOBDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$notnull;
+    }
+
+    // }}}
+    // {{{ _getBLOBDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an binary large
+     * object type field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *        of the field being declared as array indexes. Currently, the types
+     *        of supported field properties are as follows:
+     *
+     *        length
+     *            Integer value that determines the maximum length of the large
+     *            object field. If this argument is missing the field should be
+     *            declared to have the longest length allowed by the DBMS.
+     *
+     *        notnull
+     *            Boolean flag that indicates whether this field is constrained
+     *            to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *        declare the specified field.
+     * @access protected
+     */
+    function _getBLOBDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$notnull;
+    }
+
+    // }}}
+    // {{{ _getBooleanDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a boolean type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Boolean value to be used as default for this field.
+     *
+     *       notnullL
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getBooleanDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getDateDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a date type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Date value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getDateDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getTimestampDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a timestamp
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Timestamp value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getTimestampDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getTimeDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a time
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Time value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getTimeDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getFloatDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a float type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Float value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getFloatDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getDecimalDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare a decimal type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string $name name the field to be declared.
+     * @param array $field associative array with the name of the properties
+     *       of the field being declared as array indexes. Currently, the types
+     *       of supported field properties are as follows:
+     *
+     *       default
+     *           Decimal value to be used as default for this field.
+     *
+     *       notnull
+     *           Boolean flag that indicates whether this field is constrained
+     *           to not be set to null.
+     * @return string DBMS specific SQL code portion that should be used to
+     *       declare the specified field.
+     * @access protected
+     */
+    function _getDecimalDeclaration($name, $field)
+    {
+        return $this->_getDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ compareDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access public
+     */
+    function compareDefinition($current, $previous)
+    {
+        $type = !empty($current['type']) ? $current['type'] : null;
+
+        if (!method_exists($this, "_compare{$type}Definition")) {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('current' => $current, 'previous' => $previous);
+                $change =  call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+                return $change;
+            }
+            return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'type "'.$current['type'].'" is not yet supported', __FUNCTION__);
+        }
+
+        if (empty($previous['type']) || $previous['type'] != $type) {
+            return $current;
+        }
+
+        $change = $this->{"_compare{$type}Definition"}($current, $previous);
+
+        if ($previous['type'] != $type) {
+            $change['type'] = true;
+        }
+
+        $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false;
+        $notnull = !empty($current['notnull']) ? $current['notnull'] : false;
+        if ($previous_notnull != $notnull) {
+            $change['notnull'] = true;
+        }
+
+        $previous_default = array_key_exists('default', $previous) ? $previous['default'] :
+            null;
+        $default = array_key_exists('default', $current) ? $current['default'] :
+            null;
+        if ($previous_default !== $default) {
+            $change['default'] = true;
+        }
+
+        return $change;
+    }
+
+    // }}}
+    // {{{ _compareIntegerDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an integer field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareIntegerDefinition($current, $previous)
+    {
+        $change = array();
+        $previous_length = !empty($previous['length']) ? $previous['length'] : 4;
+        $length = !empty($current['length']) ? $current['length'] : 4;
+        if ($previous_length != $length) {
+            $change['length'] = $length;
+        }
+        $previous_unsigned = !empty($previous['unsigned']) ? $previous['unsigned'] : false;
+        $unsigned = !empty($current['unsigned']) ? $current['unsigned'] : false;
+        if ($previous_unsigned != $unsigned) {
+            $change['unsigned'] = true;
+        }
+        $previous_autoincrement = !empty($previous['autoincrement']) ? $previous['autoincrement'] : false;
+        $autoincrement = !empty($current['autoincrement']) ? $current['autoincrement'] : false;
+        if ($previous_autoincrement != $autoincrement) {
+            $change['autoincrement'] = true;
+        }
+        return $change;
+    }
+
+    // }}}
+    // {{{ _compareTextDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an text field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareTextDefinition($current, $previous)
+    {
+        $change = array();
+        $previous_length = !empty($previous['length']) ? $previous['length'] : 0;
+        $length = !empty($current['length']) ? $current['length'] : 0;
+        if ($previous_length != $length) {
+            $change['length'] = true;
+        }
+        $previous_fixed = !empty($previous['fixed']) ? $previous['fixed'] : 0;
+        $fixed = !empty($current['fixed']) ? $current['fixed'] : 0;
+        if ($previous_fixed != $fixed) {
+            $change['fixed'] = true;
+        }
+        return $change;
+    }
+
+    // }}}
+    // {{{ _compareCLOBDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an CLOB field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareCLOBDefinition($current, $previous)
+    {
+        return $this->_compareTextDefinition($current, $previous);
+    }
+
+    // }}}
+    // {{{ _compareBLOBDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an BLOB field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareBLOBDefinition($current, $previous)
+    {
+        return $this->_compareTextDefinition($current, $previous);
+    }
+
+    // }}}
+    // {{{ _compareDateDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an date field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareDateDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ _compareTimeDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an time field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareTimeDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ _compareTimestampDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an timestamp field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareTimestampDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ _compareBooleanDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an boolean field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareBooleanDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ _compareFloatDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an float field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareFloatDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ _compareDecimalDefinition()
+
+    /**
+     * Obtain an array of changes that may need to applied to an decimal field
+     *
+     * @param array $current new definition
+     * @param array  $previous old definition
+     * @return array  containing all changes that will need to be applied
+     * @access protected
+     */
+    function _compareDecimalDefinition($current, $previous)
+    {
+        return array();
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param string $type type to which the value should be converted to
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access public
+     */
+    function quote($value, $type = null, $quote = true, $escape_wildcards = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if ((null === $value)
+            || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)
+        ) {
+            if (!$quote) {
+                return null;
+            }
+            return 'NULL';
+        }
+
+        if (null === $type) {
+            switch (gettype($value)) {
+            case 'integer':
+                $type = 'integer';
+                break;
+            case 'double':
+                // todo: default to decimal as float is quite unusual
+                // $type = 'float';
+                $type = 'decimal';
+                break;
+            case 'boolean':
+                $type = 'boolean';
+                break;
+            case 'array':
+                 $value = serialize($value);
+            case 'object':
+                 $type = 'text';
+                break;
+            default:
+                if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) {
+                    $type = 'timestamp';
+                } elseif (preg_match('/^\d{2}:\d{2}$/', $value)) {
+                    $type = 'time';
+                } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
+                    $type = 'date';
+                } else {
+                    $type = 'text';
+                }
+                break;
+            }
+        } elseif (!empty($db->options['datatype_map'][$type])) {
+            $type = $db->options['datatype_map'][$type];
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('type' => $type, 'value' => $value, 'quote' => $quote, 'escape_wildcards' => $escape_wildcards);
+                return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+            }
+        }
+
+        if (!method_exists($this, "_quote{$type}")) {
+            return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'type not defined: '.$type, __FUNCTION__);
+        }
+        $value = $this->{"_quote{$type}"}($value, $quote, $escape_wildcards);
+        if ($quote && $escape_wildcards && $db->string_quoting['escape_pattern']
+            && $db->string_quoting['escape'] !== $db->string_quoting['escape_pattern']
+        ) {
+            $value.= $this->patternEscapeString();
+        }
+        return $value;
+    }
+
+    // }}}
+    // {{{ _quoteInteger()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteInteger($value, $quote, $escape_wildcards)
+    {
+        return (int)$value;
+    }
+
+    // }}}
+    // {{{ _quoteText()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that already contains any DBMS specific
+     *       escaped character sequences.
+     * @access protected
+     */
+    function _quoteText($value, $quote, $escape_wildcards)
+    {
+        if (!$quote) {
+            return $value;
+        }
+
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $value = $db->escape($value, $escape_wildcards);
+        if (MDB2::isError($value)) {
+            return $value;
+        }
+        return "'".$value."'";
+    }
+
+    // }}}
+    // {{{ _readFile()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _readFile($value)
+    {
+        $close = false;
+        if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
+            $close = true;
+            if (strtolower($match[1]) == 'file://') {
+                $value = $match[2];
+            }
+            $value = @fopen($value, 'r');
+        }
+
+        if (is_resource($value)) {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+
+            $fp = $value;
+            $value = '';
+            while (!@feof($fp)) {
+                $value.= @fread($fp, $db->options['lob_buffer_length']);
+            }
+            if ($close) {
+                @fclose($fp);
+            }
+        }
+
+        return $value;
+    }
+
+    // }}}
+    // {{{ _quoteLOB()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteLOB($value, $quote, $escape_wildcards)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        if ($db->options['lob_allow_url_include']) {
+            $value = $this->_readFile($value);
+            if (MDB2::isError($value)) {
+                return $value;
+            }
+        }
+        return $this->_quoteText($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteCLOB()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteCLOB($value, $quote, $escape_wildcards)
+    {
+        return $this->_quoteLOB($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteBLOB()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteBLOB($value, $quote, $escape_wildcards)
+    {
+        return $this->_quoteLOB($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteBoolean()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteBoolean($value, $quote, $escape_wildcards)
+    {
+        return ($value ? 1 : 0);
+    }
+
+    // }}}
+    // {{{ _quoteDate()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteDate($value, $quote, $escape_wildcards)
+    {
+        if ($value === 'CURRENT_DATE') {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+            if (isset($db->function) && is_object($this->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) {
+                return $db->function->now('date');
+            }
+            return 'CURRENT_DATE';
+        }
+        return $this->_quoteText($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteTimestamp()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteTimestamp($value, $quote, $escape_wildcards)
+    {
+        if ($value === 'CURRENT_TIMESTAMP') {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+            if (isset($db->function) && is_object($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) {
+                return $db->function->now('timestamp');
+            }
+            return 'CURRENT_TIMESTAMP';
+        }
+        return $this->_quoteText($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteTime()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     *       compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteTime($value, $quote, $escape_wildcards)
+    {
+        if ($value === 'CURRENT_TIME') {
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+            if (isset($db->function) && is_object($this->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) {
+                return $db->function->now('time');
+            }
+            return 'CURRENT_TIME';
+        }
+        return $this->_quoteText($value, $quote, $escape_wildcards);
+    }
+
+    // }}}
+    // {{{ _quoteFloat()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteFloat($value, $quote, $escape_wildcards)
+    {
+        if (preg_match('/^(.*)e([-+])(\d+)$/i', $value, $matches)) {
+            $decimal = $this->_quoteDecimal($matches[1], $quote, $escape_wildcards);
+            $sign = $matches[2];
+            $exponent = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
+            $value = $decimal.'E'.$sign.$exponent;
+        } else {
+            $value = $this->_quoteDecimal($value, $quote, $escape_wildcards);
+        }
+        return $value;
+    }
+
+    // }}}
+    // {{{ _quoteDecimal()
+
+    /**
+     * Convert a text value into a DBMS specific format that is suitable to
+     * compose query statements.
+     *
+     * @param string $value text string value that is intended to be converted.
+     * @param bool $quote determines if the value should be quoted and escaped
+     * @param bool $escape_wildcards if to escape escape wildcards
+     * @return string text string that represents the given argument value in
+     *       a DBMS specific format.
+     * @access protected
+     */
+    function _quoteDecimal($value, $quote, $escape_wildcards)
+    {
+        $value = (string)$value;
+        $value = preg_replace('/[^\d\.,\-+eE]/', '', $value);
+        if (preg_match('/[^\.\d]/', $value)) {
+            if (strpos($value, ',')) {
+                // 1000,00
+                if (!strpos($value, '.')) {
+                    // convert the last "," to a "."
+                    $value = strrev(str_replace(',', '.', strrev($value)));
+                // 1.000,00
+                } elseif (strpos($value, '.') && strpos($value, '.') < strpos($value, ',')) {
+                    $value = str_replace('.', '', $value);
+                    // convert the last "," to a "."
+                    $value = strrev(str_replace(',', '.', strrev($value)));
+                // 1,000.00
+                } else {
+                    $value = str_replace(',', '', $value);
+                }
+            }
+        }
+        return $value;
+    }
+
+    // }}}
+    // {{{ writeLOBToFile()
+
+    /**
+     * retrieve LOB from the database
+     *
+     * @param resource $lob stream handle
+     * @param string $file name of the file into which the LOb should be fetched
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access protected
+     */
+    function writeLOBToFile($lob, $file)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) {
+            if ($match[1] == 'file://') {
+                $file = $match[2];
+            }
+        }
+
+        $fp = @fopen($file, 'wb');
+        while (!@feof($lob)) {
+            $result = @fread($lob, $db->options['lob_buffer_length']);
+            $read = strlen($result);
+            if (@fwrite($fp, $result, $read) != $read) {
+                @fclose($fp);
+                return $db->raiseError(MDB2_ERROR, null, null,
+                    'could not write to the output file', __FUNCTION__);
+            }
+        }
+        @fclose($fp);
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _retrieveLOB()
+
+    /**
+     * retrieve LOB from the database
+     *
+     * @param array $lob array
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access protected
+     */
+    function _retrieveLOB(&$lob)
+    {
+        if (null === $lob['value']) {
+            $lob['value'] = $lob['resource'];
+        }
+        $lob['loaded'] = true;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ readLOB()
+
+    /**
+     * Read data from large object input stream.
+     *
+     * @param resource $lob stream handle
+     * @param string $data reference to a variable that will hold data
+     *                          to be read from the large object input stream
+     * @param integer $length    value that indicates the largest ammount ofdata
+     *                          to be read from the large object input stream.
+     * @return mixed the effective number of bytes read from the large object
+     *                      input stream on sucess or an MDB2 error object.
+     * @access public
+     * @see endOfLOB()
+     */
+    function _readLOB($lob, $length)
+    {
+        return substr($lob['value'], $lob['position'], $length);
+    }
+
+    // }}}
+    // {{{ _endOfLOB()
+
+    /**
+     * Determine whether it was reached the end of the large object and
+     * therefore there is no more data to be read for the its input stream.
+     *
+     * @param array $lob array
+     * @return mixed true or false on success, a MDB2 error on failure
+     * @access protected
+     */
+    function _endOfLOB($lob)
+    {
+        return $lob['endOfLOB'];
+    }
+
+    // }}}
+    // {{{ destroyLOB()
+
+    /**
+     * Free any resources allocated during the lifetime of the large object
+     * handler object.
+     *
+     * @param resource $lob stream handle
+     * @access public
+     */
+    function destroyLOB($lob)
+    {
+        $lob_data = stream_get_meta_data($lob);
+        $lob_index = $lob_data['wrapper_data']->lob_index;
+        fclose($lob);
+        if (isset($this->lobs[$lob_index])) {
+            $this->_destroyLOB($this->lobs[$lob_index]);
+            unset($this->lobs[$lob_index]);
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _destroyLOB()
+
+    /**
+     * Free any resources allocated during the lifetime of the large object
+     * handler object.
+     *
+     * @param array $lob array
+     * @access private
+     */
+    function _destroyLOB(&$lob)
+    {
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ implodeArray()
+
+    /**
+     * apply a type to all values of an array and return as a comma seperated string
+     * useful for generating IN statements
+     *
+     * @access public
+     *
+     * @param array $array data array
+     * @param string $type determines type of the field
+     *
+     * @return string comma seperated values
+     */
+    function implodeArray($array, $type = false)
+    {
+        if (!is_array($array) || empty($array)) {
+            return 'NULL';
+        }
+        if ($type) {
+            foreach ($array as $value) {
+                $return[] = $this->quote($value, $type);
+            }
+        } else {
+            $return = $array;
+        }
+        return implode(', ', $return);
+    }
+
+    // }}}
+    // {{{ matchPattern()
+
+    /**
+     * build a pattern matching string
+     *
+     * @access public
+     *
+     * @param array $pattern even keys are strings, odd are patterns (% and _)
+     * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future)
+     * @param string $field optional field name that is being matched against
+     *                  (might be required when emulating ILIKE)
+     *
+     * @return string SQL pattern
+     */
+    function matchPattern($pattern, $operator = null, $field = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $match = '';
+        if (null !== $operator) {
+            $operator = strtoupper($operator);
+            switch ($operator) {
+            // case insensitive
+            case 'ILIKE':
+                if (null === $field) {
+                    return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                        'case insensitive LIKE matching requires passing the field name', __FUNCTION__);
+                }
+                $db->loadModule('Function', null, true);
+                $match = $db->function->lower($field).' LIKE ';
+                break;
+            case 'NOT ILIKE':
+                if (null === $field) {
+                    return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                        'case insensitive NOT ILIKE matching requires passing the field name', __FUNCTION__);
+                }
+                $db->loadModule('Function', null, true);
+                $match = $db->function->lower($field).' NOT LIKE ';
+                break;
+            // case sensitive
+            case 'LIKE':
+                $match = (null === $field) ? 'LIKE ' : ($field.' LIKE ');
+                break;
+            case 'NOT LIKE':
+                $match = (null === $field) ? 'NOT LIKE ' : ($field.' NOT LIKE ');
+                break;
+            default:
+                return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'not a supported operator type:'. $operator, __FUNCTION__);
+            }
+        }
+        $match.= "'";
+        foreach ($pattern as $key => $value) {
+            if ($key % 2) {
+                $match.= $value;
+            } else {
+                $escaped = $db->escape($value);
+                if (MDB2::isError($escaped)) {
+                    return $escaped;
+                }
+                $match.= $db->escapePattern($escaped);
+            }
+        }
+        $match.= "'";
+        $match.= $this->patternEscapeString();
+        return $match;
+    }
+
+    // }}}
+    // {{{ patternEscapeString()
+
+    /**
+     * build string to define pattern escape character
+     *
+     * @access public
+     *
+     * @return string define pattern escape character
+     */
+    function patternEscapeString()
+    {
+        return '';
+    }
+
+    // }}}
+    // {{{ mapNativeDatatype()
+
+    /**
+     * Maps a native array description of a field to a MDB2 datatype and length
+     *
+     * @param array  $field native field description
+     * @return array containing the various possible types, length, sign, fixed
+     * @access public
+     */
+    function mapNativeDatatype($field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        // If the user has specified an option to map the native field
+        // type to a custom MDB2 datatype...
+        $db_type = strtok($field['type'], '(), ');
+        if (!empty($db->options['nativetype_map_callback'][$db_type])) {
+            return call_user_func_array($db->options['nativetype_map_callback'][$db_type], array($db, $field));
+        }
+
+        // Otherwise perform the built-in (i.e. normal) MDB2 native type to
+        // MDB2 datatype conversion
+        return $this->_mapNativeDatatype($field);
+    }
+
+    // }}}
+    // {{{ _mapNativeDatatype()
+
+    /**
+     * Maps a native array description of a field to a MDB2 datatype and length
+     *
+     * @param array  $field native field description
+     * @return array containing the various possible types, length, sign, fixed
+     * @access public
+     */
+    function _mapNativeDatatype($field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ mapPrepareDatatype()
+
+    /**
+     * Maps an mdb2 datatype to mysqli prepare type
+     *
+     * @param string $type
+     * @return string
+     * @access public
+     */
+    function mapPrepareDatatype($type)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!empty($db->options['datatype_map'][$type])) {
+            $type = $db->options['datatype_map'][$type];
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('type' => $type);
+                return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+            }
+        }
+
+        return $type;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Datatype/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/Datatype/mysql.php
new file mode 100644 (file)
index 0000000..5d2385d
--- /dev/null
@@ -0,0 +1,602 @@
+<?php
+// vim: set et ts=4 sw=4 fdm=marker:
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Datatype/Common.php';
+
+/**
+ * MDB2 MySQL driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common
+{
+    // {{{ _getCharsetFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $charset   name of the charset
+     * @return string  DBMS specific SQL code portion needed to set the CHARACTER SET
+     *                 of a field declaration.
+     */
+    function _getCharsetFieldDeclaration($charset)
+    {
+        return 'CHARACTER SET '.$charset;
+    }
+
+    // }}}
+    // {{{ _getCollationFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the COLLATION
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $collation   name of the collation
+     * @return string  DBMS specific SQL code portion needed to set the COLLATION
+     *                 of a field declaration.
+     */
+    function _getCollationFieldDeclaration($collation)
+    {
+        return 'COLLATE '.$collation;
+    }
+
+    // }}}
+    // {{{ getDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare
+     * of the given type
+     *
+     * @param string $type  type to which the value should be converted to
+     * @param string $name  name the field to be declared.
+     * @param string $field definition of the field
+     *
+     * @return string DBMS-specific SQL code portion that should be used to
+     *                declare the specified field.
+     * @access public
+     */
+    function getDeclaration($type, $name, $field)
+    {
+        // MySQL DDL syntax forbids combining NOT NULL with DEFAULT NULL.
+        // To get a default of NULL for NOT NULL columns, omit it.
+        if (   isset($field['notnull'])
+            && !empty($field['notnull'])
+            && array_key_exists('default', $field) // do not use isset() here!
+            && null === $field['default']
+        ) {
+            unset($field['default']);
+        }
+        return parent::getDeclaration($type, $name, $field);
+    }
+
+    // }}}
+    // {{{ getTypeDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an text type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param array $field  associative array with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      length
+     *          Integer value that determines the maximum length of the text
+     *          field. If this argument is missing the field should be
+     *          declared to have the longest length allowed by the DBMS.
+     *
+     *      default
+     *          Text value to be used as default for this field.
+     *
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *      declare the specified field.
+     * @access public
+     */
+    function getTypeDeclaration($field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        switch ($field['type']) {
+        case 'text':
+            if (empty($field['length']) && array_key_exists('default', $field)) {
+                $field['length'] = $db->varchar_max_length;
+            }
+            $length = !empty($field['length']) ? $field['length'] : false;
+            $fixed = !empty($field['fixed']) ? $field['fixed'] : false;
+            return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)')
+                : ($length ? 'VARCHAR('.$length.')' : 'TEXT');
+        case 'clob':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 255) {
+                    return 'TINYTEXT';
+                } elseif ($length <= 65532) {
+                    return 'TEXT';
+                } elseif ($length <= 16777215) {
+                    return 'MEDIUMTEXT';
+                }
+            }
+            return 'LONGTEXT';
+        case 'blob':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 255) {
+                    return 'TINYBLOB';
+                } elseif ($length <= 65532) {
+                    return 'BLOB';
+                } elseif ($length <= 16777215) {
+                    return 'MEDIUMBLOB';
+                }
+            }
+            return 'LONGBLOB';
+        case 'integer':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 1) {
+                    return 'TINYINT';
+                } elseif ($length == 2) {
+                    return 'SMALLINT';
+                } elseif ($length == 3) {
+                    return 'MEDIUMINT';
+                } elseif ($length == 4) {
+                    return 'INT';
+                } elseif ($length > 4) {
+                    return 'BIGINT';
+                }
+            }
+            return 'INT';
+        case 'boolean':
+            return 'TINYINT(1)';
+        case 'date':
+            return 'DATE';
+        case 'time':
+            return 'TIME';
+        case 'timestamp':
+            return 'DATETIME';
+        case 'float':
+            $l = '';
+            if (!empty($field['length'])) {
+                $l = '(' . $field['length'];
+                if (!empty($field['scale'])) {
+                    $l .= ',' . $field['scale'];
+                }
+                $l .= ')';
+            }
+            return 'DOUBLE' . $l;
+        case 'decimal':
+            $length = !empty($field['length']) ? $field['length'] : 18;
+            $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places'];
+            return 'DECIMAL('.$length.','.$scale.')';
+        }
+        return '';
+    }
+
+    // }}}
+    // {{{ _getIntegerDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an integer type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned integer if
+     *                        possible.
+     *
+     *                       default
+     *                        Integer value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getIntegerDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $default = $autoinc = '';
+        if (!empty($field['autoincrement'])) {
+            $autoinc = ' AUTO_INCREMENT PRIMARY KEY';
+        } elseif (array_key_exists('default', $field)) {
+            if ($field['default'] === '') {
+                $field['default'] = empty($field['notnull']) ? null : 0;
+            }
+            $default = ' DEFAULT '.$this->quote($field['default'], 'integer');
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED';
+        if (empty($default) && empty($notnull)) {
+            $default = ' DEFAULT NULL';
+        }
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc;
+    }
+
+    // }}}
+    // {{{ _getFloatDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an float type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned float if
+     *                        possible.
+     *
+     *                       default
+     *                        float value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getFloatDeclaration($name, $field)
+    {
+        // Since AUTO_INCREMENT can be used for integer or floating-point types,
+        // reuse the INTEGER declaration
+        // @see http://bugs.mysql.com/bug.php?id=31032
+        return $this->_getIntegerDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getDecimalDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an decimal type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned integer if
+     *                        possible.
+     *
+     *                       default
+     *                        Decimal value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getDecimalDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $default = '';
+        if (array_key_exists('default', $field)) {
+            if ($field['default'] === '') {
+                $field['default'] = empty($field['notnull']) ? null : 0;
+            }
+            $default = ' DEFAULT '.$this->quote($field['default'], 'integer');
+        } elseif (empty($field['notnull'])) {
+            $default = ' DEFAULT NULL';
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED';
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull;
+    }
+
+    // }}}
+    // {{{ matchPattern()
+
+    /**
+     * build a pattern matching string
+     *
+     * @access public
+     *
+     * @param array $pattern even keys are strings, odd are patterns (% and _)
+     * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future)
+     * @param string $field optional field name that is being matched against
+     *                  (might be required when emulating ILIKE)
+     *
+     * @return string SQL pattern
+     */
+    function matchPattern($pattern, $operator = null, $field = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $match = '';
+        if (null !== $operator) {
+            $field = (null === $field) ? '' : $field.' ';
+            $operator = strtoupper($operator);
+            switch ($operator) {
+            // case insensitive
+            case 'ILIKE':
+                $match = $field.'LIKE ';
+                break;
+            case 'NOT ILIKE':
+                $match = $field.'NOT LIKE ';
+                break;
+            // case sensitive
+            case 'LIKE':
+                $match = $field.'LIKE BINARY ';
+                break;
+            case 'NOT LIKE':
+                $match = $field.'NOT LIKE BINARY ';
+                break;
+            default:
+                return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'not a supported operator type:'. $operator, __FUNCTION__);
+            }
+        }
+        $match.= "'";
+        foreach ($pattern as $key => $value) {
+            if ($key % 2) {
+                $match.= $value;
+            } else {
+                $match.= $db->escapePattern($db->escape($value));
+            }
+        }
+        $match.= "'";
+        $match.= $this->patternEscapeString();
+        return $match;
+    }
+
+    // }}}
+    // {{{ _mapNativeDatatype()
+
+    /**
+     * Maps a native array description of a field to a MDB2 datatype and length
+     *
+     * @param array  $field native field description
+     * @return array containing the various possible types, length, sign, fixed
+     * @access public
+     */
+    function _mapNativeDatatype($field)
+    {
+        $db_type = strtolower($field['type']);
+        $db_type = strtok($db_type, '(), ');
+        if ($db_type == 'national') {
+            $db_type = strtok('(), ');
+        }
+        if (!empty($field['length'])) {
+            $length = strtok($field['length'], ', ');
+            $decimal = strtok(', ');
+        } else {
+            $length = strtok('(), ');
+            $decimal = strtok('(), ');
+        }
+        $type = array();
+        $unsigned = $fixed = null;
+        switch ($db_type) {
+        case 'tinyint':
+            $type[] = 'integer';
+            $type[] = 'boolean';
+            if (preg_match('/^(is|has)/', $field['name'])) {
+                $type = array_reverse($type);
+            }
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 1;
+            break;
+        case 'smallint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 2;
+            break;
+        case 'mediumint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 3;
+            break;
+        case 'int':
+        case 'integer':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 4;
+            break;
+        case 'bigint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 8;
+            break;
+        case 'tinytext':
+        case 'mediumtext':
+        case 'longtext':
+        case 'text':
+        case 'varchar':
+            $fixed = false;
+        case 'string':
+        case 'char':
+            $type[] = 'text';
+            if ($length == '1') {
+                $type[] = 'boolean';
+                if (preg_match('/^(is|has)/', $field['name'])) {
+                    $type = array_reverse($type);
+                }
+            } elseif (strstr($db_type, 'text')) {
+                $type[] = 'clob';
+                if ($decimal == 'binary') {
+                    $type[] = 'blob';
+                }
+                $type = array_reverse($type);
+            }
+            if ($fixed !== false) {
+                $fixed = true;
+            }
+            break;
+        case 'enum':
+            $type[] = 'text';
+            preg_match_all('/\'.+\'/U', $field['type'], $matches);
+            $length = 0;
+            $fixed = false;
+            if (is_array($matches)) {
+                foreach ($matches[0] as $value) {
+                    $length = max($length, strlen($value)-2);
+                }
+                if ($length == '1' && count($matches[0]) == 2) {
+                    $type[] = 'boolean';
+                    if (preg_match('/^(is|has)/', $field['name'])) {
+                        $type = array_reverse($type);
+                    }
+                }
+            }
+            $type[] = 'integer';
+        case 'set':
+            $fixed = false;
+            $type[] = 'text';
+            $type[] = 'integer';
+            break;
+        case 'date':
+            $type[] = 'date';
+            $length = null;
+            break;
+        case 'datetime':
+        case 'timestamp':
+            $type[] = 'timestamp';
+            $length = null;
+            break;
+        case 'time':
+            $type[] = 'time';
+            $length = null;
+            break;
+        case 'float':
+        case 'double':
+        case 'real':
+            $type[] = 'float';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            if ($decimal !== false) {
+                $length = $length.','.$decimal;
+            }
+            break;
+        case 'unknown':
+        case 'decimal':
+        case 'numeric':
+            $type[] = 'decimal';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            if ($decimal !== false) {
+                $length = $length.','.$decimal;
+            }
+            break;
+        case 'tinyblob':
+        case 'mediumblob':
+        case 'longblob':
+        case 'blob':
+            $type[] = 'blob';
+            $length = null;
+            break;
+        case 'binary':
+        case 'varbinary':
+            $type[] = 'blob';
+            break;
+        case 'year':
+            $type[] = 'integer';
+            $type[] = 'date';
+            $length = null;
+            break;
+        default:
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+
+            return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'unknown database attribute type: '.$db_type, __FUNCTION__);
+        }
+
+        if ((int)$length <= 0) {
+            $length = null;
+        }
+
+        return array($type, $length, $unsigned, $fixed);
+    }
+
+    // }}}
+}
+
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Datatype/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/Datatype/mysqli.php
new file mode 100644 (file)
index 0000000..95e6990
--- /dev/null
@@ -0,0 +1,640 @@
+<?php
+// vim: set et ts=4 sw=4 fdm=marker:
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Datatype/Common.php';
+
+/**
+ * MDB2 MySQLi driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Datatype_mysqli extends MDB2_Driver_Datatype_Common
+{
+    // {{{ _getCharsetFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $charset   name of the charset
+     * @return string  DBMS specific SQL code portion needed to set the CHARACTER SET
+     *                 of a field declaration.
+     */
+    function _getCharsetFieldDeclaration($charset)
+    {
+        return 'CHARACTER SET '.$charset;
+    }
+
+    // }}}
+    // {{{ _getCollationFieldDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to set the COLLATION
+     * of a field declaration to be used in statements like CREATE TABLE.
+     *
+     * @param string $collation   name of the collation
+     * @return string  DBMS specific SQL code portion needed to set the COLLATION
+     *                 of a field declaration.
+     */
+    function _getCollationFieldDeclaration($collation)
+    {
+        return 'COLLATE '.$collation;
+    }
+
+    // }}}
+    // {{{ getDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare
+     * of the given type
+     *
+     * @param string $type  type to which the value should be converted to
+     * @param string $name  name the field to be declared.
+     * @param string $field definition of the field
+     *
+     * @return string DBMS-specific SQL code portion that should be used to
+     *                declare the specified field.
+     * @access public
+     */
+    function getDeclaration($type, $name, $field)
+    {
+        // MySQL DDL syntax forbids combining NOT NULL with DEFAULT NULL.
+        // To get a default of NULL for NOT NULL columns, omit it.
+        if (   isset($field['notnull'])
+            && !empty($field['notnull'])
+            && array_key_exists('default', $field) // do not use isset() here!
+            && null === $field['default']
+        ) {
+            unset($field['default']);
+        }
+        return parent::getDeclaration($type, $name, $field);
+    }
+
+    // }}}
+    // {{{ getTypeDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an text type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param array $field  associative array with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      length
+     *          Integer value that determines the maximum length of the text
+     *          field. If this argument is missing the field should be
+     *          declared to have the longest length allowed by the DBMS.
+     *
+     *      default
+     *          Text value to be used as default for this field.
+     *
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *      declare the specified field.
+     * @access public
+     */
+    function getTypeDeclaration($field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        switch ($field['type']) {
+        case 'text':
+            if (empty($field['length']) && array_key_exists('default', $field)) {
+                $field['length'] = $db->varchar_max_length;
+            }
+            $length = !empty($field['length']) ? $field['length'] : false;
+            $fixed = !empty($field['fixed']) ? $field['fixed'] : false;
+            return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)')
+                : ($length ? 'VARCHAR('.$length.')' : 'TEXT');
+        case 'clob':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 255) {
+                    return 'TINYTEXT';
+                } elseif ($length <= 65532) {
+                    return 'TEXT';
+                } elseif ($length <= 16777215) {
+                    return 'MEDIUMTEXT';
+                }
+            }
+            return 'LONGTEXT';
+        case 'blob':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 255) {
+                    return 'TINYBLOB';
+                } elseif ($length <= 65532) {
+                    return 'BLOB';
+                } elseif ($length <= 16777215) {
+                    return 'MEDIUMBLOB';
+                }
+            }
+            return 'LONGBLOB';
+        case 'integer':
+            if (!empty($field['length'])) {
+                $length = $field['length'];
+                if ($length <= 1) {
+                    return 'TINYINT';
+                } elseif ($length == 2) {
+                    return 'SMALLINT';
+                } elseif ($length == 3) {
+                    return 'MEDIUMINT';
+                } elseif ($length == 4) {
+                    return 'INT';
+                } elseif ($length > 4) {
+                    return 'BIGINT';
+                }
+            }
+            return 'INT';
+        case 'boolean':
+            return 'TINYINT(1)';
+        case 'date':
+            return 'DATE';
+        case 'time':
+            return 'TIME';
+        case 'timestamp':
+            return 'DATETIME';
+        case 'float':
+            $l = '';
+            if (!empty($field['length'])) {
+                $l = '(' . $field['length'];
+                if (!empty($field['scale'])) {
+                    $l .= ',' . $field['scale'];
+                }
+                $l .= ')';
+            }
+            return 'DOUBLE' . $l;
+        case 'decimal':
+            $length = !empty($field['length']) ? $field['length'] : 18;
+            $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places'];
+            return 'DECIMAL('.$length.','.$scale.')';
+        }
+        return '';
+    }
+
+    // }}}
+    // {{{ _getIntegerDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an integer type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned integer if
+     *                        possible.
+     *
+     *                       default
+     *                        Integer value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getIntegerDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $default = $autoinc = '';
+        if (!empty($field['autoincrement'])) {
+            $autoinc = ' AUTO_INCREMENT PRIMARY KEY';
+        } elseif (array_key_exists('default', $field)) {
+            if ($field['default'] === '') {
+                $field['default'] = empty($field['notnull']) ? null : 0;
+            }
+            $default = ' DEFAULT '.$this->quote($field['default'], 'integer');
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED';
+        if (empty($default) && empty($notnull)) {
+            $default = ' DEFAULT NULL';
+        }
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc;
+    }
+
+    // }}}
+    // {{{ _getFloatDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an float type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned float if
+     *                        possible.
+     *
+     *                       default
+     *                        float value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getFloatDeclaration($name, $field)
+    {
+        // Since AUTO_INCREMENT can be used for integer or floating-point types,
+        // reuse the INTEGER declaration
+        // @see http://bugs.mysql.com/bug.php?id=31032
+        return $this->_getIntegerDeclaration($name, $field);
+    }
+
+    // }}}
+    // {{{ _getDecimalDeclaration()
+
+    /**
+     * Obtain DBMS specific SQL code portion needed to declare an decimal type
+     * field to be used in statements like CREATE TABLE.
+     *
+     * @param string  $name   name the field to be declared.
+     * @param string  $field  associative array with the name of the properties
+     *                        of the field being declared as array indexes.
+     *                        Currently, the types of supported field
+     *                        properties are as follows:
+     *
+     *                       unsigned
+     *                        Boolean flag that indicates whether the field
+     *                        should be declared as unsigned integer if
+     *                        possible.
+     *
+     *                       default
+     *                        Decimal value to be used as default for this
+     *                        field.
+     *
+     *                       notnull
+     *                        Boolean flag that indicates whether this field is
+     *                        constrained to not be set to null.
+     * @return string  DBMS specific SQL code portion that should be used to
+     *                 declare the specified field.
+     * @access protected
+     */
+    function _getDecimalDeclaration($name, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $default = '';
+        if (array_key_exists('default', $field)) {
+            if ($field['default'] === '') {
+                $field['default'] = empty($field['notnull']) ? null : 0;
+            }
+            $default = ' DEFAULT '.$this->quote($field['default'], 'integer');
+        } elseif (empty($field['notnull'])) {
+            $default = ' DEFAULT NULL';
+        }
+
+        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';
+        $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED';
+        $name = $db->quoteIdentifier($name, true);
+        return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull;
+    }
+
+    // }}}
+    // {{{ matchPattern()
+
+    /**
+     * build a pattern matching string
+     *
+     * @access public
+     *
+     * @param array $pattern even keys are strings, odd are patterns (% and _)
+     * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future)
+     * @param string $field optional field name that is being matched against
+     *                  (might be required when emulating ILIKE)
+     *
+     * @return string SQL pattern
+     */
+    function matchPattern($pattern, $operator = null, $field = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $match = '';
+        if (null !== $operator) {
+            $field = (null === $field) ? '' : $field.' ';
+            $operator = strtoupper($operator);
+            switch ($operator) {
+            // case insensitive
+            case 'ILIKE':
+                $match = $field.'LIKE ';
+                break;
+            case 'NOT ILIKE':
+                $match = $field.'NOT LIKE ';
+                break;
+            // case sensitive
+            case 'LIKE':
+                $match = $field.'LIKE BINARY ';
+                break;
+            case 'NOT LIKE':
+                $match = $field.'NOT LIKE BINARY ';
+                break;
+            default:
+                return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'not a supported operator type:'. $operator, __FUNCTION__);
+            }
+        }
+        $match.= "'";
+        foreach ($pattern as $key => $value) {
+            if ($key % 2) {
+                $match.= $value;
+            } else {
+                $match.= $db->escapePattern($db->escape($value));
+            }
+        }
+        $match.= "'";
+        $match.= $this->patternEscapeString();
+        return $match;
+    }
+
+    // }}}
+    // {{{ _mapNativeDatatype()
+
+    /**
+     * Maps a native array description of a field to a MDB2 datatype and length
+     *
+     * @param array  $field native field description
+     * @return array containing the various possible types, length, sign, fixed
+     * @access public
+     */
+    function _mapNativeDatatype($field)
+    {
+        $db_type = strtolower($field['type']);
+        $db_type = strtok($db_type, '(), ');
+        if ($db_type == 'national') {
+            $db_type = strtok('(), ');
+        }
+        if (!empty($field['length'])) {
+            $length = strtok($field['length'], ', ');
+            $decimal = strtok(', ');
+        } else {
+            $length = strtok('(), ');
+            $decimal = strtok('(), ');
+        }
+        $type = array();
+        $unsigned = $fixed = null;
+        switch ($db_type) {
+        case 'tinyint':
+            $type[] = 'integer';
+            $type[] = 'boolean';
+            if (preg_match('/^(is|has)/', $field['name'])) {
+                $type = array_reverse($type);
+            }
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 1;
+            break;
+        case 'smallint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 2;
+            break;
+        case 'mediumint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 3;
+            break;
+        case 'int':
+        case 'integer':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 4;
+            break;
+        case 'bigint':
+            $type[] = 'integer';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            $length = 8;
+            break;
+        case 'tinytext':
+        case 'mediumtext':
+        case 'longtext':
+        case 'text':
+        case 'varchar':
+            $fixed = false;
+        case 'string':
+        case 'char':
+            $type[] = 'text';
+            if ($length == '1') {
+                $type[] = 'boolean';
+                if (preg_match('/^(is|has)/', $field['name'])) {
+                    $type = array_reverse($type);
+                }
+            } elseif (strstr($db_type, 'text')) {
+                $type[] = 'clob';
+                if ($decimal == 'binary') {
+                    $type[] = 'blob';
+                }
+                $type = array_reverse($type);
+            }
+            if ($fixed !== false) {
+                $fixed = true;
+            }
+            break;
+        case 'enum':
+            $type[] = 'text';
+            preg_match_all('/\'.+\'/U', $field['type'], $matches);
+            $length = 0;
+            $fixed = false;
+            if (is_array($matches)) {
+                foreach ($matches[0] as $value) {
+                    $length = max($length, strlen($value)-2);
+                }
+                if ($length == '1' && count($matches[0]) == 2) {
+                    $type[] = 'boolean';
+                    if (preg_match('/^(is|has)/', $field['name'])) {
+                        $type = array_reverse($type);
+                    }
+                }
+            }
+            $type[] = 'integer';
+        case 'set':
+            $fixed = false;
+            $type[] = 'text';
+            $type[] = 'integer';
+            break;
+        case 'date':
+            $type[] = 'date';
+            $length = null;
+            break;
+        case 'datetime':
+        case 'timestamp':
+            $type[] = 'timestamp';
+            $length = null;
+            break;
+        case 'time':
+            $type[] = 'time';
+            $length = null;
+            break;
+        case 'float':
+        case 'double':
+        case 'real':
+            $type[] = 'float';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            if ($decimal !== false) {
+                $length = $length.','.$decimal;
+            }
+            break;
+        case 'unknown':
+        case 'decimal':
+        case 'numeric':
+            $type[] = 'decimal';
+            $unsigned = preg_match('/ unsigned/i', $field['type']);
+            if ($decimal !== false) {
+                $length = $length.','.$decimal;
+            }
+            break;
+        case 'tinyblob':
+        case 'mediumblob':
+        case 'longblob':
+        case 'blob':
+            $type[] = 'blob';
+            $length = null;
+            break;
+        case 'binary':
+        case 'varbinary':
+            $type[] = 'blob';
+            break;
+        case 'year':
+            $type[] = 'integer';
+            $type[] = 'date';
+            $length = null;
+            break;
+        default:
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+
+            return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'unknown database attribute type: '.$db_type, __FUNCTION__);
+        }
+
+        if ((int)$length <= 0) {
+            $length = null;
+        }
+
+        return array($type, $length, $unsigned, $fixed);
+    }
+
+    // }}}
+    // {{{ mapPrepareDatatype()
+
+    /**
+     * Maps an MDB2 datatype to native prepare type
+     *
+     * @param string $type
+     * @return string
+     * @access public
+     */
+    function mapPrepareDatatype($type)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!empty($db->options['datatype_map'][$type])) {
+            $type = $db->options['datatype_map'][$type];
+            if (!empty($db->options['datatype_map_callback'][$type])) {
+                $parameter = array('type' => $type);
+                return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter));
+            }
+        }
+
+        switch ($type) {
+            case 'boolean':
+            case 'integer':
+                return 'i';
+            case 'float':
+                return 'd';
+            case 'blob':
+                return 'b';
+            default:
+                break;
+        }
+        return 's';
+    }
+    
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Function/Common.php b/WEB-INF/lib/pear/MDB2/Driver/Function/Common.php
new file mode 100644 (file)
index 0000000..d62dc26
--- /dev/null
@@ -0,0 +1,293 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Common.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+
+/**
+ * Base class for the function modules that is extended by each MDB2 driver
+ *
+ * To load this module in the MDB2 object:
+ * $mdb->loadModule('Function');
+ *
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Function_Common extends MDB2_Module_Common
+{
+    // {{{ executeStoredProc()
+
+    /**
+     * Execute a stored procedure and return any results
+     *
+     * @param string $name string that identifies the function to execute
+     * @param mixed  $params  array that contains the paramaters to pass the stored proc
+     * @param mixed   $types  array that contains the types of the columns in
+     *                        the result set
+     * @param mixed $result_class string which specifies which result class to use
+     * @param mixed $result_wrap_class string which specifies which class to wrap results in
+     *
+     * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+        return $error;
+    }
+
+    // }}}
+    // {{{ functionTable()
+
+    /**
+     * return string for internal table used when calling only a function
+     *
+     * @return string for internal table used when calling only a function
+     * @access public
+     */
+    function functionTable()
+    {
+        return '';
+    }
+
+    // }}}
+    // {{{ now()
+
+    /**
+     * Return string to call a variable with the current timestamp inside an SQL statement
+     * There are three special variables for current date and time:
+     * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type)
+     * - CURRENT_DATE (date, DATE type)
+     * - CURRENT_TIME (time, TIME type)
+     *
+     * @param string $type 'timestamp' | 'time' | 'date'
+     *
+     * @return string to call a variable with the current timestamp
+     * @access public
+     */
+    function now($type = 'timestamp')
+    {
+        switch ($type) {
+        case 'time':
+            return 'CURRENT_TIME';
+        case 'date':
+            return 'CURRENT_DATE';
+        case 'timestamp':
+        default:
+            return 'CURRENT_TIMESTAMP';
+        }
+    }
+
+    // }}}
+    // {{{ unixtimestamp()
+
+    /**
+     * return string to call a function to get the unix timestamp from a iso timestamp
+     *
+     * @param string $expression
+     *
+     * @return string to call a variable with the timestamp
+     * @access public
+     */
+    function unixtimestamp($expression)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+        return $error;
+    }
+
+    // }}}
+    // {{{ substring()
+
+    /**
+     * return string to call a function to get a substring inside an SQL statement
+     *
+     * @return string to call a function to get a substring
+     * @access public
+     */
+    function substring($value, $position = 1, $length = null)
+    {
+        if (null !== $length) {
+            return "SUBSTRING($value FROM $position FOR $length)";
+        }
+        return "SUBSTRING($value FROM $position)";
+    }
+
+    // }}}
+    // {{{ replace()
+
+    /**
+     * return string to call a function to get replace inside an SQL statement.
+     *
+     * @return string to call a function to get a replace
+     * @access public
+     */
+    function replace($str, $from_str, $to_str)
+    {
+        return "REPLACE($str, $from_str , $to_str)";
+    }
+
+    // }}}
+    // {{{ concat()
+
+    /**
+     * Returns string to concatenate two or more string parameters
+     *
+     * @param string $value1
+     * @param string $value2
+     * @param string $values...
+     *
+     * @return string to concatenate two strings
+     * @access public
+     */
+    function concat($value1, $value2)
+    {
+        $args = func_get_args();
+        return "(".implode(' || ', $args).")";
+    }
+
+    // }}}
+    // {{{ random()
+
+    /**
+     * return string to call a function to get random value inside an SQL statement
+     *
+     * @return return string to generate float between 0 and 1
+     * @access public
+     */
+    function random()
+    {
+        return 'RAND()';
+    }
+
+    // }}}
+    // {{{ lower()
+
+    /**
+     * return string to call a function to lower the case of an expression
+     *
+     * @param string $expression
+     *
+     * @return return string to lower case of an expression
+     * @access public
+     */
+    function lower($expression)
+    {
+        return "LOWER($expression)";
+    }
+
+    // }}}
+    // {{{ upper()
+
+    /**
+     * return string to call a function to upper the case of an expression
+     *
+     * @param string $expression
+     *
+     * @return return string to upper case of an expression
+     * @access public
+     */
+    function upper($expression)
+    {
+        return "UPPER($expression)";
+    }
+
+    // }}}
+    // {{{ length()
+
+    /**
+     * return string to call a function to get the length of a string expression
+     *
+     * @param string $expression
+     *
+     * @return return string to get the string expression length
+     * @access public
+     */
+    function length($expression)
+    {
+        return "LENGTH($expression)";
+    }
+
+    // }}}
+    // {{{ guid()
+
+    /**
+     * Returns global unique identifier
+     *
+     * @return string to get global unique identifier
+     * @access public
+     */
+    function guid()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+        return $error;
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Function/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/Function/mysql.php
new file mode 100644 (file)
index 0000000..6ac2441
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Function/Common.php';
+
+/**
+ * MDB2 MySQL driver for the function modules
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Function_mysql extends MDB2_Driver_Function_Common
+{
+     // }}}
+    // {{{ executeStoredProc()
+
+    /**
+     * Execute a stored procedure and return any results
+     *
+     * @param string $name string that identifies the function to execute
+     * @param mixed  $params  array that contains the paramaters to pass the stored proc
+     * @param mixed   $types  array that contains the types of the columns in
+     *                        the result set
+     * @param mixed $result_class string which specifies which result class to use
+     * @param mixed $result_wrap_class string which specifies which class to wrap results in
+     * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'CALL '.$name;
+        $query .= $params ? '('.implode(', ', $params).')' : '()';
+        return $db->query($query, $types, $result_class, $result_wrap_class);
+    }
+
+    // }}}
+    // {{{ unixtimestamp()
+
+    /**
+     * return string to call a function to get the unix timestamp from a iso timestamp
+     *
+     * @param string $expression
+     *
+     * @return string to call a variable with the timestamp
+     * @access public
+     */
+    function unixtimestamp($expression)
+    {
+        return 'UNIX_TIMESTAMP('. $expression.')';
+    }
+
+    // }}}
+    // {{{ concat()
+
+    /**
+     * Returns string to concatenate two or more string parameters
+     *
+     * @param string $value1
+     * @param string $value2
+     * @param string $values...
+     * @return string to concatenate two strings
+     * @access public
+     **/
+    function concat($value1, $value2)
+    {
+        $args = func_get_args();
+        return "CONCAT(".implode(', ', $args).")";
+    }
+
+    // }}}
+    // {{{ guid()
+
+    /**
+     * Returns global unique identifier
+     *
+     * @return string to get global unique identifier
+     * @access public
+     */
+    function guid()
+    {
+        return 'UUID()';
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Function/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/Function/mysqli.php
new file mode 100644 (file)
index 0000000..2b4645e
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Function/Common.php';
+
+/**
+ * MDB2 MySQLi driver for the function modules
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Function_mysqli extends MDB2_Driver_Function_Common
+{
+     // }}}
+    // {{{ executeStoredProc()
+
+    /**
+     * Execute a stored procedure and return any results
+     *
+     * @param string $name string that identifies the function to execute
+     * @param mixed  $params  array that contains the paramaters to pass the stored proc
+     * @param mixed   $types  array that contains the types of the columns in
+     *                        the result set
+     * @param mixed $result_class string which specifies which result class to use
+     * @param mixed $result_wrap_class string which specifies which class to wrap results in
+     * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $multi_query = $db->getOption('multi_query');
+        if (!$multi_query) {
+            $db->setOption('multi_query', true);
+        }
+        $query = 'CALL '.$name;
+        $query .= $params ? '('.implode(', ', $params).')' : '()';
+        $result = $db->query($query, $types, $result_class, $result_wrap_class);
+        if (!$multi_query) {
+            $db->setOption('multi_query', false);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ unixtimestamp()
+
+    /**
+     * return string to call a function to get the unix timestamp from a iso timestamp
+     *
+     * @param string $expression
+     *
+     * @return string to call a variable with the timestamp
+     * @access public
+     */
+    function unixtimestamp($expression)
+    {
+        return 'UNIX_TIMESTAMP('. $expression.')';
+    }
+
+    // }}}
+    // {{{ concat()
+
+    /**
+     * Returns string to concatenate two or more string parameters
+     *
+     * @param string $value1
+     * @param string $value2
+     * @param string $values...
+     * @return string to concatenate two strings
+     * @access public
+     **/
+    function concat($value1, $value2)
+    {
+        $args = func_get_args();
+        return "CONCAT(".implode(', ', $args).")";
+    }
+
+    // }}}
+    // {{{ guid()
+
+    /**
+     * Returns global unique identifier
+     *
+     * @return string to get global unique identifier
+     * @access public
+     */
+    function guid()
+    {
+        return 'UUID()';
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Manager/Common.php b/WEB-INF/lib/pear/MDB2/Driver/Manager/Common.php
new file mode 100644 (file)
index 0000000..c9d9552
--- /dev/null
@@ -0,0 +1,1038 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Authors: Lukas Smith <smith@pooteeweet.org>                          |
+// |          Lorenzo Alberton <l.alberton@quipo.it>                      |
+// +----------------------------------------------------------------------+
+//
+// $Id: Common.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ * @author   Lorenzo Alberton <l.alberton@quipo.it>
+ */
+
+/**
+ * Base class for the management modules that is extended by each MDB2 driver
+ *
+ * To load this module in the MDB2 object:
+ * $mdb->loadModule('Manager');
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Manager_Common extends MDB2_Module_Common
+{
+    // {{{ splitTableSchema()
+
+    /**
+     * Split the "[owner|schema].table" notation into an array
+     *
+     * @param string $table [schema and] table name
+     *
+     * @return array array(schema, table)
+     * @access private
+     */
+    function splitTableSchema($table)
+    {
+        $ret = array();
+        if (strpos($table, '.') !== false) {
+            return explode('.', $table);
+        }
+        return array(null, $table);
+    }
+
+    // }}}
+    // {{{ getFieldDeclarationList()
+
+    /**
+     * Get declaration of a number of field in bulk
+     *
+     * @param array $fields  a multidimensional associative array.
+     *      The first dimension determines the field name, while the second
+     *      dimension is keyed with the name of the properties
+     *      of the field being declared as array indexes. Currently, the types
+     *      of supported field properties are as follows:
+     *
+     *      default
+     *          Boolean value to be used as default for this field.
+     *
+     *      notnull
+     *          Boolean flag that indicates whether this field is constrained
+     *          to not be set to null.
+     *
+     * @return mixed string on success, a MDB2 error on failure
+     * @access public
+     */
+    function getFieldDeclarationList($fields)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!is_array($fields) || empty($fields)) {
+            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'missing any fields', __FUNCTION__);
+        }
+        foreach ($fields as $field_name => $field) {
+            $query = $db->getDeclaration($field['type'], $field_name, $field);
+            if (MDB2::isError($query)) {
+                return $query;
+            }
+            $query_fields[] = $query;
+        }
+        return implode(', ', $query_fields);
+    }
+
+    // }}}
+    // {{{ _fixSequenceName()
+
+    /**
+     * Removes any formatting in an sequence name using the 'seqname_format' option
+     *
+     * @param string $sqn string that containts name of a potential sequence
+     * @param bool $check if only formatted sequences should be returned
+     * @return string name of the sequence with possible formatting removed
+     * @access protected
+     */
+    function _fixSequenceName($sqn, $check = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i';
+        $seq_name = preg_replace($seq_pattern, '\\1', $sqn);
+        if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) {
+            return $seq_name;
+        }
+        if ($check) {
+            return false;
+        }
+        return $sqn;
+    }
+
+    // }}}
+    // {{{ _fixIndexName()
+
+    /**
+     * Removes any formatting in an index name using the 'idxname_format' option
+     *
+     * @param string $idx string that containts name of anl index
+     * @return string name of the index with eventual formatting removed
+     * @access protected
+     */
+    function _fixIndexName($idx)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i';
+        $idx_name = preg_replace($idx_pattern, '\\1', $idx);
+        if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) {
+            return $idx_name;
+        }
+        return $idx;
+    }
+
+    // }}}
+    // {{{ createDatabase()
+
+    /**
+     * create a new database
+     *
+     * @param string $name    name of the database that should be created
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createDatabase($database, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ alterDatabase()
+
+    /**
+     * alter an existing database
+     *
+     * @param string $name    name of the database that should be created
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function alterDatabase($database, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ dropDatabase()
+
+    /**
+     * drop an existing database
+     *
+     * @param string $name name of the database that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropDatabase($database)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ _getCreateTableQuery()
+
+    /**
+     * Create a basic SQL query for a new table creation
+     *
+     * @param string $name    Name of the database that should be created
+     * @param array  $fields  Associative array that contains the definition of each field of the new table
+     * @param array  $options An associative array of table options
+     *
+     * @return mixed string (the SQL query) on success, a MDB2 error on failure
+     * @see createTable()
+     */
+    function _getCreateTableQuery($name, $fields, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!$name) {
+            return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null,
+                'no valid table name specified', __FUNCTION__);
+        }
+        if (empty($fields)) {
+            return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null,
+                'no fields specified for table "'.$name.'"', __FUNCTION__);
+        }
+        $query_fields = $this->getFieldDeclarationList($fields);
+        if (MDB2::isError($query_fields)) {
+            return $query_fields;
+        }
+        if (!empty($options['primary'])) {
+            $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')';
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = 'CREATE ';
+        if (!empty($options['temporary'])) {
+            $result .= $this->_getTemporaryTableQuery();
+        }
+        $result .= " TABLE $name ($query_fields)";
+        return $result;
+    }
+
+    // }}}
+    // {{{ _getTemporaryTableQuery()
+
+    /**
+     * A method to return the required SQL string that fits between CREATE ... TABLE
+     * to create the table as a temporary table.
+     *
+     * Should be overridden in driver classes to return the correct string for the
+     * specific database type.
+     *
+     * The default is to return the string "TEMPORARY" - this will result in a
+     * SQL error for any database that does not support temporary tables, or that
+     * requires a different SQL command from "CREATE TEMPORARY TABLE".
+     *
+     * @return string The string required to be placed between "CREATE" and "TABLE"
+     *                to generate a temporary table, if possible.
+     */
+    function _getTemporaryTableQuery()
+    {
+        return 'TEMPORARY';
+    }
+
+    // }}}
+    // {{{ createTable()
+
+    /**
+     * create a new table
+     *
+     * @param string $name   Name of the database that should be created
+     * @param array $fields  Associative array that contains the definition of each field of the new table
+     *                       The indexes of the array entries are the names of the fields of the table an
+     *                       the array entry values are associative arrays like those that are meant to be
+     *                       passed with the field definitions to get[Type]Declaration() functions.
+     *                          array(
+     *                              'id' => array(
+     *                                  'type' => 'integer',
+     *                                  'unsigned' => 1
+     *                                  'notnull' => 1
+     *                                  'default' => 0
+     *                              ),
+     *                              'name' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              ),
+     *                              'password' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              )
+     *                          );
+     * @param array $options  An associative array of table options:
+     *                          array(
+     *                              'comment' => 'Foo',
+     *                              'temporary' => true|false,
+     *                          );
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createTable($name, $fields, $options = array())
+    {
+        $query = $this->_getCreateTableQuery($name, $fields, $options);
+        if (MDB2::isError($query)) {
+            return $query;
+        }
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropTable()
+
+    /**
+     * drop an existing table
+     *
+     * @param string $name name of the table that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("DROP TABLE $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ truncateTable()
+
+    /**
+     * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported,
+     * it falls back to a DELETE FROM TABLE query)
+     *
+     * @param string $name name of the table that should be truncated
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function truncateTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("DELETE FROM $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ vacuum()
+
+    /**
+     * Optimize (vacuum) all the tables in the db (or only the specified table)
+     * and optionally run ANALYZE.
+     *
+     * @param string $table table name (all the tables if empty)
+     * @param array  $options an array with driver-specific options:
+     *               - timeout [int] (in seconds) [mssql-only]
+     *               - analyze [boolean] [pgsql and mysql]
+     *               - full [boolean] [pgsql-only]
+     *               - freeze [boolean] [pgsql-only]
+     *
+     * @return mixed MDB2_OK success, a MDB2 error on failure
+     * @access public
+     */
+    function vacuum($table = null, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ alterTable()
+
+    /**
+     * alter an existing table
+     *
+     * @param string $name         name of the table that is intended to be changed.
+     * @param array $changes     associative array that contains the details of each type
+     *                             of change that is intended to be performed. The types of
+     *                             changes that are currently supported are defined as follows:
+     *
+     *                             name
+     *
+     *                                New name for the table.
+     *
+     *                            add
+     *
+     *                                Associative array with the names of fields to be added as
+     *                                 indexes of the array. The value of each entry of the array
+     *                                 should be set to another associative array with the properties
+     *                                 of the fields to be added. The properties of the fields should
+     *                                 be the same as defined by the MDB2 parser.
+     *
+     *
+     *                            remove
+     *
+     *                                Associative array with the names of fields to be removed as indexes
+     *                                 of the array. Currently the values assigned to each entry are ignored.
+     *                                 An empty array should be used for future compatibility.
+     *
+     *                            rename
+     *
+     *                                Associative array with the names of fields to be renamed as indexes
+     *                                 of the array. The value of each entry of the array should be set to
+     *                                 another associative array with the entry named name with the new
+     *                                 field name and the entry named Declaration that is expected to contain
+     *                                 the portion of the field declaration already in DBMS specific SQL code
+     *                                 as it is used in the CREATE TABLE statement.
+     *
+     *                            change
+     *
+     *                                Associative array with the names of the fields to be changed as indexes
+     *                                 of the array. Keep in mind that if it is intended to change either the
+     *                                 name of a field and any other properties, the change array entries
+     *                                 should have the new names of the fields as array indexes.
+     *
+     *                                The value of each entry of the array should be set to another associative
+     *                                 array with the properties of the fields to that are meant to be changed as
+     *                                 array entries. These entries should be assigned to the new values of the
+     *                                 respective properties. The properties of the fields should be the same
+     *                                 as defined by the MDB2 parser.
+     *
+     *                            Example
+     *                                array(
+     *                                    'name' => 'userlist',
+     *                                    'add' => array(
+     *                                        'quota' => array(
+     *                                            'type' => 'integer',
+     *                                            'unsigned' => 1
+     *                                        )
+     *                                    ),
+     *                                    'remove' => array(
+     *                                        'file_limit' => array(),
+     *                                        'time_limit' => array()
+     *                                    ),
+     *                                    'change' => array(
+     *                                        'name' => array(
+     *                                            'length' => '20',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 20,
+     *                                            ),
+     *                                        )
+     *                                    ),
+     *                                    'rename' => array(
+     *                                        'sex' => array(
+     *                                            'name' => 'gender',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 1,
+     *                                                'default' => 'M',
+     *                                            ),
+     *                                        )
+     *                                    )
+     *                                )
+     *
+     * @param boolean $check     indicates whether the function should just check if the DBMS driver
+     *                             can perform the requested table alterations if the value is true or
+     *                             actually perform them otherwise.
+     * @access public
+     *
+      * @return mixed MDB2_OK on success, a MDB2 error on failure
+     */
+    function alterTable($name, $changes, $check)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listDatabases()
+
+    /**
+     * list all databases
+     *
+     * @return mixed array of database names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listDatabases()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implementedd', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listUsers()
+
+    /**
+     * list all users
+     *
+     * @return mixed array of user names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listUsers()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listViews()
+
+    /**
+     * list all views in the current database
+     *
+     * @param string database, the current is default
+     *               NB: not all the drivers can get the view names from
+     *               a database other than the current one
+     * @return mixed array of view names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listViews($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listTableViews()
+
+    /**
+     * list the views in the database that reference a given table
+     *
+     * @param string table for which all referenced views should be found
+     * @return mixed array of view names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableViews($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listTableTriggers()
+
+    /**
+     * list all triggers in the database that reference a given table
+     *
+     * @param string table for which all referenced triggers should be found
+     * @return mixed array of trigger names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableTriggers($table = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listFunctions()
+
+    /**
+     * list all functions in the current database
+     *
+     * @return mixed array of function names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listFunctions()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listTables()
+
+    /**
+     * list all tables in the current database
+     *
+     * @param string database, the current is default.
+     *               NB: not all the drivers can get the table names from
+     *               a database other than the current one
+     * @return mixed array of table names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTables($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listTableFields()
+
+    /**
+     * list all fields in a table in the current database
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of field names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableFields($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ createIndex()
+
+    /**
+     * Get the stucture of a field into an array
+     *
+     * @param string    $table         name of the table on which the index is to be created
+     * @param string    $name         name of the index to be created
+     * @param array     $definition        associative array that defines properties of the index to be created.
+     *                                 Currently, only one property named FIELDS is supported. This property
+     *                                 is also an associative with the names of the index fields as array
+     *                                 indexes. Each entry of this array is set to another type of associative
+     *                                 array that specifies properties of the index that are specific to
+     *                                 each field.
+     *
+     *                                Currently, only the sorting property is supported. It should be used
+     *                                 to define the sorting direction of the index. It may be set to either
+     *                                 ascending or descending.
+     *
+     *                                Not all DBMS support index sorting direction configuration. The DBMS
+     *                                 drivers of those that do not support it ignore this property. Use the
+     *                                 function supports() to determine whether the DBMS driver can manage indexes.
+     *
+     *                                 Example
+     *                                    array(
+     *                                        'fields' => array(
+     *                                            'user_name' => array(
+     *                                                'sorting' => 'ascending'
+     *                                            ),
+     *                                            'last_login' => array()
+     *                                        )
+     *                                    )
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createIndex($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "CREATE INDEX $name ON $table";
+        $fields = array();
+        foreach (array_keys($definition['fields']) as $field) {
+            $fields[] = $db->quoteIdentifier($field, true);
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropIndex()
+
+    /**
+     * drop existing index
+     *
+     * @param string    $table         name of table that should be used in method
+     * @param string    $name         name of the index to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropIndex($table, $name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $result = $db->exec("DROP INDEX $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableIndexes()
+
+    /**
+     * list all indexes in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of index names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableIndexes($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ _getAdvancedFKOptions()
+
+    /**
+     * Return the FOREIGN KEY query section dealing with non-standard options
+     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+     *
+     * @param array $definition
+     * @return string
+     * @access protected
+     */
+    function _getAdvancedFKOptions($definition)
+    {
+        return '';
+    }
+
+    // }}}
+    // {{{ createConstraint()
+
+    /**
+     * create a constraint on a table
+     *
+     * @param string    $table       name of the table on which the constraint is to be created
+     * @param string    $name        name of the constraint to be created
+     * @param array     $definition  associative array that defines properties of the constraint to be created.
+     *                               The full structure of the array looks like this:
+     *          <pre>
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          </pre>
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createConstraint($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "ALTER TABLE $table ADD CONSTRAINT $name";
+        if (!empty($definition['primary'])) {
+            $query.= ' PRIMARY KEY';
+        } elseif (!empty($definition['unique'])) {
+            $query.= ' UNIQUE';
+        } elseif (!empty($definition['foreign'])) {
+            $query.= ' FOREIGN KEY';
+        }
+        $fields = array();
+        foreach (array_keys($definition['fields']) as $field) {
+            $fields[] = $db->quoteIdentifier($field, true);
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        if (!empty($definition['foreign'])) {
+            $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true);
+            $referenced_fields = array();
+            foreach (array_keys($definition['references']['fields']) as $field) {
+                $referenced_fields[] = $db->quoteIdentifier($field, true);
+            }
+            $query .= ' ('. implode(', ', $referenced_fields) . ')';
+            $query .= $this->_getAdvancedFKOptions($definition);
+        }
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropConstraint()
+
+    /**
+     * drop existing constraint
+     *
+     * @param string    $table        name of table that should be used in method
+     * @param string    $name         name of the constraint to be dropped
+     * @param string    $primary      hint if the constraint is primary
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropConstraint($table, $name, $primary = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $result = $db->exec("ALTER TABLE $table DROP CONSTRAINT $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableConstraints()
+
+    /**
+     * list all constraints in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of constraint names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableConstraints($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * create sequence
+     *
+     * @param string    $seq_name     name of the sequence to be created
+     * @param string    $start         start value of the sequence; default is 1
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createSequence($seq_name, $start = 1)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * drop existing sequence
+     *
+     * @param string    $seq_name     name of the sequence to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropSequence($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ listSequences()
+
+    /**
+     * list all sequences in the current database
+     *
+     * @param string database, the current is default
+     *               NB: not all the drivers can get the sequence names from
+     *               a database other than the current one
+     * @return mixed array of sequence names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listSequences($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Manager/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/Manager/mysql.php
new file mode 100644 (file)
index 0000000..2f33c55
--- /dev/null
@@ -0,0 +1,1471 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Manager/Common.php';
+
+/**
+ * MDB2 MySQL driver for the management modules
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common
+{
+
+    // }}}
+    // {{{ createDatabase()
+
+    /**
+     * create a new database
+     *
+     * @param string $name    name of the database that should be created
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createDatabase($name, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name  = $db->quoteIdentifier($name, true);
+        $query = 'CREATE DATABASE ' . $name;
+        if (!empty($options['charset'])) {
+            $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text');
+        }
+        if (!empty($options['collation'])) {
+            $query .= ' COLLATE ' . $db->quote($options['collation'], 'text');
+        }
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ alterDatabase()
+
+    /**
+     * alter an existing database
+     *
+     * @param string $name    name of the database that is intended to be changed
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function alterDatabase($name, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true);
+        if (!empty($options['charset'])) {
+            $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text');
+        }
+        if (!empty($options['collation'])) {
+            $query .= ' COLLATE ' . $db->quote($options['collation'], 'text');
+        }
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ dropDatabase()
+
+    /**
+     * drop an existing database
+     *
+     * @param string $name name of the database that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropDatabase($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $query = "DROP DATABASE $name";
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ _getAdvancedFKOptions()
+
+    /**
+     * Return the FOREIGN KEY query section dealing with non-standard options
+     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+     *
+     * @param array $definition
+     * @return string
+     * @access protected
+     */
+    function _getAdvancedFKOptions($definition)
+    {
+        $query = '';
+        if (!empty($definition['match'])) {
+            $query .= ' MATCH '.$definition['match'];
+        }
+        if (!empty($definition['onupdate'])) {
+            $query .= ' ON UPDATE '.$definition['onupdate'];
+        }
+        if (!empty($definition['ondelete'])) {
+            $query .= ' ON DELETE '.$definition['ondelete'];
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ createTable()
+
+    /**
+     * create a new table
+     *
+     * @param string $name   Name of the database that should be created
+     * @param array $fields  Associative array that contains the definition of each field of the new table
+     *                       The indexes of the array entries are the names of the fields of the table an
+     *                       the array entry values are associative arrays like those that are meant to be
+     *                       passed with the field definitions to get[Type]Declaration() functions.
+     *                          array(
+     *                              'id' => array(
+     *                                  'type' => 'integer',
+     *                                  'unsigned' => 1
+     *                                  'notnull' => 1
+     *                                  'default' => 0
+     *                              ),
+     *                              'name' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              ),
+     *                              'password' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              )
+     *                          );
+     * @param array $options  An associative array of table options:
+     *                          array(
+     *                              'comment' => 'Foo',
+     *                              'charset' => 'utf8',
+     *                              'collate' => 'utf8_unicode_ci',
+     *                              'type'    => 'innodb',
+     *                          );
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createTable($name, $fields, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        // if we have an AUTO_INCREMENT column and a PK on more than one field,
+        // we have to handle it differently...
+        $autoincrement = null;
+        if (empty($options['primary'])) {
+            $pk_fields = array();
+            foreach ($fields as $fieldname => $def) {
+                if (!empty($def['primary'])) {
+                    $pk_fields[$fieldname] = true;
+                }
+                if (!empty($def['autoincrement'])) {
+                    $autoincrement = $fieldname;
+                }
+            }
+            if ((null !== $autoincrement) && count($pk_fields) > 1) {
+                $options['primary'] = $pk_fields;
+            } else {
+                // the PK constraint is on max one field => OK
+                $autoincrement = null;
+            }
+        }
+
+        $query = $this->_getCreateTableQuery($name, $fields, $options);
+        if (MDB2::isError($query)) {
+            return $query;
+        }
+
+        if (null !== $autoincrement) {
+            // we have to remove the PK clause added by _getIntegerDeclaration()
+            $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query);
+        }
+
+        $options_strings = array();
+
+        if (!empty($options['comment'])) {
+            $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text');
+        }
+
+        if (!empty($options['charset'])) {
+            $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset'];
+            if (!empty($options['collate'])) {
+                $options_strings['charset'].= ' COLLATE '.$options['collate'];
+            }
+        }
+
+        $type = false;
+        if (!empty($options['type'])) {
+            $type = $options['type'];
+        } elseif ($db->options['default_table_type']) {
+            $type = $db->options['default_table_type'];
+        }
+        if ($type) {
+            $options_strings[] = "ENGINE = $type";
+        }
+
+        if (!empty($options_strings)) {
+            $query .= ' '.implode(' ', $options_strings);
+        }
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropTable()
+
+    /**
+     * drop an existing table
+     *
+     * @param string $name name of the table that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        //delete the triggers associated to existing FK constraints
+        $constraints = $this->listTableConstraints($name);
+        if (!MDB2::isError($constraints) && !empty($constraints)) {
+            $db->loadModule('Reverse', null, true);
+            foreach ($constraints as $constraint) {
+                $definition = $db->reverse->getTableConstraintDefinition($name, $constraint);
+                if (!MDB2::isError($definition) && !empty($definition['foreign'])) {
+                    $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']);
+                    if (MDB2::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+        }
+
+        return parent::dropTable($name);
+    }
+
+    // }}}
+    // {{{ truncateTable()
+
+    /**
+     * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported,
+     * it falls back to a DELETE FROM TABLE query)
+     *
+     * @param string $name name of the table that should be truncated
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function truncateTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("TRUNCATE TABLE $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ vacuum()
+
+    /**
+     * Optimize (vacuum) all the tables in the db (or only the specified table)
+     * and optionally run ANALYZE.
+     *
+     * @param string $table table name (all the tables if empty)
+     * @param array  $options an array with driver-specific options:
+     *               - timeout [int] (in seconds) [mssql-only]
+     *               - analyze [boolean] [pgsql and mysql]
+     *               - full [boolean] [pgsql-only]
+     *               - freeze [boolean] [pgsql-only]
+     *
+     * @return mixed MDB2_OK success, a MDB2 error on failure
+     * @access public
+     */
+    function vacuum($table = null, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (empty($table)) {
+            $table = $this->listTables();
+            if (MDB2::isError($table)) {
+                return $table;
+            }
+        }
+        if (is_array($table)) {
+            foreach (array_keys($table) as $k) {
+               $table[$k] = $db->quoteIdentifier($table[$k], true);
+            }
+            $table = implode(', ', $table);
+        } else {
+            $table = $db->quoteIdentifier($table, true);
+        }
+        
+        $result = $db->exec('OPTIMIZE TABLE '.$table);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!empty($options['analyze'])) {
+            $result = $db->exec('ANALYZE TABLE '.$table);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ alterTable()
+
+    /**
+     * alter an existing table
+     *
+     * @param string $name         name of the table that is intended to be changed.
+     * @param array $changes     associative array that contains the details of each type
+     *                             of change that is intended to be performed. The types of
+     *                             changes that are currently supported are defined as follows:
+     *
+     *                             name
+     *
+     *                                New name for the table.
+     *
+     *                            add
+     *
+     *                                Associative array with the names of fields to be added as
+     *                                 indexes of the array. The value of each entry of the array
+     *                                 should be set to another associative array with the properties
+     *                                 of the fields to be added. The properties of the fields should
+     *                                 be the same as defined by the MDB2 parser.
+     *
+     *
+     *                            remove
+     *
+     *                                Associative array with the names of fields to be removed as indexes
+     *                                 of the array. Currently the values assigned to each entry are ignored.
+     *                                 An empty array should be used for future compatibility.
+     *
+     *                            rename
+     *
+     *                                Associative array with the names of fields to be renamed as indexes
+     *                                 of the array. The value of each entry of the array should be set to
+     *                                 another associative array with the entry named name with the new
+     *                                 field name and the entry named Declaration that is expected to contain
+     *                                 the portion of the field declaration already in DBMS specific SQL code
+     *                                 as it is used in the CREATE TABLE statement.
+     *
+     *                            change
+     *
+     *                                Associative array with the names of the fields to be changed as indexes
+     *                                 of the array. Keep in mind that if it is intended to change either the
+     *                                 name of a field and any other properties, the change array entries
+     *                                 should have the new names of the fields as array indexes.
+     *
+     *                                The value of each entry of the array should be set to another associative
+     *                                 array with the properties of the fields to that are meant to be changed as
+     *                                 array entries. These entries should be assigned to the new values of the
+     *                                 respective properties. The properties of the fields should be the same
+     *                                 as defined by the MDB2 parser.
+     *
+     *                            Example
+     *                                array(
+     *                                    'name' => 'userlist',
+     *                                    'add' => array(
+     *                                        'quota' => array(
+     *                                            'type' => 'integer',
+     *                                            'unsigned' => 1
+     *                                        )
+     *                                    ),
+     *                                    'remove' => array(
+     *                                        'file_limit' => array(),
+     *                                        'time_limit' => array()
+     *                                    ),
+     *                                    'change' => array(
+     *                                        'name' => array(
+     *                                            'length' => '20',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 20,
+     *                                            ),
+     *                                        )
+     *                                    ),
+     *                                    'rename' => array(
+     *                                        'sex' => array(
+     *                                            'name' => 'gender',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 1,
+     *                                                'default' => 'M',
+     *                                            ),
+     *                                        )
+     *                                    )
+     *                                )
+     *
+     * @param boolean $check     indicates whether the function should just check if the DBMS driver
+     *                             can perform the requested table alterations if the value is true or
+     *                             actually perform them otherwise.
+     * @access public
+     *
+      * @return mixed MDB2_OK on success, a MDB2 error on failure
+     */
+    function alterTable($name, $changes, $check)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        foreach ($changes as $change_name => $change) {
+            switch ($change_name) {
+            case 'add':
+            case 'remove':
+            case 'change':
+            case 'rename':
+            case 'name':
+                break;
+            default:
+                return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null,
+                    'change type "'.$change_name.'" not yet supported', __FUNCTION__);
+            }
+        }
+
+        if ($check) {
+            return MDB2_OK;
+        }
+
+        $query = '';
+        if (!empty($changes['name'])) {
+            $change_name = $db->quoteIdentifier($changes['name'], true);
+            $query .= 'RENAME TO ' . $change_name;
+        }
+
+        if (!empty($changes['add']) && is_array($changes['add'])) {
+            foreach ($changes['add'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field);
+            }
+        }
+
+        if (!empty($changes['remove']) && is_array($changes['remove'])) {
+            foreach ($changes['remove'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $field_name = $db->quoteIdentifier($field_name, true);
+                $query.= 'DROP ' . $field_name;
+            }
+        }
+
+        $rename = array();
+        if (!empty($changes['rename']) && is_array($changes['rename'])) {
+            foreach ($changes['rename'] as $field_name => $field) {
+                $rename[$field['name']] = $field_name;
+            }
+        }
+
+        if (!empty($changes['change']) && is_array($changes['change'])) {
+            foreach ($changes['change'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                if (isset($rename[$field_name])) {
+                    $old_field_name = $rename[$field_name];
+                    unset($rename[$field_name]);
+                } else {
+                    $old_field_name = $field_name;
+                }
+                $old_field_name = $db->quoteIdentifier($old_field_name, true);
+                $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']);
+            }
+        }
+
+        if (!empty($rename) && is_array($rename)) {
+            foreach ($rename as $rename_name => $renamed_field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $field = $changes['rename'][$renamed_field];
+                $renamed_field = $db->quoteIdentifier($renamed_field, true);
+                $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']);
+            }
+        }
+
+        if (!$query) {
+            return MDB2_OK;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("ALTER TABLE $name $query");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listDatabases()
+
+    /**
+     * list all databases
+     *
+     * @return mixed array of database names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listDatabases()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $result = $db->queryCol('SHOW DATABASES');
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listUsers()
+
+    /**
+     * list all users
+     *
+     * @return mixed array of user names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listUsers()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER');
+    }
+
+    // }}}
+    // {{{ listFunctions()
+
+    /**
+     * list all functions in the current database
+     *
+     * @return mixed array of function names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listFunctions()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SELECT name FROM mysql.proc";
+        /*
+        SELECT ROUTINE_NAME
+          FROM INFORMATION_SCHEMA.ROUTINES
+         WHERE ROUTINE_TYPE = 'FUNCTION'
+        */
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTableTriggers()
+
+    /**
+     * list all triggers in the database that reference a given table
+     *
+     * @param string table for which all referenced triggers should be found
+     * @return mixed array of trigger names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableTriggers($table = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SHOW TRIGGERS';
+        if (null !== $table) {
+            $table = $db->quote($table, 'text');
+            $query .= " LIKE $table";
+        }
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTables()
+
+    /**
+     * list all tables in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of table names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTables($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SHOW /*!50002 FULL*/ TABLES";
+        if (null !== $database) {
+            $query .= " FROM $database";
+        }
+        $query.= "/*!50002  WHERE Table_type = 'BASE TABLE'*/";
+
+        $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED);
+        if (MDB2::isError($table_names)) {
+            return $table_names;
+        }
+
+        $result = array();
+        foreach ($table_names as $table) {
+            if (!$this->_fixSequenceName($table[0], true)) {
+                $result[] = $table[0];
+            }
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listViews()
+
+    /**
+     * list all views in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of view names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listViews($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SHOW FULL TABLES';
+        if (null !== $database) {
+            $query.= " FROM $database";
+        }
+        $query.= " WHERE Table_type = 'VIEW'";
+
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTableFields()
+
+    /**
+     * list all fields in a table in the current database
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of field names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableFields($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $result = $db->queryCol("SHOW COLUMNS FROM $table");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ createIndex()
+
+    /**
+     * Get the stucture of a field into an array
+     *
+     * @author Leoncx
+     * @param string $table      name of the table on which the index is to be created
+     * @param string $name       name of the index to be created
+     * @param array  $definition associative array that defines properties of the index to be created.
+     *                           Currently, only one property named FIELDS is supported. This property
+     *                           is also an associative with the names of the index fields as array
+     *                           indexes. Each entry of this array is set to another type of associative
+     *                           array that specifies properties of the index that are specific to
+     *                           each field.
+     *
+     *                           Currently, only the sorting property is supported. It should be used
+     *                           to define the sorting direction of the index. It may be set to either
+     *                           ascending or descending.
+     *
+     *                           Not all DBMS support index sorting direction configuration. The DBMS
+     *                           drivers of those that do not support it ignore this property. Use the
+     *                           function supports() to determine whether the DBMS driver can manage indexes.
+     *
+     *                           Example
+     *                               array(
+     *                                   'fields' => array(
+     *                                       'user_name' => array(
+     *                                           'sorting' => 'ascending'
+     *                                           'length' => 10
+     *                                       ),
+     *                                       'last_login' => array()
+     *                                    )
+     *                                )
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createIndex($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "CREATE INDEX $name ON $table";
+        $fields = array();
+        foreach ($definition['fields'] as $field => $fieldinfo) {
+            if (!empty($fieldinfo['length'])) {
+                $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')';
+            } else {
+                $fields[] = $db->quoteIdentifier($field, true);
+            }
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropIndex()
+
+    /**
+     * drop existing index
+     *
+     * @param string    $table         name of table that should be used in method
+     * @param string    $name         name of the index to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropIndex($table, $name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $result = $db->exec("DROP INDEX $name ON $table");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableIndexes()
+
+    /**
+     * list all indexes in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of index names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableIndexes($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $key_name = 'Key_name';
+        $non_unique = 'Non_unique';
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $key_name = strtolower($key_name);
+                $non_unique = strtolower($non_unique);
+            } else {
+                $key_name = strtoupper($key_name);
+                $non_unique = strtoupper($non_unique);
+            }
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table";
+        $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($indexes)) {
+            return $indexes;
+        }
+
+        $result = array();
+        foreach ($indexes as $index_data) {
+            if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) {
+                $result[$index] = true;
+            }
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_change_key_case($result, $db->options['field_case']);
+        }
+        return array_keys($result);
+    }
+
+    // }}}
+    // {{{ createConstraint()
+
+    /**
+     * create a constraint on a table
+     *
+     * @param string    $table        name of the table on which the constraint is to be created
+     * @param string    $name         name of the constraint to be created
+     * @param array     $definition   associative array that defines properties of the constraint to be created.
+     *                                Currently, only one property named FIELDS is supported. This property
+     *                                is also an associative with the names of the constraint fields as array
+     *                                constraints. Each entry of this array is set to another type of associative
+     *                                array that specifies properties of the constraint that are specific to
+     *                                each field.
+     *
+     *                                Example
+     *                                   array(
+     *                                       'fields' => array(
+     *                                           'user_name' => array(),
+     *                                           'last_login' => array()
+     *                                       )
+     *                                   )
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createConstraint($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $type = '';
+        $idx_name = $db->quoteIdentifier($db->getIndexName($name), true);
+        if (!empty($definition['primary'])) {
+            $type = 'PRIMARY';
+            $idx_name = 'KEY';
+        } elseif (!empty($definition['unique'])) {
+            $type = 'UNIQUE';
+        } elseif (!empty($definition['foreign'])) {
+            $type = 'CONSTRAINT';
+        }
+        if (empty($type)) {
+            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'invalid definition, could not create constraint', __FUNCTION__);
+        }
+
+        $table_quoted = $db->quoteIdentifier($table, true);
+        $query = "ALTER TABLE $table_quoted ADD $type $idx_name";
+        if (!empty($definition['foreign'])) {
+            $query .= ' FOREIGN KEY';
+        }
+        $fields = array();
+        foreach ($definition['fields'] as $field => $fieldinfo) {
+            $quoted = $db->quoteIdentifier($field, true);
+            if (!empty($fieldinfo['length'])) {
+                $quoted .= '(' . $fieldinfo['length'] . ')';
+            }
+            $fields[] = $quoted;
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        if (!empty($definition['foreign'])) {
+            $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true);
+            $referenced_fields = array();
+            foreach (array_keys($definition['references']['fields']) as $field) {
+                $referenced_fields[] = $db->quoteIdentifier($field, true);
+            }
+            $query .= ' ('. implode(', ', $referenced_fields) . ')';
+            $query .= $this->_getAdvancedFKOptions($definition);
+
+            // add index on FK column(s) or we can't add a FK constraint
+            // @see http://forums.mysql.com/read.php?22,19755,226009
+            $result = $this->createIndex($table, $name.'_fkidx', $definition);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $res = $db->exec($query);
+        if (MDB2::isError($res)) {
+            return $res;
+        }
+        if (!empty($definition['foreign'])) {
+            return $this->_createFKTriggers($table, array($name => $definition));
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropConstraint()
+
+    /**
+     * drop existing constraint
+     *
+     * @param string    $table        name of table that should be used in method
+     * @param string    $name         name of the constraint to be dropped
+     * @param string    $primary      hint if the constraint is primary
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropConstraint($table, $name, $primary = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if ($primary || strtolower($name) == 'primary') {
+            $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY';
+            $result = $db->exec($query);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            return MDB2_OK;
+        }
+
+        //is it a FK constraint? If so, also delete the associated triggers
+        $db->loadModule('Reverse', null, true);
+        $definition = $db->reverse->getTableConstraintDefinition($table, $name);
+        if (!MDB2::isError($definition) && !empty($definition['foreign'])) {
+            //first drop the FK enforcing triggers
+            $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            //then drop the constraint itself
+            $table = $db->quoteIdentifier($table, true);
+            $name = $db->quoteIdentifier($db->getIndexName($name), true);
+            $query = "ALTER TABLE $table DROP FOREIGN KEY $name";
+            $result = $db->exec($query);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            return MDB2_OK;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "ALTER TABLE $table DROP INDEX $name";
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _createFKTriggers()
+
+    /**
+     * Create triggers to enforce the FOREIGN KEY constraint on the table
+     *
+     * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql,
+     * we call a non-existent procedure to raise the FK violation message.
+     * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877
+     *
+     * @param string $table        table name
+     * @param array  $foreign_keys FOREIGN KEY definitions
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access private
+     */
+    function _createFKTriggers($table, $foreign_keys)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        // create triggers to enforce FOREIGN KEY constraints
+        if ($db->supports('triggers') && !empty($foreign_keys)) {
+            $table_quoted = $db->quoteIdentifier($table, true);
+            foreach ($foreign_keys as $fkname => $fkdef) {
+                if (empty($fkdef)) {
+                    continue;
+                }
+                //set actions to default if not set
+                $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']);
+                $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']);
+
+                $trigger_names = array(
+                    'insert'    => $fkname.'_insert_trg',
+                    'update'    => $fkname.'_update_trg',
+                    'pk_update' => $fkname.'_pk_update_trg',
+                    'pk_delete' => $fkname.'_pk_delete_trg',
+                );
+                $table_fields = array_keys($fkdef['fields']);
+                $referenced_fields = array_keys($fkdef['references']['fields']);
+
+                //create the ON [UPDATE|DELETE] triggers on the primary table
+                $restrict_action = ' IF (SELECT ';
+                $aliased_fields = array();
+                foreach ($table_fields as $field) {
+                    $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field;
+                }
+                $restrict_action .= implode(',', $aliased_fields)
+                       .' FROM '.$table_quoted
+                       .' WHERE ';
+                $conditions  = array();
+                $new_values  = array();
+                $null_values = array();
+                for ($i=0; $i<count($table_fields); $i++) {
+                    $conditions[]  = $table_fields[$i] .' = OLD.'.$referenced_fields[$i];
+                    $new_values[]  = $table_fields[$i] .' = NEW.'.$referenced_fields[$i];
+                    $null_values[] = $table_fields[$i] .' = NULL';
+                }
+                $conditions2 = array();
+                for ($i=0; $i<count($referenced_fields); $i++) {
+                    $conditions2[]  = 'NEW.'.$referenced_fields[$i] .' <> OLD.'.$referenced_fields[$i];
+                }
+
+                $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL';
+                $restrict_action2 = empty($conditions2) ? '' : ' AND (' .implode(' OR ', $conditions2) .')';
+                $restrict_action3 = ' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();'
+                                   .' END IF;';
+
+                $restrict_action_update = $restrict_action . $restrict_action2 . $restrict_action3;
+                $restrict_action_delete = $restrict_action . $restrict_action3; // There is no NEW row in on DELETE trigger
+
+                $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';';
+                $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';';
+                $setnull_action        = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';';
+
+                if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) {
+                    $db->loadModule('Reverse', null, true);
+                    $default_values = array();
+                    foreach ($table_fields as $table_field) {
+                        $field_definition = $db->reverse->getTableFieldDefinition($table, $field);
+                        if (MDB2::isError($field_definition)) {
+                            return $field_definition;
+                        }
+                        $default_values[] = $table_field .' = '. $field_definition[0]['default'];
+                    }
+                    $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';';
+                }
+
+                $query = 'CREATE TRIGGER %s'
+                        .' %s ON '.$fkdef['references']['table']
+                        .' FOR EACH ROW BEGIN '
+                        .' SET FOREIGN_KEY_CHECKS = 0; ';  //only really needed for ON UPDATE CASCADE
+
+                if ('CASCADE' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE',  'update') . $cascade_action_update;
+                } elseif ('SET NULL' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action;
+                } elseif ('SET DEFAULT' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action;
+                } elseif ('NO ACTION' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'AFTER UPDATE', 'update');
+                } elseif ('RESTRICT' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update');
+                }
+                if ('CASCADE' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE',  'delete') . $cascade_action_delete;
+                } elseif ('SET NULL' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action;
+                } elseif ('SET DEFAULT' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action;
+                } elseif ('NO ACTION' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete');
+                } elseif ('RESTRICT' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete');
+                }
+                $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;';
+                $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;';
+
+                $db->pushErrorHandling(PEAR_ERROR_RETURN);
+                $db->expectError(MDB2_ERROR_CANNOT_CREATE);
+                $result = $db->exec($sql_delete);
+                $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table';
+                $db->popExpect();
+                $db->popErrorHandling();
+                if (MDB2::isError($result)) {
+                    if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                        return $result;
+                    }
+                    $db->warnings[] = $expected_errmsg;
+                }
+                $db->pushErrorHandling(PEAR_ERROR_RETURN);
+                $db->expectError(MDB2_ERROR_CANNOT_CREATE);
+                $result = $db->exec($sql_update);
+                $db->popExpect();
+                $db->popErrorHandling();
+                if (MDB2::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                    if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                        return $result;
+                    }
+                    $db->warnings[] = $expected_errmsg;
+                }
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _dropFKTriggers()
+
+    /**
+     * Drop the triggers created to enforce the FOREIGN KEY constraint on the table
+     *
+     * @param string $table            table name
+     * @param string $fkname           FOREIGN KEY constraint name
+     * @param string $referenced_table referenced table name
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access private
+     */
+    function _dropFKTriggers($table, $fkname, $referenced_table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $triggers  = $this->listTableTriggers($table);
+        $triggers2 = $this->listTableTriggers($referenced_table);
+        if (!MDB2::isError($triggers2) && !MDB2::isError($triggers)) {
+            $triggers = array_merge($triggers, $triggers2);
+            $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i';
+            foreach ($triggers as $trigger) {
+                if (preg_match($pattern, $trigger)) {
+                    $result = $db->exec('DROP TRIGGER '.$trigger);
+                    if (MDB2::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableConstraints()
+
+    /**
+     * list all constraints in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of constraint names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableConstraints($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $key_name = 'Key_name';
+        $non_unique = 'Non_unique';
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $key_name = strtolower($key_name);
+                $non_unique = strtolower($non_unique);
+            } else {
+                $key_name = strtoupper($key_name);
+                $non_unique = strtoupper($non_unique);
+            }
+        }
+
+        $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true);
+        $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($indexes)) {
+            return $indexes;
+        }
+
+        $result = array();
+        foreach ($indexes as $index_data) {
+            if (!$index_data[$non_unique]) {
+                if ($index_data[$key_name] !== 'PRIMARY') {
+                    $index = $this->_fixIndexName($index_data[$key_name]);
+                } else {
+                    $index = 'PRIMARY';
+                }
+                if (!empty($index)) {
+                    $result[$index] = true;
+                }
+            }
+        }
+        
+        //list FOREIGN KEY constraints...
+        $query = 'SHOW CREATE TABLE '. $db->escape($table);
+        $definition = $db->queryOne($query, 'text', 1);
+        if (!MDB2::isError($definition) && !empty($definition)) {
+            $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims';
+            if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) {
+                foreach ($matches[1] as $constraint) {
+                    $result[$constraint] = true;
+                }
+            }
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_change_key_case($result, $db->options['field_case']);
+        }
+        return array_keys($result);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * create sequence
+     *
+     * @param string    $seq_name name of the sequence to be created
+     * @param string    $start    start value of the sequence; default is 1
+     * @param array     $options  An associative array of table options:
+     *                          array(
+     *                              'comment' => 'Foo',
+     *                              'charset' => 'utf8',
+     *                              'collate' => 'utf8_unicode_ci',
+     *                              'type'    => 'innodb',
+     *                          );
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createSequence($seq_name, $start = 1, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true);
+        $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true);
+        
+        $options_strings = array();
+
+        if (!empty($options['comment'])) {
+            $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text');
+        }
+
+        if (!empty($options['charset'])) {
+            $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset'];
+            if (!empty($options['collate'])) {
+                $options_strings['charset'].= ' COLLATE '.$options['collate'];
+            }
+        }
+
+        $type = false;
+        if (!empty($options['type'])) {
+            $type = $options['type'];
+        } elseif ($db->options['default_table_type']) {
+            $type = $db->options['default_table_type'];
+        }
+        if ($type) {
+            $options_strings[] = "ENGINE = $type";
+        }
+
+        $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))";
+        if (!empty($options_strings)) {
+            $query .= ' '.implode(' ', $options_strings);
+        }
+        $res = $db->exec($query);
+        if (MDB2::isError($res)) {
+            return $res;
+        }
+
+        if ($start == 1) {
+            return MDB2_OK;
+        }
+
+        $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')';
+        $res = $db->exec($query);
+        if (!MDB2::isError($res)) {
+            return MDB2_OK;
+        }
+
+        // Handle error
+        $result = $db->exec("DROP TABLE $sequence_name");
+        if (MDB2::isError($result)) {
+            return $db->raiseError($result, null, null,
+                'could not drop inconsistent sequence table', __FUNCTION__);
+        }
+
+        return $db->raiseError($res, null, null,
+            'could not create sequence table', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * drop existing sequence
+     *
+     * @param string    $seq_name     name of the sequence to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropSequence($seq_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true);
+        $result = $db->exec("DROP TABLE $sequence_name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listSequences()
+
+    /**
+     * list all sequences in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of sequence names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listSequences($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SHOW TABLES";
+        if (null !== $database) {
+            $query .= " FROM $database";
+        }
+        $table_names = $db->queryCol($query);
+        if (MDB2::isError($table_names)) {
+            return $table_names;
+        }
+
+        $result = array();
+        foreach ($table_names as $table_name) {
+            if ($sqn = $this->_fixSequenceName($table_name, true)) {
+                $result[] = $sqn;
+            }
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Manager/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/Manager/mysqli.php
new file mode 100644 (file)
index 0000000..8af564a
--- /dev/null
@@ -0,0 +1,1471 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Manager/Common.php';
+
+/**
+ * MDB2 MySQLi driver for the management modules
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Manager_mysqli extends MDB2_Driver_Manager_Common
+{
+
+    // }}}
+    // {{{ createDatabase()
+
+    /**
+     * create a new database
+     *
+     * @param string $name    name of the database that should be created
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createDatabase($name, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name  = $db->quoteIdentifier($name, true);
+        $query = 'CREATE DATABASE ' . $name;
+        if (!empty($options['charset'])) {
+            $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text');
+        }
+        if (!empty($options['collation'])) {
+            $query .= ' COLLATE ' . $db->quote($options['collation'], 'text');
+        }
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ alterDatabase()
+
+    /**
+     * alter an existing database
+     *
+     * @param string $name    name of the database that is intended to be changed
+     * @param array  $options array with charset, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function alterDatabase($name, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true);
+        if (!empty($options['charset'])) {
+            $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text');
+        }
+        if (!empty($options['collation'])) {
+            $query .= ' COLLATE ' . $db->quote($options['collation'], 'text');
+        }
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ dropDatabase()
+
+    /**
+     * drop an existing database
+     *
+     * @param string $name name of the database that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropDatabase($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $query = "DROP DATABASE $name";
+        return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ _getAdvancedFKOptions()
+
+    /**
+     * Return the FOREIGN KEY query section dealing with non-standard options
+     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+     *
+     * @param array $definition
+     * @return string
+     * @access protected
+     */
+    function _getAdvancedFKOptions($definition)
+    {
+        $query = '';
+        if (!empty($definition['match'])) {
+            $query .= ' MATCH '.$definition['match'];
+        }
+        if (!empty($definition['onupdate'])) {
+            $query .= ' ON UPDATE '.$definition['onupdate'];
+        }
+        if (!empty($definition['ondelete'])) {
+            $query .= ' ON DELETE '.$definition['ondelete'];
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ createTable()
+
+    /**
+     * create a new table
+     *
+     * @param string $name   Name of the database that should be created
+     * @param array $fields  Associative array that contains the definition of each field of the new table
+     *                       The indexes of the array entries are the names of the fields of the table an
+     *                       the array entry values are associative arrays like those that are meant to be
+     *                       passed with the field definitions to get[Type]Declaration() functions.
+     *                          array(
+     *                              'id' => array(
+     *                                  'type' => 'integer',
+     *                                  'unsigned' => 1
+     *                                  'notnull' => 1
+     *                                  'default' => 0
+     *                              ),
+     *                              'name' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              ),
+     *                              'password' => array(
+     *                                  'type' => 'text',
+     *                                  'length' => 12
+     *                              )
+     *                          );
+     * @param array $options  An associative array of table options:
+     *                          array(
+     *                              'comment' => 'Foo',
+     *                              'charset' => 'utf8',
+     *                              'collate' => 'utf8_unicode_ci',
+     *                              'type'    => 'innodb',
+     *                          );
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createTable($name, $fields, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        // if we have an AUTO_INCREMENT column and a PK on more than one field,
+        // we have to handle it differently...
+        $autoincrement = null;
+        if (empty($options['primary'])) {
+            $pk_fields = array();
+            foreach ($fields as $fieldname => $def) {
+                if (!empty($def['primary'])) {
+                    $pk_fields[$fieldname] = true;
+                }
+                if (!empty($def['autoincrement'])) {
+                    $autoincrement = $fieldname;
+                }
+            }
+            if ((null !== $autoincrement) && count($pk_fields) > 1) {
+                $options['primary'] = $pk_fields;
+            } else {
+                // the PK constraint is on max one field => OK
+                $autoincrement = null;
+            }
+        }
+
+        $query = $this->_getCreateTableQuery($name, $fields, $options);
+        if (MDB2::isError($query)) {
+            return $query;
+        }
+
+        if (null !== $autoincrement) {
+            // we have to remove the PK clause added by _getIntegerDeclaration()
+            $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query);
+        }
+
+        $options_strings = array();
+
+        if (!empty($options['comment'])) {
+            $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text');
+        }
+
+        if (!empty($options['charset'])) {
+            $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset'];
+            if (!empty($options['collate'])) {
+                $options_strings['charset'].= ' COLLATE '.$options['collate'];
+            }
+        }
+
+        $type = false;
+        if (!empty($options['type'])) {
+            $type = $options['type'];
+        } elseif ($db->options['default_table_type']) {
+            $type = $db->options['default_table_type'];
+        }
+        if ($type) {
+            $options_strings[] = "ENGINE = $type";
+        }
+
+        if (!empty($options_strings)) {
+            $query .= ' '.implode(' ', $options_strings);
+        }
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropTable()
+
+    /**
+     * drop an existing table
+     *
+     * @param string $name name of the table that should be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        //delete the triggers associated to existing FK constraints
+        $constraints = $this->listTableConstraints($name);
+        if (!MDB2::isError($constraints) && !empty($constraints)) {
+            $db->loadModule('Reverse', null, true);
+            foreach ($constraints as $constraint) {
+                $definition = $db->reverse->getTableConstraintDefinition($name, $constraint);
+                if (!MDB2::isError($definition) && !empty($definition['foreign'])) {
+                    $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']);
+                    if (MDB2::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+        }
+
+        return parent::dropTable($name);
+    }
+
+    // }}}
+    // {{{ truncateTable()
+
+    /**
+     * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported,
+     * it falls back to a DELETE FROM TABLE query)
+     *
+     * @param string $name name of the table that should be truncated
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function truncateTable($name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("TRUNCATE TABLE $name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ vacuum()
+
+    /**
+     * Optimize (vacuum) all the tables in the db (or only the specified table)
+     * and optionally run ANALYZE.
+     *
+     * @param string $table table name (all the tables if empty)
+     * @param array  $options an array with driver-specific options:
+     *               - timeout [int] (in seconds) [mssql-only]
+     *               - analyze [boolean] [pgsql and mysql]
+     *               - full [boolean] [pgsql-only]
+     *               - freeze [boolean] [pgsql-only]
+     *
+     * @return mixed MDB2_OK success, a MDB2 error on failure
+     * @access public
+     */
+    function vacuum($table = null, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (empty($table)) {
+            $table = $this->listTables();
+            if (MDB2::isError($table)) {
+                return $table;
+            }
+        }
+        if (is_array($table)) {
+            foreach (array_keys($table) as $k) {
+               $table[$k] = $db->quoteIdentifier($table[$k], true);
+            }
+            $table = implode(', ', $table);
+        } else {
+            $table = $db->quoteIdentifier($table, true);
+        }
+
+        $result = $db->exec('OPTIMIZE TABLE '.$table);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!empty($options['analyze'])) {
+            $result = $db->exec('ANALYZE TABLE '.$table);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ alterTable()
+
+    /**
+     * alter an existing table
+     *
+     * @param string $name         name of the table that is intended to be changed.
+     * @param array $changes     associative array that contains the details of each type
+     *                             of change that is intended to be performed. The types of
+     *                             changes that are currently supported are defined as follows:
+     *
+     *                             name
+     *
+     *                                New name for the table.
+     *
+     *                            add
+     *
+     *                                Associative array with the names of fields to be added as
+     *                                 indexes of the array. The value of each entry of the array
+     *                                 should be set to another associative array with the properties
+     *                                 of the fields to be added. The properties of the fields should
+     *                                 be the same as defined by the MDB2 parser.
+     *
+     *
+     *                            remove
+     *
+     *                                Associative array with the names of fields to be removed as indexes
+     *                                 of the array. Currently the values assigned to each entry are ignored.
+     *                                 An empty array should be used for future compatibility.
+     *
+     *                            rename
+     *
+     *                                Associative array with the names of fields to be renamed as indexes
+     *                                 of the array. The value of each entry of the array should be set to
+     *                                 another associative array with the entry named name with the new
+     *                                 field name and the entry named Declaration that is expected to contain
+     *                                 the portion of the field declaration already in DBMS specific SQL code
+     *                                 as it is used in the CREATE TABLE statement.
+     *
+     *                            change
+     *
+     *                                Associative array with the names of the fields to be changed as indexes
+     *                                 of the array. Keep in mind that if it is intended to change either the
+     *                                 name of a field and any other properties, the change array entries
+     *                                 should have the new names of the fields as array indexes.
+     *
+     *                                The value of each entry of the array should be set to another associative
+     *                                 array with the properties of the fields to that are meant to be changed as
+     *                                 array entries. These entries should be assigned to the new values of the
+     *                                 respective properties. The properties of the fields should be the same
+     *                                 as defined by the MDB2 parser.
+     *
+     *                            Example
+     *                                array(
+     *                                    'name' => 'userlist',
+     *                                    'add' => array(
+     *                                        'quota' => array(
+     *                                            'type' => 'integer',
+     *                                            'unsigned' => 1
+     *                                        )
+     *                                    ),
+     *                                    'remove' => array(
+     *                                        'file_limit' => array(),
+     *                                        'time_limit' => array()
+     *                                    ),
+     *                                    'change' => array(
+     *                                        'name' => array(
+     *                                            'length' => '20',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 20,
+     *                                            ),
+     *                                        )
+     *                                    ),
+     *                                    'rename' => array(
+     *                                        'sex' => array(
+     *                                            'name' => 'gender',
+     *                                            'definition' => array(
+     *                                                'type' => 'text',
+     *                                                'length' => 1,
+     *                                                'default' => 'M',
+     *                                            ),
+     *                                        )
+     *                                    )
+     *                                )
+     *
+     * @param boolean $check     indicates whether the function should just check if the DBMS driver
+     *                             can perform the requested table alterations if the value is true or
+     *                             actually perform them otherwise.
+     * @access public
+     *
+      * @return mixed MDB2_OK on success, a MDB2 error on failure
+     */
+    function alterTable($name, $changes, $check)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        foreach ($changes as $change_name => $change) {
+            switch ($change_name) {
+            case 'add':
+            case 'remove':
+            case 'change':
+            case 'rename':
+            case 'name':
+                break;
+            default:
+                return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null,
+                    'change type "'.$change_name.'" not yet supported', __FUNCTION__);
+            }
+        }
+
+        if ($check) {
+            return MDB2_OK;
+        }
+
+        $query = '';
+        if (!empty($changes['name'])) {
+            $change_name = $db->quoteIdentifier($changes['name'], true);
+            $query .= 'RENAME TO ' . $change_name;
+        }
+
+        if (!empty($changes['add']) && is_array($changes['add'])) {
+            foreach ($changes['add'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field);
+            }
+        }
+
+        if (!empty($changes['remove']) && is_array($changes['remove'])) {
+            foreach ($changes['remove'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $field_name = $db->quoteIdentifier($field_name, true);
+                $query.= 'DROP ' . $field_name;
+            }
+        }
+
+        $rename = array();
+        if (!empty($changes['rename']) && is_array($changes['rename'])) {
+            foreach ($changes['rename'] as $field_name => $field) {
+                $rename[$field['name']] = $field_name;
+            }
+        }
+
+        if (!empty($changes['change']) && is_array($changes['change'])) {
+            foreach ($changes['change'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                if (isset($rename[$field_name])) {
+                    $old_field_name = $rename[$field_name];
+                    unset($rename[$field_name]);
+                } else {
+                    $old_field_name = $field_name;
+                }
+                $old_field_name = $db->quoteIdentifier($old_field_name, true);
+                $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']);
+            }
+        }
+
+        if (!empty($rename) && is_array($rename)) {
+            foreach ($rename as $rename_name => $renamed_field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $field = $changes['rename'][$renamed_field];
+                $renamed_field = $db->quoteIdentifier($renamed_field, true);
+                $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']);
+            }
+        }
+
+        if (!$query) {
+            return MDB2_OK;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        $result = $db->exec("ALTER TABLE $name $query");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listDatabases()
+
+    /**
+     * list all databases
+     *
+     * @return mixed array of database names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listDatabases()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $result = $db->queryCol('SHOW DATABASES');
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listUsers()
+
+    /**
+     * list all users
+     *
+     * @return mixed array of user names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listUsers()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER');
+    }
+
+    // }}}
+    // {{{ listFunctions()
+
+    /**
+     * list all functions in the current database
+     *
+     * @return mixed array of function names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listFunctions()
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SELECT name FROM mysql.proc";
+        /*
+        SELECT ROUTINE_NAME
+          FROM INFORMATION_SCHEMA.ROUTINES
+         WHERE ROUTINE_TYPE = 'FUNCTION'
+        */
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTableTriggers()
+
+    /**
+     * list all triggers in the database that reference a given table
+     *
+     * @param string table for which all referenced triggers should be found
+     * @return mixed array of trigger names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableTriggers($table = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SHOW TRIGGERS';
+        if (null !== $table) {
+            $table = $db->quote($table, 'text');
+            $query .= " LIKE $table";
+        }
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTables()
+
+    /**
+     * list all tables in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of table names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTables($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SHOW /*!50002 FULL*/ TABLES";
+        if (null !== $database) {
+            $query .= " FROM $database";
+        }
+        $query.= "/*!50002  WHERE Table_type = 'BASE TABLE'*/";
+
+        $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED);
+        if (MDB2::isError($table_names)) {
+            return $table_names;
+        }
+
+        $result = array();
+        foreach ($table_names as $table) {
+            if (!$this->_fixSequenceName($table[0], true)) {
+                $result[] = $table[0];
+            }
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listViews()
+
+    /**
+     * list all views in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of view names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listViews($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SHOW FULL TABLES';
+        if (null !== $database) {
+            $query.= " FROM $database";
+        }
+        $query.= " WHERE Table_type = 'VIEW'";
+
+        $result = $db->queryCol($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ listTableFields()
+
+    /**
+     * list all fields in a table in the current database
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of field names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableFields($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $result = $db->queryCol("SHOW COLUMNS FROM $table");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ createIndex()
+
+    /**
+     * Get the stucture of a field into an array
+     *
+     * @author Leoncx
+     * @param string    $table         name of the table on which the index is to be created
+     * @param string    $name         name of the index to be created
+     * @param array     $definition        associative array that defines properties of the index to be created.
+     *                                 Currently, only one property named FIELDS is supported. This property
+     *                                 is also an associative with the names of the index fields as array
+     *                                 indexes. Each entry of this array is set to another type of associative
+     *                                 array that specifies properties of the index that are specific to
+     *                                 each field.
+     *
+     *                                Currently, only the sorting property is supported. It should be used
+     *                                 to define the sorting direction of the index. It may be set to either
+     *                                 ascending or descending.
+     *
+     *                                Not all DBMS support index sorting direction configuration. The DBMS
+     *                                 drivers of those that do not support it ignore this property. Use the
+     *                                 function supports() to determine whether the DBMS driver can manage indexes.
+     *
+     *                                 Example
+     *                                    array(
+     *                                        'fields' => array(
+     *                                            'user_name' => array(
+     *                                                'sorting' => 'ascending'
+     *                                                'length' => 10
+     *                                            ),
+     *                                            'last_login' => array()
+     *                                        )
+     *                                    )
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createIndex($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "CREATE INDEX $name ON $table";
+        $fields = array();
+        foreach ($definition['fields'] as $field => $fieldinfo) {
+            if (!empty($fieldinfo['length'])) {
+                $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')';
+            } else {
+                $fields[] = $db->quoteIdentifier($field, true);
+            }
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropIndex()
+
+    /**
+     * drop existing index
+     *
+     * @param string    $table         name of table that should be used in method
+     * @param string    $name         name of the index to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropIndex($table, $name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $result = $db->exec("DROP INDEX $name ON $table");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableIndexes()
+
+    /**
+     * list all indexes in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of index names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableIndexes($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $key_name = 'Key_name';
+        $non_unique = 'Non_unique';
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $key_name = strtolower($key_name);
+                $non_unique = strtolower($non_unique);
+            } else {
+                $key_name = strtoupper($key_name);
+                $non_unique = strtoupper($non_unique);
+            }
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table";
+        $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($indexes)) {
+            return $indexes;
+        }
+
+        $result = array();
+        foreach ($indexes as $index_data) {
+            if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) {
+                $result[$index] = true;
+            }
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_change_key_case($result, $db->options['field_case']);
+        }
+        return array_keys($result);
+    }
+
+    // }}}
+    // {{{ createConstraint()
+
+    /**
+     * create a constraint on a table
+     *
+     * @param string    $table         name of the table on which the constraint is to be created
+     * @param string    $name         name of the constraint to be created
+     * @param array     $definition        associative array that defines properties of the constraint to be created.
+     *                                 Currently, only one property named FIELDS is supported. This property
+     *                                 is also an associative with the names of the constraint fields as array
+     *                                 constraints. Each entry of this array is set to another type of associative
+     *                                 array that specifies properties of the constraint that are specific to
+     *                                 each field.
+     *
+     *                                 Example
+     *                                    array(
+     *                                        'fields' => array(
+     *                                            'user_name' => array(),
+     *                                            'last_login' => array()
+     *                                        )
+     *                                    )
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createConstraint($table, $name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $type = '';
+        $idx_name = $db->quoteIdentifier($db->getIndexName($name), true);
+        if (!empty($definition['primary'])) {
+            $type = 'PRIMARY';
+            $idx_name = 'KEY';
+        } elseif (!empty($definition['unique'])) {
+            $type = 'UNIQUE';
+        } elseif (!empty($definition['foreign'])) {
+            $type = 'CONSTRAINT';
+        }
+        if (empty($type)) {
+            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'invalid definition, could not create constraint', __FUNCTION__);
+        }
+
+        $table_quoted = $db->quoteIdentifier($table, true);
+        $query = "ALTER TABLE $table_quoted ADD $type $idx_name";
+        if (!empty($definition['foreign'])) {
+            $query .= ' FOREIGN KEY';
+        }
+        $fields = array();
+        foreach ($definition['fields'] as $field => $fieldinfo) {
+            $quoted = $db->quoteIdentifier($field, true);
+            if (!empty($fieldinfo['length'])) {
+                $quoted .= '(' . $fieldinfo['length'] . ')';
+            }
+            $fields[] = $quoted;
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+        if (!empty($definition['foreign'])) {
+            $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true);
+            $referenced_fields = array();
+            foreach (array_keys($definition['references']['fields']) as $field) {
+                $referenced_fields[] = $db->quoteIdentifier($field, true);
+            }
+            $query .= ' ('. implode(', ', $referenced_fields) . ')';
+            $query .= $this->_getAdvancedFKOptions($definition);
+
+            // add index on FK column(s) or we can't add a FK constraint
+            // @see http://forums.mysql.com/read.php?22,19755,226009
+            $result = $this->createIndex($table, $name.'_fkidx', $definition);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $res = $db->exec($query);
+        if (MDB2::isError($res)) {
+            return $res;
+        }
+        if (!empty($definition['foreign'])) {
+            return $this->_createFKTriggers($table, array($name => $definition));
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ dropConstraint()
+
+    /**
+     * drop existing constraint
+     *
+     * @param string    $table        name of table that should be used in method
+     * @param string    $name         name of the constraint to be dropped
+     * @param string    $primary      hint if the constraint is primary
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropConstraint($table, $name, $primary = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        
+        if ($primary || strtolower($name) == 'primary') {
+            $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY';
+            $result = $db->exec($query);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            return MDB2_OK;
+        }
+        
+        //is it a FK constraint? If so, also delete the associated triggers
+        $db->loadModule('Reverse', null, true);
+        $definition = $db->reverse->getTableConstraintDefinition($table, $name);
+        if (!MDB2::isError($definition) && !empty($definition['foreign'])) {
+            //first drop the FK enforcing triggers
+            $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            //then drop the constraint itself
+            $table = $db->quoteIdentifier($table, true);
+            $name = $db->quoteIdentifier($db->getIndexName($name), true);
+            $query = "ALTER TABLE $table DROP FOREIGN KEY $name";
+            $result = $db->exec($query);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            return MDB2_OK;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $name = $db->quoteIdentifier($db->getIndexName($name), true);
+        $query = "ALTER TABLE $table DROP INDEX $name";
+        $result = $db->exec($query);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _createFKTriggers()
+
+    /**
+     * Create triggers to enforce the FOREIGN KEY constraint on the table
+     *
+     * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql,
+     * we call a non-existent procedure to raise the FK violation message.
+     * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877
+     *
+     * @param string $table        table name
+     * @param array  $foreign_keys FOREIGN KEY definitions
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access private
+     */
+    function _createFKTriggers($table, $foreign_keys)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        // create triggers to enforce FOREIGN KEY constraints
+        if ($db->supports('triggers') && !empty($foreign_keys)) {
+            $table_quoted = $db->quoteIdentifier($table, true);
+            foreach ($foreign_keys as $fkname => $fkdef) {
+                if (empty($fkdef)) {
+                    continue;
+                }
+                //set actions to default if not set
+                $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']);
+                $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']);
+
+                $trigger_names = array(
+                    'insert'    => $fkname.'_insert_trg',
+                    'update'    => $fkname.'_update_trg',
+                    'pk_update' => $fkname.'_pk_update_trg',
+                    'pk_delete' => $fkname.'_pk_delete_trg',
+                );
+                $table_fields = array_keys($fkdef['fields']);
+                $referenced_fields = array_keys($fkdef['references']['fields']);
+
+                //create the ON [UPDATE|DELETE] triggers on the primary table
+                $restrict_action = ' IF (SELECT ';
+                $aliased_fields = array();
+                foreach ($table_fields as $field) {
+                    $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field;
+                }
+                $restrict_action .= implode(',', $aliased_fields)
+                       .' FROM '.$table_quoted
+                       .' WHERE ';
+                $conditions  = array();
+                $new_values  = array();
+                $null_values = array();
+                for ($i=0; $i<count($table_fields); $i++) {
+                    $conditions[]  = $table_fields[$i] .' = OLD.'.$referenced_fields[$i];
+                    $new_values[]  = $table_fields[$i] .' = NEW.'.$referenced_fields[$i];
+                    $null_values[] = $table_fields[$i] .' = NULL';
+                }
+                $conditions2 = array();
+                for ($i=0; $i<count($referenced_fields); $i++) {
+                    $conditions2[]  = 'NEW.'.$referenced_fields[$i] .' <> OLD.'.$referenced_fields[$i];
+                }
+
+                $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL';
+                $restrict_action2 = empty($conditions2) ? '' : ' AND (' .implode(' OR ', $conditions2) .')';
+                $restrict_action3 = ' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();'
+                                   .' END IF;';
+
+                $restrict_action_update = $restrict_action . $restrict_action2 . $restrict_action3;
+                $restrict_action_delete = $restrict_action . $restrict_action3; // There is no NEW row in on DELETE trigger
+
+                $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';';
+                $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';';
+                $setnull_action        = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';';
+
+                if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) {
+                    $db->loadModule('Reverse', null, true);
+                    $default_values = array();
+                    foreach ($table_fields as $table_field) {
+                        $field_definition = $db->reverse->getTableFieldDefinition($table, $field);
+                        if (MDB2::isError($field_definition)) {
+                            return $field_definition;
+                        }
+                        $default_values[] = $table_field .' = '. $field_definition[0]['default'];
+                    }
+                    $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';';
+                }
+
+                $query = 'CREATE TRIGGER %s'
+                        .' %s ON '.$fkdef['references']['table']
+                        .' FOR EACH ROW BEGIN '
+                        .' SET FOREIGN_KEY_CHECKS = 0; ';  //only really needed for ON UPDATE CASCADE
+
+                if ('CASCADE' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE',  'update') . $cascade_action_update;
+                } elseif ('SET NULL' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action;
+                } elseif ('SET DEFAULT' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action;
+                } elseif ('NO ACTION' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'AFTER UPDATE', 'update');
+                } elseif ('RESTRICT' == $fkdef['onupdate']) {
+                    $sql_update = sprintf($query.$restrict_action_update, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update');
+                }
+                if ('CASCADE' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE',  'delete') . $cascade_action_delete;
+                } elseif ('SET NULL' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action;
+                } elseif ('SET DEFAULT' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action;
+                } elseif ('NO ACTION' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete');
+                } elseif ('RESTRICT' == $fkdef['ondelete']) {
+                    $sql_delete = sprintf($query.$restrict_action_delete, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete');
+                }
+                $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;';
+                $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;';
+
+                $db->pushErrorHandling(PEAR_ERROR_RETURN);
+                $db->expectError(MDB2_ERROR_CANNOT_CREATE); 
+                $result = $db->exec($sql_delete);
+                $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table';
+                $db->popExpect();
+                $db->popErrorHandling();
+                if (MDB2::isError($result)) {
+                    if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                        return $result;
+                    }
+                    $db->warnings[] = $expected_errmsg;
+                }
+                $db->pushErrorHandling(PEAR_ERROR_RETURN);
+                $db->expectError(MDB2_ERROR_CANNOT_CREATE);
+                $result = $db->exec($sql_update);
+                $db->popExpect();
+                $db->popErrorHandling();
+                if (MDB2::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                    if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) {
+                        return $result;
+                    }
+                    $db->warnings[] = $expected_errmsg;
+                }
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _dropFKTriggers()
+
+    /**
+     * Drop the triggers created to enforce the FOREIGN KEY constraint on the table
+     *
+     * @param string $table            table name
+     * @param string $fkname           FOREIGN KEY constraint name
+     * @param string $referenced_table referenced table name
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access private
+     */
+    function _dropFKTriggers($table, $fkname, $referenced_table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $triggers  = $this->listTableTriggers($table);
+        $triggers2 = $this->listTableTriggers($referenced_table);
+        if (!MDB2::isError($triggers2) && !MDB2::isError($triggers)) {
+            $triggers = array_merge($triggers, $triggers2);
+            $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i';
+            foreach ($triggers as $trigger) {
+                if (preg_match($pattern, $trigger)) {
+                    $result = $db->exec('DROP TRIGGER '.$trigger);
+                    if (MDB2::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listTableConstraints()
+
+    /**
+     * list all constraints in a table
+     *
+     * @param string $table name of table that should be used in method
+     * @return mixed array of constraint names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listTableConstraints($table)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $key_name = 'Key_name';
+        $non_unique = 'Non_unique';
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $key_name = strtolower($key_name);
+                $non_unique = strtolower($non_unique);
+            } else {
+                $key_name = strtoupper($key_name);
+                $non_unique = strtoupper($non_unique);
+            }
+        }
+
+        $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true);
+        $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($indexes)) {
+            return $indexes;
+        }
+
+        $result = array();
+        foreach ($indexes as $index_data) {
+            if (!$index_data[$non_unique]) {
+                if ($index_data[$key_name] !== 'PRIMARY') {
+                    $index = $this->_fixIndexName($index_data[$key_name]);
+                } else {
+                    $index = 'PRIMARY';
+                }
+                if (!empty($index)) {
+                    $result[$index] = true;
+                }
+            }
+        }
+
+        //list FOREIGN KEY constraints...
+        $query = 'SHOW CREATE TABLE '. $db->escape($table);
+        $definition = $db->queryOne($query, 'text', 1);
+        if (!MDB2::isError($definition) && !empty($definition)) {
+            $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims';
+            if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) {
+                foreach ($matches[1] as $constraint) {
+                    $result[$constraint] = true;
+                }
+            }
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_change_key_case($result, $db->options['field_case']);
+        }
+        return array_keys($result);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * create sequence
+     *
+     * @param string    $seq_name name of the sequence to be created
+     * @param string    $start    start value of the sequence; default is 1
+     * @param array     $options  An associative array of table options:
+     *                          array(
+     *                              'comment' => 'Foo',
+     *                              'charset' => 'utf8',
+     *                              'collate' => 'utf8_unicode_ci',
+     *                              'type'    => 'innodb',
+     *                          );
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function createSequence($seq_name, $start = 1, $options = array())
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true);
+        $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true);
+        
+        $options_strings = array();
+
+        if (!empty($options['comment'])) {
+            $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text');
+        }
+
+        if (!empty($options['charset'])) {
+            $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset'];
+            if (!empty($options['collate'])) {
+                $options_strings['charset'].= ' COLLATE '.$options['collate'];
+            }
+        }
+
+        $type = false;
+        if (!empty($options['type'])) {
+            $type = $options['type'];
+        } elseif ($db->options['default_table_type']) {
+            $type = $db->options['default_table_type'];
+        }
+        if ($type) {
+            $options_strings[] = "ENGINE = $type";
+        }
+
+        $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))";
+        if (!empty($options_strings)) {
+            $query .= ' '.implode(' ', $options_strings);
+        }
+        $res = $db->exec($query);
+        if (MDB2::isError($res)) {
+            return $res;
+        }
+
+        if ($start == 1) {
+            return MDB2_OK;
+        }
+
+        $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')';
+        $res = $db->exec($query);
+        if (!MDB2::isError($res)) {
+            return MDB2_OK;
+        }
+
+        // Handle error
+        $result = $db->exec("DROP TABLE $sequence_name");
+        if (MDB2::isError($result)) {
+            return $db->raiseError($result, null, null,
+                'could not drop inconsistent sequence table', __FUNCTION__);
+        }
+
+        return $db->raiseError($res, null, null,
+            'could not create sequence table', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * drop existing sequence
+     *
+     * @param string    $seq_name     name of the sequence to be dropped
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function dropSequence($seq_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true);
+        $result = $db->exec("DROP TABLE $sequence_name");
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ listSequences()
+
+    /**
+     * list all sequences in the current database
+     *
+     * @param string database, the current is default
+     * @return mixed array of sequence names on success, a MDB2 error on failure
+     * @access public
+     */
+    function listSequences($database = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = "SHOW TABLES";
+        if (null !== $database) {
+            $query .= " FROM $database";
+        }
+        $table_names = $db->queryCol($query);
+        if (MDB2::isError($table_names)) {
+            return $table_names;
+        }
+
+        $result = array();
+        foreach ($table_names as $table_name) {
+            if ($sqn = $this->_fixSequenceName($table_name, true)) {
+                $result[] = $sqn;
+            }
+        }
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result);
+        }
+        return $result;
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Native/Common.php b/WEB-INF/lib/pear/MDB2/Driver/Native/Common.php
new file mode 100644 (file)
index 0000000..20e652e
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Common.php 242348 2007-09-09 13:47:36Z quipo $
+//
+
+/**
+ * Base class for the natuve modules that is extended by each MDB2 driver
+ *
+ * To load this module in the MDB2 object:
+ * $mdb->loadModule('Native');
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Native_Common extends MDB2_Module_Common
+{
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Native/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/Native/mysql.php
new file mode 100644 (file)
index 0000000..2d4ffe0
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 215004 2006-06-18 21:59:05Z lsmith $
+//
+
+require_once 'MDB2/Driver/Native/Common.php';
+
+/**
+ * MDB2 MySQL driver for the native module
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common
+{
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Native/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/Native/mysqli.php
new file mode 100644 (file)
index 0000000..22d8f36
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 215004 2006-06-18 21:59:05Z lsmith $
+//
+
+require_once 'MDB2/Driver/Native/Common.php';
+
+/**
+ * MDB2 MySQLi driver for the native module
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Native_mysqli extends MDB2_Driver_Native_Common
+{
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Reverse/Common.php b/WEB-INF/lib/pear/MDB2/Driver/Reverse/Common.php
new file mode 100644 (file)
index 0000000..e31cb5a
--- /dev/null
@@ -0,0 +1,517 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Common.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+/**
+ * @package MDB2
+ * @category Database
+ */
+
+/**
+ * These are constants for the tableInfo-function
+ * they are bitwised or'ed. so if there are more constants to be defined
+ * in the future, adjust MDB2_TABLEINFO_FULL accordingly
+ */
+
+define('MDB2_TABLEINFO_ORDER',      1);
+define('MDB2_TABLEINFO_ORDERTABLE', 2);
+define('MDB2_TABLEINFO_FULL',       3);
+
+/**
+ * Base class for the schema reverse engineering module that is extended by each MDB2 driver
+ *
+ * To load this module in the MDB2 object:
+ * $mdb->loadModule('Reverse');
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_Reverse_Common extends MDB2_Module_Common
+{
+    // {{{ splitTableSchema()
+
+    /**
+     * Split the "[owner|schema].table" notation into an array
+     *
+     * @param string $table [schema and] table name
+     *
+     * @return array array(schema, table)
+     * @access private
+     */
+    function splitTableSchema($table)
+    {
+        $ret = array();
+        if (strpos($table, '.') !== false) {
+            return explode('.', $table);
+        }
+        return array(null, $table);
+    }
+
+    // }}}
+    // {{{ getTableFieldDefinition()
+
+    /**
+     * Get the structure of a field into an array
+     *
+     * @param string    $table     name of table that should be used in method
+     * @param string    $field     name of field that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure.
+     *          The returned array contains an array for each field definition,
+     *          with all or some of these indices, depending on the field data type:
+     *          [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type]
+     * @access public
+     */
+    function getTableFieldDefinition($table, $field)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTableIndexDefinition()
+
+    /**
+     * Get the structure of an index into an array
+     *
+     * @param string    $table      name of table that should be used in method
+     * @param string    $index      name of index that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     *          The returned array has this structure:
+     *          </pre>
+     *          array (
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting] => ascending
+     *                  )
+     *              )
+     *          );
+     *          </pre>
+     * @access public
+     */
+    function getTableIndexDefinition($table, $index)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTableConstraintDefinition()
+
+    /**
+     * Get the structure of an constraints into an array
+     *
+     * @param string    $table      name of table that should be used in method
+     * @param string    $index      name of index that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     *          The returned array has this structure:
+     *          <pre>
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          </pre>
+     * @access public
+     */
+    function getTableConstraintDefinition($table, $index)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getSequenceDefinition()
+
+    /**
+     * Get the structure of a sequence into an array
+     *
+     * @param string    $sequence   name of sequence that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     *          The returned array has this structure:
+     *          <pre>
+     *          array (
+     *              [start] => n
+     *          );
+     *          </pre>
+     * @access public
+     */
+    function getSequenceDefinition($sequence)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $start = $db->currId($sequence);
+        if (MDB2::isError($start)) {
+            return $start;
+        }
+        if ($db->supports('current_id')) {
+            $start++;
+        } else {
+            $db->warnings[] = 'database does not support getting current
+                sequence value, the sequence value was incremented';
+        }
+        $definition = array();
+        if ($start != 1) {
+            $definition = array('start' => $start);
+        }
+        return $definition;
+    }
+
+    // }}}
+    // {{{ getTriggerDefinition()
+
+    /**
+     * Get the structure of a trigger into an array
+     *
+     * EXPERIMENTAL
+     *
+     * WARNING: this function is experimental and may change the returned value 
+     * at any time until labelled as non-experimental
+     *
+     * @param string    $trigger    name of trigger that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     *          The returned array has this structure:
+     *          <pre>
+     *          array (
+     *              [trigger_name]    => 'trigger name',
+     *              [table_name]      => 'table name',
+     *              [trigger_body]    => 'trigger body definition',
+     *              [trigger_type]    => 'BEFORE' | 'AFTER',
+     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
+     *                  //or comma separated list of multiple events, when supported
+     *              [trigger_enabled] => true|false
+     *              [trigger_comment] => 'trigger comment',
+     *          );
+     *          </pre>
+     *          The oci8 driver also returns a [when_clause] index.
+     * @access public
+     */
+    function getTriggerDefinition($trigger)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+            'method not implemented', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * The format of the resulting array depends on which <var>$mode</var>
+     * you select.  The sample output below is based on this query:
+     * <pre>
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * </pre>
+     *
+     * <ul>
+     * <li>
+     *
+     * <kbd>null</kbd> (default)
+     *   <pre>
+     *   [0] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   [1] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldPhone
+     *       [type] => string
+     *       [len] => 20
+     *       [flags] =>
+     *   )
+     *   [2] => Array (
+     *       [table] => tblBar
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>MDB2_TABLEINFO_ORDER</kbd>
+     *
+     *   <p>In addition to the information found in the default output,
+     *   a notation of the number of columns is provided by the
+     *   <samp>num_fields</samp> element while the <samp>order</samp>
+     *   element provides an array with the column names as the keys and
+     *   their location index number (corresponding to the keys in the
+     *   the default output) as the values.</p>
+     *
+     *   <p>If a result set has identical field names, the last one is
+     *   used.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [order] => Array (
+     *       [fldId] => 2
+     *       [fldTrans] => 1
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>MDB2_TABLEINFO_ORDERTABLE</kbd>
+     *
+     *   <p>Similar to <kbd>MDB2_TABLEINFO_ORDER</kbd> but adds more
+     *   dimensions to the array in which the table names are keys and
+     *   the field names are sub-keys.  This is helpful for queries that
+     *   join tables which have identical field names.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [ordertable] => Array (
+     *       [tblFoo] => Array (
+     *           [fldId] => 0
+     *           [fldPhone] => 1
+     *       )
+     *       [tblBar] => Array (
+     *           [fldId] => 2
+     *       )
+     *   )
+     *   </pre>
+     *
+     * </li>
+     * </ul>
+     *
+     * The <samp>flags</samp> element contains a space separated list
+     * of extra information about the field.  This data is inconsistent
+     * between DBMS's due to the way each DBMS works.
+     *   + <samp>primary_key</samp>
+     *   + <samp>unique_key</samp>
+     *   + <samp>multiple_key</samp>
+     *   + <samp>not_null</samp>
+     *
+     * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp>
+     * elements if <var>$result</var> is a table name.  The following DBMS's
+     * provide full information from queries:
+     *   + fbsql
+     *   + mysql
+     *
+     * If the 'portability' option has <samp>MDB2_PORTABILITY_FIX_CASE</samp>
+     * turned on, the names of tables and fields will be lower or upper cased.
+     *
+     * @param object|string  $result  MDB2_result object from a query or a
+     *                                string containing the name of a table.
+     *                                While this also accepts a query result
+     *                                resource identifier, this behavior is
+     *                                deprecated.
+     * @param int  $mode   either unused or one of the tableInfo modes:
+     *                     <kbd>MDB2_TABLEINFO_ORDERTABLE</kbd>,
+     *                     <kbd>MDB2_TABLEINFO_ORDER</kbd> or
+     *                     <kbd>MDB2_TABLEINFO_FULL</kbd> (which does both).
+     *                     These are bitwise, so the first two can be
+     *                     combined using <kbd>|</kbd>.
+     *
+     * @return array  an associative array with the information requested.
+     *                 A MDB2_Error object on failure.
+     *
+     * @see MDB2_Driver_Common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if (!is_string($result)) {
+            return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'method not implemented', __FUNCTION__);
+        }
+
+        $db->loadModule('Manager', null, true);
+        $fields = $db->manager->listTableFields($result);
+        if (MDB2::isError($fields)) {
+            return $fields;
+        }
+
+        $flags = array();
+
+        $idxname_format = $db->getOption('idxname_format');
+        $db->setOption('idxname_format', '%s');
+
+        $indexes = $db->manager->listTableIndexes($result);
+        if (MDB2::isError($indexes)) {
+            $db->setOption('idxname_format', $idxname_format);
+            return $indexes;
+        }
+
+        foreach ($indexes as $index) {
+            $definition = $this->getTableIndexDefinition($result, $index);
+            if (MDB2::isError($definition)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $definition;
+            }
+            if (count($definition['fields']) > 1) {
+                foreach ($definition['fields'] as $field => $sort) {
+                    $flags[$field] = 'multiple_key';
+                }
+            }
+        }
+
+        $constraints = $db->manager->listTableConstraints($result);
+        if (MDB2::isError($constraints)) {
+            return $constraints;
+        }
+
+        foreach ($constraints as $constraint) {
+            $definition = $this->getTableConstraintDefinition($result, $constraint);
+            if (MDB2::isError($definition)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $definition;
+            }
+            $flag = !empty($definition['primary'])
+                ? 'primary_key' : (!empty($definition['unique'])
+                    ? 'unique_key' : false);
+            if ($flag) {
+                foreach ($definition['fields'] as $field => $sort) {
+                    if (empty($flags[$field]) || $flags[$field] != 'primary_key') {
+                        $flags[$field] = $flag;
+                    }
+                }
+            }
+        }
+
+        $res = array();
+
+        if ($mode) {
+            $res['num_fields'] = count($fields);
+        }
+
+        foreach ($fields as $i => $field) {
+            $definition = $this->getTableFieldDefinition($result, $field);
+            if (MDB2::isError($definition)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $definition;
+            }
+            $res[$i] = $definition[0];
+            $res[$i]['name'] = $field;
+            $res[$i]['table'] = $result;
+            $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype']));
+            // 'primary_key', 'unique_key', 'multiple_key'
+            $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field];
+            // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]'
+            if (!empty($res[$i]['notnull'])) {
+                $res[$i]['flags'].= ' not_null';
+            }
+            if (!empty($res[$i]['unsigned'])) {
+                $res[$i]['flags'].= ' unsigned';
+            }
+            if (!empty($res[$i]['auto_increment'])) {
+                $res[$i]['flags'].= ' autoincrement';
+            }
+            if (!empty($res[$i]['default'])) {
+                $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']);
+            }
+
+            if ($mode & MDB2_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        $db->setOption('idxname_format', $idxname_format);
+        return $res;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Reverse/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/Reverse/mysql.php
new file mode 100644 (file)
index 0000000..3aea5a7
--- /dev/null
@@ -0,0 +1,546 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Reverse/Common.php';
+
+/**
+ * MDB2 MySQL driver for the schema reverse engineering module
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ * @author  Lorenzo Alberton <l.alberton@quipo.it>
+ */
+class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common
+{
+    // {{{ getTableFieldDefinition()
+
+    /**
+     * Get the structure of a field into an array
+     *
+     * @param string $table_name name of table that should be used in method
+     * @param string $field_name name of field that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableFieldDefinition($table_name, $field_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $result = $db->loadModule('Datatype', null, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name);
+        $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($columns)) {
+            return $columns;
+        }
+        foreach ($columns as $column) {
+            $column = array_change_key_case($column, CASE_LOWER);
+            $column['name'] = $column['field'];
+            unset($column['field']);
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $column['name'] = strtolower($column['name']);
+                } else {
+                    $column['name'] = strtoupper($column['name']);
+                }
+            } else {
+                $column = array_change_key_case($column, $db->options['field_case']);
+            }
+            if ($field_name == $column['name']) {
+                $mapped_datatype = $db->datatype->mapNativeDatatype($column);
+                if (MDB2::isError($mapped_datatype)) {
+                    return $mapped_datatype;
+                }
+                list($types, $length, $unsigned, $fixed) = $mapped_datatype;
+                $notnull = false;
+                if (empty($column['null']) || $column['null'] !== 'YES') {
+                    $notnull = true;
+                }
+                $default = false;
+                if (array_key_exists('default', $column)) {
+                    $default = $column['default'];
+                    if ((null === $default) && $notnull) {
+                        $default = '';
+                    }
+                }
+                $definition[0] = array(
+                    'notnull' => $notnull,
+                    'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type'])
+                );
+                $autoincrement = false;
+                if (!empty($column['extra'])) {
+                    if ($column['extra'] == 'auto_increment') {
+                        $autoincrement = true;
+                    } else {
+                        $definition[0]['extra'] = $column['extra'];
+                    }
+                }
+                $collate = null;
+                if (!empty($column['collation'])) {
+                    $collate = $column['collation'];
+                    $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate);
+                }
+
+                if (null !== $length) {
+                    $definition[0]['length'] = $length;
+                }
+                if (null !== $unsigned) {
+                    $definition[0]['unsigned'] = $unsigned;
+                }
+                if (null !== $fixed) {
+                    $definition[0]['fixed'] = $fixed;
+                }
+                if ($default !== false) {
+                    $definition[0]['default'] = $default;
+                }
+                if ($autoincrement !== false) {
+                    $definition[0]['autoincrement'] = $autoincrement;
+                }
+                if (null !== $collate) {
+                    $definition[0]['collate'] = $collate;
+                    $definition[0]['charset'] = $charset;
+                }
+                foreach ($types as $key => $type) {
+                    $definition[$key] = $definition[0];
+                    if ($type == 'clob' || $type == 'blob') {
+                        unset($definition[$key]['default']);
+                    } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) {
+                        $definition[$key]['default'] = '0000-00-00 00:00:00';
+                    }
+                    $definition[$key]['type'] = $type;
+                    $definition[$key]['mdb2type'] = $type;
+                }
+                return $definition;
+            }
+        }
+
+        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+            'it was not specified an existing table column', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTableIndexDefinition()
+
+    /**
+     * Get the structure of an index into an array
+     *
+     * @param string $table_name name of table that should be used in method
+     * @param string $index_name name of index that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableIndexDefinition($table_name, $index_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
+        $index_name_mdb2 = $db->getIndexName($index_name);
+        $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2)));
+        if (!MDB2::isError($result) && (null !== $result)) {
+            // apply 'idxname_format' only if the query succeeded, otherwise
+            // fallback to the given $index_name, without transformation
+            $index_name = $index_name_mdb2;
+        }
+        $result = $db->query(sprintf($query, $db->quote($index_name)));
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $colpos = 1;
+        $definition = array();
+        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
+            $row = array_change_key_case($row, CASE_LOWER);
+            $key_name = $row['key_name'];
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $key_name = strtolower($key_name);
+                } else {
+                    $key_name = strtoupper($key_name);
+                }
+            }
+            if ($index_name == $key_name) {
+                if (!$row['non_unique']) {
+                    return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                        $index_name . ' is not an existing table index', __FUNCTION__);
+                }
+                $column_name = $row['column_name'];
+                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                    if ($db->options['field_case'] == CASE_LOWER) {
+                        $column_name = strtolower($column_name);
+                    } else {
+                        $column_name = strtoupper($column_name);
+                    }
+                }
+                $definition['fields'][$column_name] = array(
+                    'position' => $colpos++
+                );
+                if (!empty($row['collation'])) {
+                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
+                        ? 'ascending' : 'descending');
+                }
+            }
+        }
+        $result->free();
+        if (empty($definition['fields'])) {
+            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                $index_name . ' is not an existing table index', __FUNCTION__);
+        }
+        return $definition;
+    }
+
+    // }}}
+    // {{{ getTableConstraintDefinition()
+
+    /**
+     * Get the structure of a constraint into an array
+     *
+     * @param string $table_name      name of table that should be used in method
+     * @param string $constraint_name name of constraint that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableConstraintDefinition($table_name, $constraint_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+        $constraint_name_original = $constraint_name;
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
+        if (strtolower($constraint_name) != 'primary') {
+            $constraint_name_mdb2 = $db->getIndexName($constraint_name);
+            $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2)));
+            if (!MDB2::isError($result) && (null !== $result)) {
+                // apply 'idxname_format' only if the query succeeded, otherwise
+                // fallback to the given $index_name, without transformation
+                $constraint_name = $constraint_name_mdb2;
+            }
+        }
+        $result = $db->query(sprintf($query, $db->quote($constraint_name)));
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $colpos = 1;
+        //default values, eventually overridden
+        $definition = array(
+            'primary' => false,
+            'unique'  => false,
+            'foreign' => false,
+            'check'   => false,
+            'fields'  => array(),
+            'references' => array(
+                'table'  => '',
+                'fields' => array(),
+            ),
+            'onupdate'  => '',
+            'ondelete'  => '',
+            'match'     => '',
+            'deferrable'        => false,
+            'initiallydeferred' => false,
+        );
+        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
+            $row = array_change_key_case($row, CASE_LOWER);
+            $key_name = $row['key_name'];
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $key_name = strtolower($key_name);
+                } else {
+                    $key_name = strtoupper($key_name);
+                }
+            }
+            if ($constraint_name == $key_name) {
+                if ($row['non_unique']) {
+                    //FOREIGN KEY?
+                    return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
+                }
+                if ($row['key_name'] == 'PRIMARY') {
+                    $definition['primary'] = true;
+                } elseif (!$row['non_unique']) {
+                    $definition['unique'] = true;
+                }
+                $column_name = $row['column_name'];
+                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                    if ($db->options['field_case'] == CASE_LOWER) {
+                        $column_name = strtolower($column_name);
+                    } else {
+                        $column_name = strtoupper($column_name);
+                    }
+                }
+                $definition['fields'][$column_name] = array(
+                    'position' => $colpos++
+                );
+                if (!empty($row['collation'])) {
+                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
+                        ? 'ascending' : 'descending');
+                }
+            }
+        }
+        $result->free();
+        if (empty($definition['fields'])) {
+            return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
+        }
+        return $definition;
+    }
+
+    // }}}
+    // {{{ _getTableFKConstraintDefinition()
+    
+    /**
+     * Get the FK definition from the CREATE TABLE statement
+     *
+     * @param string $table           table name
+     * @param string $constraint_name constraint name
+     * @param array  $definition      default values for constraint definition
+     *
+     * @return array|PEAR_Error
+     * @access private
+     */
+    function _getTableFKConstraintDefinition($table, $constraint_name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        //Use INFORMATION_SCHEMA instead?
+        //SELECT *
+        //  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
+        // WHERE CONSTRAINT_SCHEMA = '$dbname'
+        //   AND TABLE_NAME = '$table'
+        //   AND CONSTRAINT_NAME = '$constraint_name';
+        $query = 'SHOW CREATE TABLE '. $db->escape($table);
+        $constraint = $db->queryOne($query, 'text', 1);
+        if (!MDB2::isError($constraint) && !empty($constraint)) {
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $constraint = strtolower($constraint);
+                } else {
+                    $constraint = strtoupper($constraint);
+                }
+            }
+            $constraint_name_original = $constraint_name;
+            $constraint_name = $db->getIndexName($constraint_name);
+            $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
+            if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
+                //fallback to original constraint name
+                $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
+            }
+            if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
+                $definition['foreign'] = true;
+                $column_names = explode(',', $matches[1]);
+                $referenced_cols = explode(',', $matches[3]);
+                $definition['references'] = array(
+                    'table'  => $matches[2],
+                    'fields' => array(),
+                );
+                $colpos = 1;
+                foreach ($column_names as $column_name) {
+                    $definition['fields'][trim($column_name)] = array(
+                        'position' => $colpos++
+                    );
+                }
+                $colpos = 1;
+                foreach ($referenced_cols as $column_name) {
+                    $definition['references']['fields'][trim($column_name)] = array(
+                        'position' => $colpos++
+                    );
+                }
+                $definition['ondelete'] = empty($matches[4]) ? 'RESTRICT' : strtoupper($matches[4]);
+                $definition['onupdate'] = empty($matches[5]) ? 'RESTRICT' : strtoupper($matches[5]);
+                $definition['match']    = 'SIMPLE';
+                return $definition;
+            }
+        }
+        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                $constraint_name . ' is not an existing table constraint', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTriggerDefinition()
+
+    /**
+     * Get the structure of a trigger into an array
+     *
+     * EXPERIMENTAL
+     *
+     * WARNING: this function is experimental and may change the returned value
+     * at any time until labelled as non-experimental
+     *
+     * @param string    $trigger    name of trigger that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTriggerDefinition($trigger)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SELECT trigger_name,
+                         event_object_table AS table_name,
+                         action_statement AS trigger_body,
+                         action_timing AS trigger_type,
+                         event_manipulation AS trigger_event
+                    FROM information_schema.triggers
+                   WHERE trigger_name = '. $db->quote($trigger, 'text');
+        $types = array(
+            'trigger_name'    => 'text',
+            'table_name'      => 'text',
+            'trigger_body'    => 'text',
+            'trigger_type'    => 'text',
+            'trigger_event'   => 'text',
+        );
+        $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($def)) {
+            return $def;
+        }
+        $def['trigger_comment'] = '';
+        $def['trigger_enabled'] = true;
+        return $def;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  MDB2_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A MDB2_Error object on failure.
+     *
+     * @see MDB2_Driver_Common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+           return parent::tableInfo($result, $mode);
+        }
+
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
+        if (!is_resource($resource)) {
+            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'Could not generate result resource', __FUNCTION__);
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $case_func = 'strtolower';
+            } else {
+                $case_func = 'strtoupper';
+            }
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mysql_num_fields($resource);
+        $res   = array();
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        $db->loadModule('Datatype', null, true);
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table'  => $case_func(@mysql_field_table($resource, $i)),
+                'name'   => $case_func(@mysql_field_name($resource, $i)),
+                'type'   => @mysql_field_type($resource, $i),
+                'length' => @mysql_field_len($resource, $i),
+                'flags'  => @mysql_field_flags($resource, $i),
+            );
+            if ($res[$i]['type'] == 'string') {
+                $res[$i]['type'] = 'char';
+            } elseif ($res[$i]['type'] == 'unknown') {
+                $res[$i]['type'] = 'decimal';
+            }
+            $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
+            if (MDB2::isError($mdb2type_info)) {
+               return $mdb2type_info;
+            }
+            $res[$i]['mdb2type'] = $mdb2type_info[0][0];
+            if ($mode & MDB2_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        return $res;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/Reverse/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/Reverse/mysqli.php
new file mode 100644 (file)
index 0000000..abd5882
--- /dev/null
@@ -0,0 +1,610 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 327310 2012-08-27 15:16:18Z danielc $
+//
+
+require_once 'MDB2/Driver/Reverse/Common.php';
+
+/**
+ * MDB2 MySQLi driver for the schema reverse engineering module
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ * @author  Lorenzo Alberton <l.alberton@quipo.it>
+ */
+class MDB2_Driver_Reverse_mysqli extends MDB2_Driver_Reverse_Common
+{
+    /**
+     * Array for converting MYSQLI_*_FLAG constants to text values
+     * @var    array
+     * @access public
+     */
+    var $flags = array(
+        MYSQLI_NOT_NULL_FLAG        => 'not_null',
+        MYSQLI_PRI_KEY_FLAG         => 'primary_key',
+        MYSQLI_UNIQUE_KEY_FLAG      => 'unique_key',
+        MYSQLI_MULTIPLE_KEY_FLAG    => 'multiple_key',
+        MYSQLI_BLOB_FLAG            => 'blob',
+        MYSQLI_UNSIGNED_FLAG        => 'unsigned',
+        MYSQLI_ZEROFILL_FLAG        => 'zerofill',
+        MYSQLI_AUTO_INCREMENT_FLAG  => 'auto_increment',
+        MYSQLI_TIMESTAMP_FLAG       => 'timestamp',
+        MYSQLI_SET_FLAG             => 'set',
+        // MYSQLI_NUM_FLAG             => 'numeric',  // unnecessary
+        // MYSQLI_PART_KEY_FLAG        => 'multiple_key',  // duplicatvie
+        MYSQLI_GROUP_FLAG           => 'group_by'
+    );
+
+    /**
+     * Array for converting MYSQLI_TYPE_* constants to text values
+     * @var    array
+     * @access public
+     */
+    var $types = array(
+        MYSQLI_TYPE_DECIMAL     => 'decimal',
+        246                     => 'decimal',
+        MYSQLI_TYPE_TINY        => 'tinyint',
+        MYSQLI_TYPE_SHORT       => 'int',
+        MYSQLI_TYPE_LONG        => 'int',
+        MYSQLI_TYPE_FLOAT       => 'float',
+        MYSQLI_TYPE_DOUBLE      => 'double',
+        // MYSQLI_TYPE_NULL        => 'DEFAULT NULL',  // let flags handle it
+        MYSQLI_TYPE_TIMESTAMP   => 'timestamp',
+        MYSQLI_TYPE_LONGLONG    => 'bigint',
+        MYSQLI_TYPE_INT24       => 'mediumint',
+        MYSQLI_TYPE_DATE        => 'date',
+        MYSQLI_TYPE_TIME        => 'time',
+        MYSQLI_TYPE_DATETIME    => 'datetime',
+        MYSQLI_TYPE_YEAR        => 'year',
+        MYSQLI_TYPE_NEWDATE     => 'date',
+        MYSQLI_TYPE_ENUM        => 'enum',
+        MYSQLI_TYPE_SET         => 'set',
+        MYSQLI_TYPE_TINY_BLOB   => 'tinyblob',
+        MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
+        MYSQLI_TYPE_LONG_BLOB   => 'longblob',
+        MYSQLI_TYPE_BLOB        => 'blob',
+        MYSQLI_TYPE_VAR_STRING  => 'varchar',
+        MYSQLI_TYPE_STRING      => 'char',
+        MYSQLI_TYPE_GEOMETRY    => 'geometry',
+    );
+
+    // {{{ getTableFieldDefinition()
+
+    /**
+     * Get the structure of a field into an array
+     *
+     * @param string $table_name name of table that should be used in method
+     * @param string $field_name name of field that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableFieldDefinition($table_name, $field_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $result = $db->loadModule('Datatype', null, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name);
+        $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($columns)) {
+            return $columns;
+        }
+        foreach ($columns as $column) {
+            $column = array_change_key_case($column, CASE_LOWER);
+            $column['name'] = $column['field'];
+            unset($column['field']);
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $column['name'] = strtolower($column['name']);
+                } else {
+                    $column['name'] = strtoupper($column['name']);
+                }
+            } else {
+                $column = array_change_key_case($column, $db->options['field_case']);
+            }
+            if ($field_name == $column['name']) {
+                $mapped_datatype = $db->datatype->mapNativeDatatype($column);
+                if (MDB2::isError($mapped_datatype)) {
+                    return $mapped_datatype;
+                }
+                list($types, $length, $unsigned, $fixed) = $mapped_datatype;
+                $notnull = false;
+                if (empty($column['null']) || $column['null'] !== 'YES') {
+                    $notnull = true;
+                }
+                $default = false;
+                if (array_key_exists('default', $column)) {
+                    $default = $column['default'];
+                    if ((null === $default) && $notnull) {
+                        $default = '';
+                    }
+                }
+                $definition[0] = array(
+                    'notnull' => $notnull,
+                    'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type'])
+                );
+                $autoincrement = false;
+                if (!empty($column['extra'])) {
+                    if ($column['extra'] == 'auto_increment') {
+                        $autoincrement = true;
+                    } else {
+                        $definition[0]['extra'] = $column['extra'];
+                    }
+                }
+                $collate = null;
+                if (!empty($column['collation'])) {
+                    $collate = $column['collation'];
+                    $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate);
+                }
+
+                if (null !== $length) {
+                    $definition[0]['length'] = $length;
+                }
+                if (null !== $unsigned) {
+                    $definition[0]['unsigned'] = $unsigned;
+                }
+                if (null !== $fixed) {
+                    $definition[0]['fixed'] = $fixed;
+                }
+                if ($default !== false) {
+                    $definition[0]['default'] = $default;
+                }
+                if ($autoincrement !== false) {
+                    $definition[0]['autoincrement'] = $autoincrement;
+                }
+                if (null !== $collate) {
+                    $definition[0]['collate'] = $collate;
+                    $definition[0]['charset'] = $charset;
+                }
+                foreach ($types as $key => $type) {
+                    $definition[$key] = $definition[0];
+                    if ($type == 'clob' || $type == 'blob') {
+                        unset($definition[$key]['default']);
+                    } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) {
+                        $definition[$key]['default'] = '0000-00-00 00:00:00';
+                    }
+                    $definition[$key]['type'] = $type;
+                    $definition[$key]['mdb2type'] = $type;
+                }
+                return $definition;
+            }
+        }
+
+        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+            'it was not specified an existing table column', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTableIndexDefinition()
+
+    /**
+     * Get the structure of an index into an array
+     *
+     * @param string $table_name name of table that should be used in method
+     * @param string $index_name name of index that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableIndexDefinition($table_name, $index_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
+        $index_name_mdb2 = $db->getIndexName($index_name);
+        $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2)));
+        if (!MDB2::isError($result) && (null !== $result)) {
+            // apply 'idxname_format' only if the query succeeded, otherwise
+            // fallback to the given $index_name, without transformation
+            $index_name = $index_name_mdb2;
+        }
+        $result = $db->query(sprintf($query, $db->quote($index_name)));
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $colpos = 1;
+        $definition = array();
+        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
+            $row = array_change_key_case($row, CASE_LOWER);
+            $key_name = $row['key_name'];
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $key_name = strtolower($key_name);
+                } else {
+                    $key_name = strtoupper($key_name);
+                }
+            }
+            if ($index_name == $key_name) {
+                if (!$row['non_unique']) {
+                    return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                        $index_name . ' is not an existing table index', __FUNCTION__);
+                }
+                $column_name = $row['column_name'];
+                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                    if ($db->options['field_case'] == CASE_LOWER) {
+                        $column_name = strtolower($column_name);
+                    } else {
+                        $column_name = strtoupper($column_name);
+                    }
+                }
+                $definition['fields'][$column_name] = array(
+                    'position' => $colpos++
+                );
+                if (!empty($row['collation'])) {
+                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
+                        ? 'ascending' : 'descending');
+                }
+            }
+        }
+        $result->free();
+        if (empty($definition['fields'])) {
+            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                $index_name . ' is not an existing table index', __FUNCTION__);
+        }
+        return $definition;
+    }
+
+    // }}}
+    // {{{ getTableConstraintDefinition()
+
+    /**
+     * Get the structure of a constraint into an array
+     *
+     * @param string $table_name      name of table that should be used in method
+     * @param string $constraint_name name of constraint that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTableConstraintDefinition($table_name, $constraint_name)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        list($schema, $table) = $this->splitTableSchema($table_name);
+        $constraint_name_original = $constraint_name;
+
+        $table = $db->quoteIdentifier($table, true);
+        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
+        if (strtolower($constraint_name) != 'primary') {
+            $constraint_name_mdb2 = $db->getIndexName($constraint_name);
+            $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2)));
+            if (!MDB2::isError($result) && (null !== $result)) {
+                // apply 'idxname_format' only if the query succeeded, otherwise
+                // fallback to the given $index_name, without transformation
+                $constraint_name = $constraint_name_mdb2;
+            }
+        }
+        $result = $db->query(sprintf($query, $db->quote($constraint_name)));
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $colpos = 1;
+        //default values, eventually overridden
+        $definition = array(
+            'primary' => false,
+            'unique'  => false,
+            'foreign' => false,
+            'check'   => false,
+            'fields'  => array(),
+            'references' => array(
+                'table'  => '',
+                'fields' => array(),
+            ),
+            'onupdate'  => '',
+            'ondelete'  => '',
+            'match'     => '',
+            'deferrable'        => false,
+            'initiallydeferred' => false,
+        );
+        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
+            $row = array_change_key_case($row, CASE_LOWER);
+            $key_name = $row['key_name'];
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $key_name = strtolower($key_name);
+                } else {
+                    $key_name = strtoupper($key_name);
+                }
+            }
+            if ($constraint_name == $key_name) {
+                if ($row['non_unique']) {
+                    //FOREIGN KEY?
+                    return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
+                }
+                if ($row['key_name'] == 'PRIMARY') {
+                    $definition['primary'] = true;
+                } elseif (!$row['non_unique']) {
+                    $definition['unique'] = true;
+                }
+                $column_name = $row['column_name'];
+                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                    if ($db->options['field_case'] == CASE_LOWER) {
+                        $column_name = strtolower($column_name);
+                    } else {
+                        $column_name = strtoupper($column_name);
+                    }
+                }
+                $definition['fields'][$column_name] = array(
+                    'position' => $colpos++
+                );
+                if (!empty($row['collation'])) {
+                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
+                        ? 'ascending' : 'descending');
+                }
+            }
+        }
+        $result->free();
+        if (empty($definition['fields'])) {
+            return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
+        }
+        return $definition;
+    }
+
+    // }}}
+    // {{{ _getTableFKConstraintDefinition()
+
+    /**
+     * Get the FK definition from the CREATE TABLE statement
+     *
+     * @param string $table           table name
+     * @param string $constraint_name constraint name
+     * @param array  $definition      default values for constraint definition
+     *
+     * @return array|PEAR_Error
+     * @access private
+     */
+    function _getTableFKConstraintDefinition($table, $constraint_name, $definition)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        //Use INFORMATION_SCHEMA instead?
+        //SELECT *
+        //  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
+        // WHERE CONSTRAINT_SCHEMA = '$dbname'
+        //   AND TABLE_NAME = '$table'
+        //   AND CONSTRAINT_NAME = '$constraint_name';
+        $query = 'SHOW CREATE TABLE '. $db->escape($table);
+        $constraint = $db->queryOne($query, 'text', 1);
+        if (!MDB2::isError($constraint) && !empty($constraint)) {
+            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+                if ($db->options['field_case'] == CASE_LOWER) {
+                    $constraint = strtolower($constraint);
+                } else {
+                    $constraint = strtoupper($constraint);
+                }
+            }
+            $constraint_name_original = $constraint_name;
+            $constraint_name = $db->getIndexName($constraint_name);
+            $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
+            if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
+                //fallback to original constraint name
+                $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
+            }
+            if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
+                $definition['foreign'] = true;
+                $column_names = explode(',', $matches[1]);
+                $referenced_cols = explode(',', $matches[3]);
+                $definition['references'] = array(
+                    'table'  => $matches[2],
+                    'fields' => array(),
+                );
+                $colpos = 1;
+                foreach ($column_names as $column_name) {
+                    $definition['fields'][trim($column_name)] = array(
+                        'position' => $colpos++
+                    );
+                }
+                $colpos = 1;
+                foreach ($referenced_cols as $column_name) {
+                    $definition['references']['fields'][trim($column_name)] = array(
+                        'position' => $colpos++
+                    );
+                }
+                $definition['ondelete'] = empty($matches[4]) ? 'RESTRICT' : strtoupper($matches[4]);
+                $definition['onupdate'] = empty($matches[5]) ? 'RESTRICT' : strtoupper($matches[5]);
+                $definition['match']    = 'SIMPLE';
+                return $definition;
+            }
+        }
+        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                $constraint_name . ' is not an existing table constraint', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ getTriggerDefinition()
+
+    /**
+     * Get the structure of a trigger into an array
+     *
+     * EXPERIMENTAL
+     *
+     * WARNING: this function is experimental and may change the returned value
+     * at any time until labelled as non-experimental
+     *
+     * @param string    $trigger    name of trigger that should be used in method
+     * @return mixed data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function getTriggerDefinition($trigger)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $query = 'SELECT trigger_name,
+                         event_object_table AS table_name,
+                         action_statement AS trigger_body,
+                         action_timing AS trigger_type,
+                         event_manipulation AS trigger_event
+                    FROM information_schema.triggers
+                   WHERE trigger_name = '. $db->quote($trigger, 'text');
+        $types = array(
+            'trigger_name'    => 'text',
+            'table_name'      => 'text',
+            'trigger_body'    => 'text',
+            'trigger_type'    => 'text',
+            'trigger_event'   => 'text',
+        );
+        $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
+        if (MDB2::isError($def)) {
+            return $def;
+        }
+        $def['trigger_comment'] = '';
+        $def['trigger_enabled'] = true;
+        return $def;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  MDB2_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A MDB2_Error object on failure.
+     *
+     * @see MDB2_Driver_Common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+           return parent::tableInfo($result, $mode);
+        }
+
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
+        if (!is_object($resource)) {
+            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'Could not generate result resource', __FUNCTION__);
+        }
+
+        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            if ($db->options['field_case'] == CASE_LOWER) {
+                $case_func = 'strtolower';
+            } else {
+                $case_func = 'strtoupper';
+            }
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mysqli_num_fields($resource);
+        $res = array();
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        $db->loadModule('Datatype', null, true);
+        for ($i = 0; $i < $count; $i++) {
+            $tmp = @mysqli_fetch_field($resource);
+
+            $flags = '';
+            foreach ($this->flags as $const => $means) {
+                if ($tmp->flags & $const) {
+                    $flags.= $means . ' ';
+                }
+            }
+            if ($tmp->def) {
+                $flags.= 'default_' . rawurlencode($tmp->def);
+            }
+            $flags = trim($flags);
+
+            $res[$i] = array(
+                'table'  => $case_func($tmp->table),
+                'name'   => $case_func($tmp->name),
+                'type'   => isset($this->types[$tmp->type])
+                    ? $this->types[$tmp->type] : 'unknown',
+                // http://bugs.php.net/?id=36579
+                'length' => $tmp->length,
+                'flags'  => $flags,
+            );
+            $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
+            if (MDB2::isError($mdb2type_info)) {
+               return $mdb2type_info;
+            }
+            $res[$i]['mdb2type'] = $mdb2type_info[0][0];
+            if ($mode & MDB2_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        return $res;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/mysql.php b/WEB-INF/lib/pear/MDB2/Driver/mysql.php
new file mode 100644 (file)
index 0000000..2dc51b1
--- /dev/null
@@ -0,0 +1,1729 @@
+<?php
+// vim: set et ts=4 sw=4 fdm=marker:
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php 327320 2012-08-27 15:52:50Z danielc $
+//
+
+/**
+ * MDB2 MySQL driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_mysql extends MDB2_Driver_Common
+{
+    // {{{ properties
+
+    public $string_quoting = array(
+        'start' => "'",
+        'end' => "'",
+        'escape' => '\\',
+        'escape_pattern' => '\\',
+    );
+
+    public $identifier_quoting = array(
+        'start' => '`',
+        'end' => '`',
+        'escape' => '`',
+    );
+
+    public $sql_comments = array(
+        array('start' => '-- ', 'end' => "\n", 'escape' => false),
+        array('start' => '#', 'end' => "\n", 'escape' => false),
+        array('start' => '/*', 'end' => '*/', 'escape' => false),
+    );
+
+    protected $server_capabilities_checked = false;
+
+    protected $start_transaction = false;
+
+    public $varchar_max_length = 255;
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+        parent::__construct();
+
+        $this->phptype = 'mysql';
+        $this->dbsyntax = 'mysql';
+
+        $this->supported['sequences'] = 'emulated';
+        $this->supported['indexes'] = true;
+        $this->supported['affected_rows'] = true;
+        $this->supported['transactions'] = false;
+        $this->supported['savepoints'] = false;
+        $this->supported['summary_functions'] = true;
+        $this->supported['order_by_text'] = true;
+        $this->supported['current_id'] = 'emulated';
+        $this->supported['limit_queries'] = true;
+        $this->supported['LOBs'] = true;
+        $this->supported['replace'] = true;
+        $this->supported['sub_selects'] = 'emulated';
+        $this->supported['triggers'] = false;
+        $this->supported['auto_increment'] = true;
+        $this->supported['primary_key'] = true;
+        $this->supported['result_introspection'] = true;
+        $this->supported['prepared_statements'] = 'emulated';
+        $this->supported['identifier_quoting'] = true;
+        $this->supported['pattern_escaping'] = true;
+        $this->supported['new_link'] = true;
+
+        $this->options['DBA_username'] = false;
+        $this->options['DBA_password'] = false;
+        $this->options['default_table_type'] = '';
+        $this->options['max_identifiers_length'] = 64;
+
+        $this->_reCheckSupportedOptions();
+    }
+
+    // }}}
+    // {{{ _reCheckSupportedOptions()
+
+    /**
+     * If the user changes certain options, other capabilities may depend
+     * on the new settings, so we need to check them (again).
+     *
+     * @access private
+     */
+    function _reCheckSupportedOptions()
+    {
+        $this->supported['transactions'] = $this->options['use_transactions'];
+        $this->supported['savepoints']   = $this->options['use_transactions'];
+        if ($this->options['default_table_type']) {
+            switch (strtoupper($this->options['default_table_type'])) {
+            case 'BLACKHOLE':
+            case 'MEMORY':
+            case 'ARCHIVE':
+            case 'CSV':
+            case 'HEAP':
+            case 'ISAM':
+            case 'MERGE':
+            case 'MRG_ISAM':
+            case 'ISAM':
+            case 'MRG_MYISAM':
+            case 'MYISAM':
+                $this->supported['savepoints']   = false;
+                $this->supported['transactions'] = false;
+                $this->warnings[] = $this->options['default_table_type'] .
+                    ' is not a supported default table type';
+                break;
+            }
+        }
+    }
+
+    // }}}
+    // {{{ function setOption($option, $value)
+
+    /**
+     * set the option for the db class
+     *
+     * @param   string  option name
+     * @param   mixed   value for the option
+     *
+     * @return  mixed   MDB2_OK or MDB2 Error Object
+     *
+     * @access  public
+     */
+    function setOption($option, $value)
+    {
+        $res = parent::setOption($option, $value);
+        $this->_reCheckSupportedOptions();
+    }
+
+    // }}}
+    // {{{ errorInfo()
+
+    /**
+     * This method is used to collect information about an error
+     *
+     * @param integer $error
+     * @return array
+     * @access public
+     */
+    function errorInfo($error = null)
+    {
+        if ($this->connection) {
+            $native_code = @mysql_errno($this->connection);
+            $native_msg  = @mysql_error($this->connection);
+        } else {
+            $native_code = @mysql_errno();
+            $native_msg  = @mysql_error();
+        }
+        if (is_null($error)) {
+            static $ecode_map;
+            if (empty($ecode_map)) {
+                $ecode_map = array(
+                    1000 => MDB2_ERROR_INVALID, //hashchk
+                    1001 => MDB2_ERROR_INVALID, //isamchk
+                    1004 => MDB2_ERROR_CANNOT_CREATE,
+                    1005 => MDB2_ERROR_CANNOT_CREATE,
+                    1006 => MDB2_ERROR_CANNOT_CREATE,
+                    1007 => MDB2_ERROR_ALREADY_EXISTS,
+                    1008 => MDB2_ERROR_CANNOT_DROP,
+                    1009 => MDB2_ERROR_CANNOT_DROP,
+                    1010 => MDB2_ERROR_CANNOT_DROP,
+                    1011 => MDB2_ERROR_CANNOT_DELETE,
+                    1022 => MDB2_ERROR_ALREADY_EXISTS,
+                    1029 => MDB2_ERROR_NOT_FOUND,
+                    1032 => MDB2_ERROR_NOT_FOUND,
+                    1044 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1045 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1046 => MDB2_ERROR_NODBSELECTED,
+                    1048 => MDB2_ERROR_CONSTRAINT,
+                    1049 => MDB2_ERROR_NOSUCHDB,
+                    1050 => MDB2_ERROR_ALREADY_EXISTS,
+                    1051 => MDB2_ERROR_NOSUCHTABLE,
+                    1054 => MDB2_ERROR_NOSUCHFIELD,
+                    1060 => MDB2_ERROR_ALREADY_EXISTS,
+                    1061 => MDB2_ERROR_ALREADY_EXISTS,
+                    1062 => MDB2_ERROR_ALREADY_EXISTS,
+                    1064 => MDB2_ERROR_SYNTAX,
+                    1067 => MDB2_ERROR_INVALID,
+                    1072 => MDB2_ERROR_NOT_FOUND,
+                    1086 => MDB2_ERROR_ALREADY_EXISTS,
+                    1091 => MDB2_ERROR_NOT_FOUND,
+                    1100 => MDB2_ERROR_NOT_LOCKED,
+                    1109 => MDB2_ERROR_NOT_FOUND,
+                    1125 => MDB2_ERROR_ALREADY_EXISTS,
+                    1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
+                    1138 => MDB2_ERROR_INVALID,
+                    1142 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1143 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1146 => MDB2_ERROR_NOSUCHTABLE,
+                    1149 => MDB2_ERROR_SYNTAX,
+                    1169 => MDB2_ERROR_CONSTRAINT,
+                    1176 => MDB2_ERROR_NOT_FOUND,
+                    1177 => MDB2_ERROR_NOSUCHTABLE,
+                    1213 => MDB2_ERROR_DEADLOCK,
+                    1216 => MDB2_ERROR_CONSTRAINT,
+                    1217 => MDB2_ERROR_CONSTRAINT,
+                    1227 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1235 => MDB2_ERROR_CANNOT_CREATE,
+                    1299 => MDB2_ERROR_INVALID_DATE,
+                    1300 => MDB2_ERROR_INVALID,
+                    1304 => MDB2_ERROR_ALREADY_EXISTS,
+                    1305 => MDB2_ERROR_NOT_FOUND,
+                    1306 => MDB2_ERROR_CANNOT_DROP,
+                    1307 => MDB2_ERROR_CANNOT_CREATE,
+                    1334 => MDB2_ERROR_CANNOT_ALTER,
+                    1339 => MDB2_ERROR_NOT_FOUND,
+                    1356 => MDB2_ERROR_INVALID,
+                    1359 => MDB2_ERROR_ALREADY_EXISTS,
+                    1360 => MDB2_ERROR_NOT_FOUND,
+                    1363 => MDB2_ERROR_NOT_FOUND,
+                    1365 => MDB2_ERROR_DIVZERO,
+                    1451 => MDB2_ERROR_CONSTRAINT,
+                    1452 => MDB2_ERROR_CONSTRAINT,
+                    1542 => MDB2_ERROR_CANNOT_DROP,
+                    1546 => MDB2_ERROR_CONSTRAINT,
+                    1582 => MDB2_ERROR_CONSTRAINT,
+                    2003 => MDB2_ERROR_CONNECT_FAILED,
+                    2019 => MDB2_ERROR_INVALID,
+                );
+            }
+            if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
+                $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
+                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
+                $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
+            } else {
+                // Doing this in case mode changes during runtime.
+                $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
+                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
+                $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
+            }
+            if (isset($ecode_map[$native_code])) {
+                $error = $ecode_map[$native_code];
+            }
+        }
+        return array($error, $native_code, $native_msg);
+    }
+
+    // }}}
+    // {{{ escape()
+
+    /**
+     * Quotes a string so it can be safely used in a query. It will quote
+     * the text so it can safely be used within a query.
+     *
+     * @param   string  the input string to quote
+     * @param   bool    escape wildcards
+     *
+     * @return  string  quoted string
+     *
+     * @access  public
+     */
+    function escape($text, $escape_wildcards = false)
+    {
+        if ($escape_wildcards) {
+            $text = $this->escapePattern($text);
+        }
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+        $text = @mysql_real_escape_string($text, $connection);
+        return $text;
+    }
+
+    // }}}
+    // {{{ beginTransaction()
+
+    /**
+     * Start a transaction or set a savepoint.
+     *
+     * @param   string  name of a savepoint to set
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function beginTransaction($savepoint = null)
+    {
+        $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        $this->_getServerCapabilities();
+        if (!is_null($savepoint)) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            if (!$this->in_transaction) {
+                return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                    'savepoint cannot be released when changes are auto committed', __FUNCTION__);
+            }
+            $query = 'SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        } elseif ($this->in_transaction) {
+            return MDB2_OK;  //nothing to do
+        }
+        if (!$this->destructor_registered && $this->opened_persistent) {
+            $this->destructor_registered = true;
+            register_shutdown_function('MDB2_closeOpenTransactions');
+        }
+        $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0';
+        $result = $this->_doQuery($query, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $this->in_transaction = true;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commit the database changes done during a transaction that is in
+     * progress or release a savepoint. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after committing the pending changes.
+     *
+     * @param   string  name of a savepoint to release
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function commit($savepoint = null)
+    {
+        $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        if (!$this->in_transaction) {
+            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
+        }
+        if (!is_null($savepoint)) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            $server_info = $this->getServerVersion();
+            if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
+                return MDB2_OK;
+            }
+            $query = 'RELEASE SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        }
+
+        if (!$this->supports('transactions')) {
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'transactions are not supported', __FUNCTION__);
+        }
+
+        $result = $this->_doQuery('COMMIT', true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!$this->start_transaction) {
+            $query = 'SET AUTOCOMMIT = 1';
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $this->in_transaction = false;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Cancel any database changes done during a transaction or since a specific
+     * savepoint that is in progress. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after canceling the pending changes.
+     *
+     * @param   string  name of a savepoint to rollback to
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function rollback($savepoint = null)
+    {
+        $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        if (!$this->in_transaction) {
+            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                'rollback cannot be done changes are auto committed', __FUNCTION__);
+        }
+        if (!is_null($savepoint)) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        }
+
+        $query = 'ROLLBACK';
+        $result = $this->_doQuery($query, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!$this->start_transaction) {
+            $query = 'SET AUTOCOMMIT = 1';
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $this->in_transaction = false;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function setTransactionIsolation()
+
+    /**
+     * Set the transacton isolation level.
+     *
+     * @param   string  standard isolation level
+     *                  READ UNCOMMITTED (allows dirty reads)
+     *                  READ COMMITTED (prevents dirty reads)
+     *                  REPEATABLE READ (prevents nonrepeatable reads)
+     *                  SERIALIZABLE (prevents phantom reads)
+     * @param   array some transaction options:
+     *                  'wait' => 'WAIT' | 'NO WAIT'
+     *                  'rw'   => 'READ WRITE' | 'READ ONLY'
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function setTransactionIsolation($isolation, $options = array())
+    {
+        $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
+        if (!$this->supports('transactions')) {
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'transactions are not supported', __FUNCTION__);
+        }
+        switch ($isolation) {
+        case 'READ UNCOMMITTED':
+        case 'READ COMMITTED':
+        case 'REPEATABLE READ':
+        case 'SERIALIZABLE':
+            break;
+        default:
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'isolation level is not supported: '.$isolation, __FUNCTION__);
+        }
+
+        $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation";
+        return $this->_doQuery($query, true);
+    }
+
+    // }}}
+    // {{{ _doConnect()
+
+    /**
+     * do the grunt work of the connect
+     *
+     * @return connection on success or MDB2 Error Object on failure
+     * @access protected
+     */
+    function _doConnect($username, $password, $persistent = false)
+    {
+        if (!extension_loaded($this->phptype)) {
+            return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
+        }
+
+        $params = array();
+        $unix = ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix');
+        if (empty($this->dsn['hostspec'])) {
+            $this->dsn['hostspec'] = $unix ? '' : 'localhost';
+        }
+        if ($this->dsn['hostspec']) {
+            $params[0] = $this->dsn['hostspec'] . ($this->dsn['port'] ? ':' . $this->dsn['port'] : '');
+        } else {
+            $params[0] = ':' . $this->dsn['socket'];
+        }
+        $params[] = $username ? $username : null;
+        $params[] = $password ? $password : null;
+        if (!$persistent) {
+            if ($this->_isNewLinkSet()) {
+                $params[] = true;
+            } else {
+                $params[] = false;
+            }
+        }
+        if (version_compare(phpversion(), '4.3.0', '>=')) {
+            $params[] = isset($this->dsn['client_flags'])
+                ? $this->dsn['client_flags'] : null;
+        }
+        $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
+
+        $connection = @call_user_func_array($connect_function, $params);
+        if (!$connection) {
+            if (($err = @mysql_error()) != '') {
+                return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
+                    $err, __FUNCTION__);
+            } else {
+                return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
+                    'unable to establish a connection', __FUNCTION__);
+            }
+        }
+
+        if (!empty($this->dsn['charset'])) {
+            $result = $this->setCharset($this->dsn['charset'], $connection);
+            if (MDB2::isError($result)) {
+                $this->disconnect(false);
+                return $result;
+            }
+        }
+
+        return $connection;
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database
+     *
+     * @return MDB2_OK on success, MDB2 Error Object on failure
+     * @access public
+     */
+    function connect()
+    {
+        if (is_resource($this->connection)) {
+            //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
+            if (MDB2::areEquals($this->connected_dsn, $this->dsn)
+                && $this->opened_persistent == $this->options['persistent']
+            ) {
+                return MDB2_OK;
+            }
+            $this->disconnect(false);
+        }
+
+        $connection = $this->_doConnect(
+            $this->dsn['username'],
+            $this->dsn['password'],
+            $this->options['persistent']
+        );
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $this->connection = $connection;
+        $this->connected_dsn = $this->dsn;
+        $this->connected_database_name = '';
+        $this->opened_persistent = $this->options['persistent'];
+        $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
+
+        if ($this->database_name) {
+            if ($this->database_name != $this->connected_database_name) {
+                if (!@mysql_select_db($this->database_name, $connection)) {
+                    $err = $this->raiseError(null, null, null,
+                        'Could not select the database: '.$this->database_name, __FUNCTION__);
+                    return $err;
+                }
+                $this->connected_database_name = $this->database_name;
+            }
+        }
+
+        $this->_getServerCapabilities();
+
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ setCharset()
+
+    /**
+     * Set the charset on the current connection
+     *
+     * @param string    charset (or array(charset, collation))
+     * @param resource  connection handle
+     *
+     * @return true on success, MDB2 Error Object on failure
+     */
+    function setCharset($charset, $connection = null)
+    {
+        if (is_null($connection)) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        $collation = null;
+        if (is_array($charset) && 2 == count($charset)) {
+            $collation = array_pop($charset);
+            $charset   = array_pop($charset);
+        }
+        $client_info = mysql_get_client_info();
+        if (function_exists('mysql_set_charset') && version_compare($client_info, '5.0.6')) {
+            if (!$result = mysql_set_charset($charset, $connection)) {
+                $err = $this->raiseError(null, null, null,
+                    'Could not set client character set', __FUNCTION__);
+                return $err;
+            }
+            return $result;
+        }
+        $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'";
+        if (!is_null($collation)) {
+            $query .= " COLLATE '".mysql_real_escape_string($collation, $connection)."'";
+        }
+        return $this->_doQuery($query, true, $connection);
+    }
+
+    // }}}
+    // {{{ databaseExists()
+
+    /**
+     * check if given database name is exists?
+     *
+     * @param string $name    name of the database that should be checked
+     *
+     * @return mixed true/false on success, a MDB2 error on failure
+     * @access public
+     */
+    function databaseExists($name)
+    {
+        $connection = $this->_doConnect($this->dsn['username'],
+                                        $this->dsn['password'],
+                                        $this->options['persistent']);
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $result = @mysql_select_db($name, $connection);
+        @mysql_close($connection);
+
+        return $result;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Log out and disconnect from the database.
+     *
+     * @param  boolean $force if the disconnect should be forced even if the
+     *                        connection is opened persistently
+     * @return mixed true on success, false if not connected and error
+     *                object on error
+     * @access public
+     */
+    function disconnect($force = true)
+    {
+        if (is_resource($this->connection)) {
+            if ($this->in_transaction) {
+                $dsn = $this->dsn;
+                $database_name = $this->database_name;
+                $persistent = $this->options['persistent'];
+                $this->dsn = $this->connected_dsn;
+                $this->database_name = $this->connected_database_name;
+                $this->options['persistent'] = $this->opened_persistent;
+                $this->rollback();
+                $this->dsn = $dsn;
+                $this->database_name = $database_name;
+                $this->options['persistent'] = $persistent;
+            }
+
+            if (!$this->opened_persistent || $force) {
+                $ok = @mysql_close($this->connection);
+                if (!$ok) {
+                    return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED,
+                           null, null, null, __FUNCTION__);
+                }
+            }
+        } else {
+            return false;
+        }
+        return parent::disconnect($force);
+    }
+
+    // }}}
+    // {{{ standaloneQuery()
+
+    /**
+     * execute a query as DBA
+     *
+     * @param string $query the SQL query
+     * @param mixed   $types  array that contains the types of the columns in
+     *                        the result set
+     * @param boolean $is_manip  if the query is a manipulation query
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function standaloneQuery($query, $types = null, $is_manip = false)
+    {
+        $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
+        $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
+        $connection = $this->_doConnect($user, $pass, $this->options['persistent']);
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
+
+        $result = $this->_doQuery($query, $is_manip, $connection, $this->database_name);
+        if (!MDB2::isError($result)) {
+            $result = $this->_affectedRows($connection, $result);
+        }
+
+        @mysql_close($connection);
+        return $result;
+    }
+
+    // }}}
+    // {{{ _doQuery()
+
+    /**
+     * Execute a query
+     * @param string $query  query
+     * @param boolean $is_manip  if the query is a manipulation query
+     * @param resource $connection
+     * @param string $database_name
+     * @return result or error object
+     * @access protected
+     */
+    function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
+    {
+        $this->last_query = $query;
+        $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        if ($this->options['disable_query']) {
+            $result = $is_manip ? 0 : null;
+            return $result;
+        }
+
+        if (is_null($connection)) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        if (is_null($database_name)) {
+            $database_name = $this->database_name;
+        }
+
+        if ($database_name) {
+            if ($database_name != $this->connected_database_name) {
+                if (!@mysql_select_db($database_name, $connection)) {
+                    $err = $this->raiseError(null, null, null,
+                        'Could not select the database: '.$database_name, __FUNCTION__);
+                    return $err;
+                }
+                $this->connected_database_name = $database_name;
+            }
+        }
+
+        $function = $this->options['result_buffering']
+            ? 'mysql_query' : 'mysql_unbuffered_query';
+        $result = @$function($query, $connection);
+        if (!$result && 0 !== mysql_errno($connection)) {
+            $err = $this->raiseError(null, null, null,
+                'Could not execute statement', __FUNCTION__);
+            return $err;
+        }
+
+        $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
+        return $result;
+    }
+
+    // }}}
+    // {{{ _affectedRows()
+
+    /**
+     * Returns the number of rows affected
+     *
+     * @param resource $result
+     * @param resource $connection
+     * @return mixed MDB2 Error Object or the number of rows affected
+     * @access private
+     */
+    function _affectedRows($connection, $result = null)
+    {
+        if (is_null($connection)) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        return @mysql_affected_rows($connection);
+    }
+
+    // }}}
+    // {{{ _modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * @param string $query  query to modify
+     * @param boolean $is_manip  if it is a DML query
+     * @param integer $limit  limit the number of rows
+     * @param integer $offset  start reading from given offset
+     * @return string modified query
+     * @access protected
+     */
+    function _modifyQuery($query, $is_manip, $limit, $offset)
+    {
+        if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
+            // "DELETE FROM table" gives 0 affected rows in MySQL.
+            // This little hack lets you know how many rows were deleted.
+            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+                                      'DELETE FROM \1 WHERE 1=1', $query);
+            }
+        }
+        if ($limit > 0
+            && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
+        ) {
+            $query = rtrim($query);
+            if (substr($query, -1) == ';') {
+                $query = substr($query, 0, -1);
+            }
+
+            // LIMIT doesn't always come last in the query
+            // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
+            $after = '';
+            if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
+                $after = $matches[0];
+                $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
+            } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
+               $after = $matches[0];
+               $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
+            } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
+               $after = $matches[0];
+               $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
+            }
+
+            if ($is_manip) {
+                return $query . " LIMIT $limit" . $after;
+            } else {
+                return $query . " LIMIT $offset, $limit" . $after;
+            }
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ getServerVersion()
+
+    /**
+     * return version information about the server
+     *
+     * @param bool   $native  determines if the raw version string should be returned
+     * @return mixed array/string with version information or MDB2 error object
+     * @access public
+     */
+    function getServerVersion($native = false)
+    {
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+        if ($this->connected_server_info) {
+            $server_info = $this->connected_server_info;
+        } else {
+            $server_info = @mysql_get_server_info($connection);
+        }
+        if (!$server_info) {
+            return $this->raiseError(null, null, null,
+                'Could not get server information', __FUNCTION__);
+        }
+        // cache server_info
+        $this->connected_server_info = $server_info;
+        if (!$native) {
+            $tmp = explode('.', $server_info, 3);
+            if (isset($tmp[2]) && strpos($tmp[2], '-')) {
+                $tmp2 = explode('-', @$tmp[2], 2);
+            } else {
+                $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
+                $tmp2[1] = null;
+            }
+            $server_info = array(
+                'major' => isset($tmp[0]) ? $tmp[0] : null,
+                'minor' => isset($tmp[1]) ? $tmp[1] : null,
+                'patch' => $tmp2[0],
+                'extra' => $tmp2[1],
+                'native' => $server_info,
+            );
+        }
+        return $server_info;
+    }
+
+    // }}}
+    // {{{ _getServerCapabilities()
+
+    /**
+     * Fetch some information about the server capabilities
+     * (transactions, subselects, prepared statements, etc).
+     *
+     * @access private
+     */
+    function _getServerCapabilities()
+    {
+        if (!$this->server_capabilities_checked) {
+            $this->server_capabilities_checked = true;
+
+            //set defaults
+            $this->supported['sub_selects'] = 'emulated';
+            $this->supported['prepared_statements'] = 'emulated';
+            $this->supported['triggers'] = false;
+            $this->start_transaction = false;
+            $this->varchar_max_length = 255;
+
+            $server_info = $this->getServerVersion();
+            if (is_array($server_info)) {
+                $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'];
+
+                if (!version_compare($server_version, '4.1.0', '<')) {
+                    $this->supported['sub_selects'] = true;
+                    $this->supported['prepared_statements'] = true;
+                }
+
+                // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB)
+                if (version_compare($server_version, '4.1.0', '>=')) {
+                    if (version_compare($server_version, '4.1.1', '<')) {
+                        $this->supported['savepoints'] = false;
+                    }
+                } elseif (version_compare($server_version, '4.0.14', '<')) {
+                    $this->supported['savepoints'] = false;
+                }
+
+                if (!version_compare($server_version, '4.0.11', '<')) {
+                    $this->start_transaction = true;
+                }
+
+                if (!version_compare($server_version, '5.0.3', '<')) {
+                    $this->varchar_max_length = 65532;
+                }
+
+                if (!version_compare($server_version, '5.0.2', '<')) {
+                    $this->supported['triggers'] = true;
+                }
+            }
+        }
+    }
+
+    // }}}
+    // {{{ function _skipUserDefinedVariable($query, $position)
+
+    /**
+     * Utility method, used by prepare() to avoid misinterpreting MySQL user
+     * defined variables (SELECT @x:=5) for placeholders.
+     * Check if the placeholder is a false positive, i.e. if it is an user defined
+     * variable instead. If so, skip it and advance the position, otherwise
+     * return the current position, which is valid
+     *
+     * @param string $query
+     * @param integer $position current string cursor position
+     * @return integer $new_position
+     * @access protected
+     */
+    function _skipUserDefinedVariable($query, $position)
+    {
+        $found = strpos(strrev(substr($query, 0, $position)), '@');
+        if ($found === false) {
+            return $position;
+        }
+        $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
+        $substring = substr($query, $pos, $position - $pos + 2);
+        if (preg_match('/^@\w+\s*:=$/', $substring)) {
+            return $position + 1; //found an user defined variable: skip it
+        }
+        return $position;
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute().
+     * With some database backends, this is emulated.
+     * prepare() requires a generic query as string like
+     * 'INSERT INTO numbers VALUES(?,?)' or
+     * 'INSERT INTO numbers VALUES(:foo,:bar)'.
+     * The ? and :name and are placeholders which can be set using
+     * bindParam() and the query can be sent off using the execute() method.
+     * The allowed format for :name can be set with the 'bindname_format' option.
+     *
+     * @param string $query the query to prepare
+     * @param mixed   $types  array that contains the types of the placeholders
+     * @param mixed   $result_types  array that contains the types of the columns in
+     *                        the result set or MDB2_PREPARE_RESULT, if set to
+     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
+     * @param mixed   $lobs   key (field) value (parameter) pair for all lob placeholders
+     * @return mixed resource handle for the prepared query on success, a MDB2
+     *        error on failure
+     * @access public
+     * @see bindParam, execute
+     */
+    function prepare($query, $types = null, $result_types = null, $lobs = array())
+    {
+        // connect to get server capabilities (http://pear.php.net/bugs/16147)
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        if ($this->options['emulate_prepared']
+            || $this->supported['prepared_statements'] !== true
+        ) {
+            return parent::prepare($query, $types, $result_types, $lobs);
+        }
+        $is_manip = ($result_types === MDB2_PREPARE_MANIP);
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
+        $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        $placeholder_type_guess = $placeholder_type = null;
+        $question = '?';
+        $colon = ':';
+        $positions = array();
+        $position = 0;
+        while ($position < strlen($query)) {
+            $q_position = strpos($query, $question, $position);
+            $c_position = strpos($query, $colon, $position);
+            if ($q_position && $c_position) {
+                $p_position = min($q_position, $c_position);
+            } elseif ($q_position) {
+                $p_position = $q_position;
+            } elseif ($c_position) {
+                $p_position = $c_position;
+            } else {
+                break;
+            }
+            if (is_null($placeholder_type)) {
+                $placeholder_type_guess = $query[$p_position];
+            }
+
+            $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
+            if (MDB2::isError($new_pos)) {
+                return $new_pos;
+            }
+            if ($new_pos != $position) {
+                $position = $new_pos;
+                continue; //evaluate again starting from the new position
+            }
+
+            //make sure this is not part of an user defined variable
+            $new_pos = $this->_skipUserDefinedVariable($query, $position);
+            if ($new_pos != $position) {
+                $position = $new_pos;
+                continue; //evaluate again starting from the new position
+            }
+
+            if ($query[$position] == $placeholder_type_guess) {
+                if (is_null($placeholder_type)) {
+                    $placeholder_type = $query[$p_position];
+                    $question = $colon = $placeholder_type;
+                }
+                if ($placeholder_type == ':') {
+                    $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
+                    $parameter = preg_replace($regexp, '\\1', $query);
+                    if ($parameter === '') {
+                        $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
+                            'named parameter name must match "bindname_format" option', __FUNCTION__);
+                        return $err;
+                    }
+                    $positions[$p_position] = $parameter;
+                    $query = substr_replace($query, '?', $position, strlen($parameter)+1);
+                } else {
+                    $positions[$p_position] = count($positions);
+                }
+                $position = $p_position + 1;
+            } else {
+                $position = $p_position;
+            }
+        }
+
+        static $prep_statement_counter = 1;
+        $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand()));
+        $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']);
+        $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
+        $statement = $this->_doQuery($query, true, $connection);
+        if (MDB2::isError($statement)) {
+            return $statement;
+        }
+
+        $class_name = 'MDB2_Statement_'.$this->phptype;
+        $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
+        $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
+        return $obj;
+    }
+
+    // }}}
+    // {{{ replace()
+
+    /**
+     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
+     * query, except that if there is already a row in the table with the same
+     * key field values, the old row is deleted before the new row is inserted.
+     *
+     * The REPLACE type of query does not make part of the SQL standards. Since
+     * practically only MySQL implements it natively, this type of query is
+     * emulated through this method for other DBMS using standard types of
+     * queries inside a transaction to assure the atomicity of the operation.
+     *
+     * @access public
+     *
+     * @param string $table name of the table on which the REPLACE query will
+     *  be executed.
+     * @param array $fields associative array that describes the fields and the
+     *  values that will be inserted or updated in the specified table. The
+     *  indexes of the array are the names of all the fields of the table. The
+     *  values of the array are also associative arrays that describe the
+     *  values and other properties of the table fields.
+     *
+     *  Here follows a list of field properties that need to be specified:
+     *
+     *    value:
+     *          Value to be assigned to the specified field. This value may be
+     *          of specified in database independent type format as this
+     *          function can perform the necessary datatype conversions.
+     *
+     *    Default:
+     *          this property is required unless the Null property
+     *          is set to 1.
+     *
+     *    type
+     *          Name of the type of the field. Currently, all types Metabase
+     *          are supported except for clob and blob.
+     *
+     *    Default: no type conversion
+     *
+     *    null
+     *          Boolean property that indicates that the value for this field
+     *          should be set to null.
+     *
+     *          The default value for fields missing in INSERT queries may be
+     *          specified the definition of a table. Often, the default value
+     *          is already null, but since the REPLACE may be emulated using
+     *          an UPDATE query, make sure that all fields of the table are
+     *          listed in this function argument array.
+     *
+     *    Default: 0
+     *
+     *    key
+     *          Boolean property that indicates that this field should be
+     *          handled as a primary key or at least as part of the compound
+     *          unique index of the table that will determine the row that will
+     *          updated if it exists or inserted a new row otherwise.
+     *
+     *          This function will fail if no key field is specified or if the
+     *          value of a key field is set to null because fields that are
+     *          part of unique index they may not be null.
+     *
+     *    Default: 0
+     *
+     * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     */
+    function replace($table, $fields)
+    {
+        $count = count($fields);
+        $query = $values = '';
+        $keys = $colnum = 0;
+        for (reset($fields); $colnum < $count; next($fields), $colnum++) {
+            $name = key($fields);
+            if ($colnum > 0) {
+                $query .= ',';
+                $values.= ',';
+            }
+            $query.= $this->quoteIdentifier($name, true);
+            if (isset($fields[$name]['null']) && $fields[$name]['null']) {
+                $value = 'NULL';
+            } else {
+                $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
+                $value = $this->quote($fields[$name]['value'], $type);
+                if (MDB2::isError($value)) {
+                    return $value;
+                }
+            }
+            $values.= $value;
+            if (isset($fields[$name]['key']) && $fields[$name]['key']) {
+                if ($value === 'NULL') {
+                    return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                        'key value '.$name.' may not be NULL', __FUNCTION__);
+                }
+                $keys++;
+            }
+        }
+        if ($keys == 0) {
+            return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                'not specified which fields are keys', __FUNCTION__);
+        }
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $table = $this->quoteIdentifier($table, true);
+        $query = "REPLACE INTO $table ($query) VALUES ($values)";
+        $result = $this->_doQuery($query, true, $connection);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $this->_affectedRows($connection, $result);
+    }
+
+    // }}}
+    // {{{ nextID()
+
+    /**
+     * Returns the next free id of a sequence
+     *
+     * @param string $seq_name name of the sequence
+     * @param boolean $ondemand when true the sequence is
+     *                          automatic created, if it
+     *                          not exists
+     *
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function nextID($seq_name, $ondemand = true)
+    {
+        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
+        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
+        $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
+        $this->pushErrorHandling(PEAR_ERROR_RETURN);
+        $this->expectError(MDB2_ERROR_NOSUCHTABLE);
+        $result = $this->_doQuery($query, true);
+        $this->popExpect();
+        $this->popErrorHandling();
+        if (MDB2::isError($result)) {
+            if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
+                $this->loadModule('Manager', null, true);
+                $result = $this->manager->createSequence($seq_name);
+                if (MDB2::isError($result)) {
+                    return $this->raiseError($result, null, null,
+                        'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
+                } else {
+                    return $this->nextID($seq_name, false);
+                }
+            }
+            return $result;
+        }
+        $value = $this->lastInsertID();
+        if (is_numeric($value)) {
+            $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
+            }
+        }
+        return $value;
+    }
+
+    // }}}
+    // {{{ lastInsertID()
+
+    /**
+     * Returns the autoincrement ID if supported or $id or fetches the current
+     * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
+     *
+     * @param string $table name of the table into which a new row was inserted
+     * @param string $field name of the field into which a new row was inserted
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function lastInsertID($table = null, $field = null)
+    {
+        // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
+        // not casting to integer to handle BIGINT http://pear.php.net/bugs/bug.php?id=17650
+        return $this->queryOne('SELECT LAST_INSERT_ID()');
+    }
+
+    // }}}
+    // {{{ currID()
+
+    /**
+     * Returns the current id of a sequence
+     *
+     * @param string $seq_name name of the sequence
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function currID($seq_name)
+    {
+        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
+        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
+        $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
+        return $this->queryOne($query, 'integer');
+    }
+}
+
+/**
+ * MDB2 MySQL result driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Result_mysql extends MDB2_Result_Common
+{
+    // }}}
+    // {{{ fetchRow()
+
+    /**
+     * Fetch a row and insert the data into an existing array.
+     *
+     * @param int       $fetchmode  how the array data should be indexed
+     * @param int    $rownum    number of the row where the data can be found
+     * @return int data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if (!is_null($rownum)) {
+            $seek = $this->seek($rownum);
+            if (MDB2::isError($seek)) {
+                return $seek;
+            }
+        }
+        if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->db->fetchmode;
+        }
+        if (   $fetchmode == MDB2_FETCHMODE_ASSOC
+            || $fetchmode == MDB2_FETCHMODE_OBJECT
+        ) {
+            $row = @mysql_fetch_assoc($this->result);
+            if (is_array($row)
+                && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
+            ) {
+                $row = array_change_key_case($row, $this->db->options['field_case']);
+            }
+        } else {
+           $row = @mysql_fetch_row($this->result);
+        }
+
+        if (!$row) {
+            if ($this->result === false) {
+                $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+                return $err;
+            }
+            return null;
+        }
+        $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
+        $rtrim = false;
+        if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
+            if (empty($this->types)) {
+                $mode += MDB2_PORTABILITY_RTRIM;
+            } else {
+                $rtrim = true;
+            }
+        }
+        if ($mode) {
+            $this->db->_fixResultArrayValues($row, $mode);
+        }
+        if (   (   $fetchmode != MDB2_FETCHMODE_ASSOC
+                && $fetchmode != MDB2_FETCHMODE_OBJECT)
+            && !empty($this->types)
+        ) {
+            $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
+        } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC
+                || $fetchmode == MDB2_FETCHMODE_OBJECT)
+            && !empty($this->types_assoc)
+        ) {
+            $row = $this->db->datatype->convertResultRow($this->types_assoc, $row, $rtrim);
+        }
+        if (!empty($this->values)) {
+            $this->_assignBindColumns($row);
+        }
+        if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
+            $object_class = $this->db->options['fetch_class'];
+            if ($object_class == 'stdClass') {
+                $row = (object) $row;
+            } else {
+                $rowObj = new $object_class($row);
+                $row = $rowObj;
+            }
+        }
+        ++$this->rownum;
+        return $row;
+    }
+
+    // }}}
+    // {{{ _getColumnNames()
+
+    /**
+     * Retrieve the names of columns returned by the DBMS in a query result.
+     *
+     * @return  mixed   Array variable that holds the names of columns as keys
+     *                  or an MDB2 error on failure.
+     *                  Some DBMS may not return any columns when the result set
+     *                  does not contain any rows.
+     * @access private
+     */
+    function _getColumnNames()
+    {
+        $columns = array();
+        $numcols = $this->numCols();
+        if (MDB2::isError($numcols)) {
+            return $numcols;
+        }
+        for ($column = 0; $column < $numcols; $column++) {
+            $column_name = @mysql_field_name($this->result, $column);
+            $columns[$column_name] = $column;
+        }
+        if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $columns = array_change_key_case($columns, $this->db->options['field_case']);
+        }
+        return $columns;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Count the number of columns returned by the DBMS in a query result.
+     *
+     * @return mixed integer value with the number of columns, a MDB2 error
+     *                       on failure
+     * @access public
+     */
+    function numCols()
+    {
+        $cols = @mysql_num_fields($this->result);
+        if (is_null($cols)) {
+            if ($this->result === false) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            } elseif (is_null($this->result)) {
+                return count($this->types);
+            }
+            return $this->db->raiseError(null, null, null,
+                'Could not get column count', __FUNCTION__);
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Free the internal resources associated with result.
+     *
+     * @return boolean true on success, false if result is invalid
+     * @access public
+     */
+    function free()
+    {
+        if (is_resource($this->result) && $this->db->connection) {
+            $free = @mysql_free_result($this->result);
+            if ($free === false) {
+                return $this->db->raiseError(null, null, null,
+                    'Could not free result', __FUNCTION__);
+            }
+        }
+        $this->result = false;
+        return MDB2_OK;
+    }
+}
+
+/**
+ * MDB2 MySQL buffered result driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_BufferedResult_mysql extends MDB2_Result_mysql
+{
+    // }}}
+    // {{{ seek()
+
+    /**
+     * Seek to a specific row in a result set
+     *
+     * @param int    $rownum    number of the row where the data can be found
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function seek($rownum = 0)
+    {
+        if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) {
+            if ($this->result === false) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            } elseif (is_null($this->result)) {
+                return MDB2_OK;
+            }
+            return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
+                'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
+        }
+        $this->rownum = $rownum - 1;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ valid()
+
+    /**
+     * Check if the end of the result set has been reached
+     *
+     * @return mixed true or false on sucess, a MDB2 error on failure
+     * @access public
+     */
+    function valid()
+    {
+        $numrows = $this->numRows();
+        if (MDB2::isError($numrows)) {
+            return $numrows;
+        }
+        return $this->rownum < ($numrows - 1);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Returns the number of rows in a result object
+     *
+     * @return mixed MDB2 Error Object or the number of rows
+     * @access public
+     */
+    function numRows()
+    {
+        $rows = @mysql_num_rows($this->result);
+        if (false === $rows) {
+            if (false === $this->result) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            } elseif (is_null($this->result)) {
+                return 0;
+            }
+            return $this->db->raiseError(null, null, null,
+                'Could not get row count', __FUNCTION__);
+        }
+        return $rows;
+    }
+
+    // }}}
+}
+
+/**
+ * MDB2 MySQL statement driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Statement_mysql extends MDB2_Statement_Common
+{
+    // {{{ _execute()
+
+    /**
+     * Execute a prepared query statement helper method.
+     *
+     * @param mixed $result_class string which specifies which result class to use
+     * @param mixed $result_wrap_class string which specifies which class to wrap results in
+     *
+     * @return mixed MDB2_Result or integer (affected rows) on success,
+     *               a MDB2 error on failure
+     * @access private
+     */
+    function _execute($result_class = true, $result_wrap_class = true)
+    {
+        if (is_null($this->statement)) {
+            $result = parent::_execute($result_class, $result_wrap_class);
+            return $result;
+        }
+        $this->db->last_query = $this->query;
+        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
+        if ($this->db->getOption('disable_query')) {
+            $result = $this->is_manip ? 0 : null;
+            return $result;
+        }
+
+        $connection = $this->db->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $query = 'EXECUTE '.$this->statement;
+        if (!empty($this->positions)) {
+            $parameters = array();
+            foreach ($this->positions as $parameter) {
+                if (!array_key_exists($parameter, $this->values)) {
+                    return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                        'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
+                }
+                $close = false;
+                $value = $this->values[$parameter];
+                $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
+                if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) {
+                    if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
+                        if ($match[1] == 'file://') {
+                            $value = $match[2];
+                        }
+                        $value = @fopen($value, 'r');
+                        $close = true;
+                    }
+                    if (is_resource($value)) {
+                        $data = '';
+                        while (!@feof($value)) {
+                            $data.= @fread($value, $this->db->options['lob_buffer_length']);
+                        }
+                        if ($close) {
+                            @fclose($value);
+                        }
+                        $value = $data;
+                    }
+                }
+                $quoted = $this->db->quote($value, $type);
+                if (MDB2::isError($quoted)) {
+                    return $quoted;
+                }
+                $param_query = 'SET @'.$parameter.' = '.$quoted;
+                $result = $this->db->_doQuery($param_query, true, $connection);
+                if (MDB2::isError($result)) {
+                    return $result;
+                }
+            }
+            $query.= ' USING @'.implode(', @', array_values($this->positions));
+        }
+
+        $result = $this->db->_doQuery($query, $this->is_manip, $connection);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        if ($this->is_manip) {
+            $affected_rows = $this->db->_affectedRows($connection, $result);
+            return $affected_rows;
+        }
+
+        $result = $this->db->_wrapResult($result, $this->result_types,
+            $result_class, $result_wrap_class, $this->limit, $this->offset);
+        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
+        return $result;
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Release resources allocated for the specified prepared query.
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function free()
+    {
+        if (is_null($this->positions)) {
+            return $this->db->raiseError(MDB2_ERROR, null, null,
+                'Prepared statement has already been freed', __FUNCTION__);
+        }
+        $result = MDB2_OK;
+
+        if (!is_null($this->statement)) {
+            $connection = $this->db->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+            $query = 'DEALLOCATE PREPARE '.$this->statement;
+            $result = $this->db->_doQuery($query, true, $connection);
+        }
+
+        parent::free();
+        return $result;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Driver/mysqli.php b/WEB-INF/lib/pear/MDB2/Driver/mysqli.php
new file mode 100644 (file)
index 0000000..f49e8eb
--- /dev/null
@@ -0,0 +1,1905 @@
+<?php
+// vim: set et ts=4 sw=4 fdm=marker:
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysqli.php 327320 2012-08-27 15:52:50Z danielc $
+//
+
+/**
+ * MDB2 MySQLi driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Driver_mysqli extends MDB2_Driver_Common
+{
+    // {{{ properties
+
+    public $string_quoting = array(
+        'start'  => "'",
+        'end'    => "'",
+        'escape' => '\\',
+        'escape_pattern' => '\\',
+    );
+
+    public $identifier_quoting = array(
+        'start'  => '`',
+        'end'    => '`',
+        'escape' => '`',
+    );
+
+    /**
+     * The ouptut of mysqli_errno() in _doQuery(), if any.
+     * @var integer
+     */
+    protected $_query_errno;
+
+    /**
+     * The ouptut of mysqli_error() in _doQuery(), if any.
+     * @var string
+     */
+    protected $_query_error;
+
+    public $sql_comments = array(
+        array('start' => '-- ', 'end' => "\n", 'escape' => false),
+        array('start' => '#', 'end' => "\n", 'escape' => false),
+        array('start' => '/*', 'end' => '*/', 'escape' => false),
+    );
+
+    protected $server_capabilities_checked = false;
+
+    protected $start_transaction = false;
+
+    public $varchar_max_length = 255;
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+        parent::__construct();
+
+        $this->phptype = 'mysqli';
+        $this->dbsyntax = 'mysql';
+
+        $this->supported['sequences'] = 'emulated';
+        $this->supported['indexes'] = true;
+        $this->supported['affected_rows'] = true;
+        $this->supported['transactions'] = false;
+        $this->supported['savepoints'] = false;
+        $this->supported['summary_functions'] = true;
+        $this->supported['order_by_text'] = true;
+        $this->supported['current_id'] = 'emulated';
+        $this->supported['limit_queries'] = true;
+        $this->supported['LOBs'] = true;
+        $this->supported['replace'] = true;
+        $this->supported['sub_selects'] = 'emulated';
+        $this->supported['triggers'] = false;
+        $this->supported['auto_increment'] = true;
+        $this->supported['primary_key'] = true;
+        $this->supported['result_introspection'] = true;
+        $this->supported['prepared_statements'] = 'emulated';
+        $this->supported['identifier_quoting'] = true;
+        $this->supported['pattern_escaping'] = true;
+        $this->supported['new_link'] = true;
+
+        $this->options['DBA_username'] = false;
+        $this->options['DBA_password'] = false;
+        $this->options['default_table_type'] = '';
+        $this->options['multi_query'] = false;
+        $this->options['max_identifiers_length'] = 64;
+
+        $this->_reCheckSupportedOptions();
+    }
+
+    // }}}
+    // {{{ _reCheckSupportedOptions()
+
+    /**
+     * If the user changes certain options, other capabilities may depend
+     * on the new settings, so we need to check them (again).
+     *
+     * @access private
+     */
+    function _reCheckSupportedOptions()
+    {
+        $this->supported['transactions'] = $this->options['use_transactions'];
+        $this->supported['savepoints']   = $this->options['use_transactions'];
+        if ($this->options['default_table_type']) {
+            switch (strtoupper($this->options['default_table_type'])) {
+            case 'BLACKHOLE':
+            case 'MEMORY':
+            case 'ARCHIVE':
+            case 'CSV':
+            case 'HEAP':
+            case 'ISAM':
+            case 'MERGE':
+            case 'MRG_ISAM':
+            case 'ISAM':
+            case 'MRG_MYISAM':
+            case 'MYISAM':
+                $this->supported['savepoints']   = false;
+                $this->supported['transactions'] = false;
+                $this->warnings[] = $this->options['default_table_type'] .
+                    ' is not a supported default table type';
+                break;
+            }
+        }
+    }
+
+    // }}}
+    // {{{ function setOption($option, $value)
+
+    /**
+     * set the option for the db class
+     *
+     * @param   string  option name
+     * @param   mixed   value for the option
+     *
+     * @return  mixed   MDB2_OK or MDB2 Error Object
+     *
+     * @access  public
+     */
+    function setOption($option, $value)
+    {
+        $res = parent::setOption($option, $value);
+        $this->_reCheckSupportedOptions();
+    }
+
+    // }}}
+    // {{{ errorInfo()
+
+    /**
+     * This method is used to collect information about an error
+     *
+     * @param integer $error
+     * @return array
+     * @access public
+     */
+    function errorInfo($error = null)
+    {
+        if ($this->_query_errno) {
+            $native_code = $this->_query_errno;
+            $native_msg  = $this->_query_error;
+        } elseif ($this->connection) {
+            $native_code = @mysqli_errno($this->connection);
+            $native_msg  = @mysqli_error($this->connection);
+        } else {
+            $native_code = @mysqli_connect_errno();
+            $native_msg  = @mysqli_connect_error();
+        }
+        if (null === $error) {
+            static $ecode_map;
+            if (empty($ecode_map)) {
+                $ecode_map = array(
+                    1000 => MDB2_ERROR_INVALID, //hashchk
+                    1001 => MDB2_ERROR_INVALID, //isamchk
+                    1004 => MDB2_ERROR_CANNOT_CREATE,
+                    1005 => MDB2_ERROR_CANNOT_CREATE,
+                    1006 => MDB2_ERROR_CANNOT_CREATE,
+                    1007 => MDB2_ERROR_ALREADY_EXISTS,
+                    1008 => MDB2_ERROR_CANNOT_DROP,
+                    1009 => MDB2_ERROR_CANNOT_DROP,
+                    1010 => MDB2_ERROR_CANNOT_DROP,
+                    1011 => MDB2_ERROR_CANNOT_DELETE,
+                    1022 => MDB2_ERROR_ALREADY_EXISTS,
+                    1029 => MDB2_ERROR_NOT_FOUND,
+                    1032 => MDB2_ERROR_NOT_FOUND,
+                    1044 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1045 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1046 => MDB2_ERROR_NODBSELECTED,
+                    1048 => MDB2_ERROR_CONSTRAINT,
+                    1049 => MDB2_ERROR_NOSUCHDB,
+                    1050 => MDB2_ERROR_ALREADY_EXISTS,
+                    1051 => MDB2_ERROR_NOSUCHTABLE,
+                    1054 => MDB2_ERROR_NOSUCHFIELD,
+                    1060 => MDB2_ERROR_ALREADY_EXISTS,
+                    1061 => MDB2_ERROR_ALREADY_EXISTS,
+                    1062 => MDB2_ERROR_ALREADY_EXISTS,
+                    1064 => MDB2_ERROR_SYNTAX,
+                    1067 => MDB2_ERROR_INVALID,
+                    1072 => MDB2_ERROR_NOT_FOUND,
+                    1086 => MDB2_ERROR_ALREADY_EXISTS,
+                    1091 => MDB2_ERROR_NOT_FOUND,
+                    1100 => MDB2_ERROR_NOT_LOCKED,
+                    1109 => MDB2_ERROR_NOT_FOUND,
+                    1125 => MDB2_ERROR_ALREADY_EXISTS,
+                    1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
+                    1138 => MDB2_ERROR_INVALID,
+                    1142 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1143 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1146 => MDB2_ERROR_NOSUCHTABLE,
+                    1149 => MDB2_ERROR_SYNTAX,
+                    1169 => MDB2_ERROR_CONSTRAINT,
+                    1176 => MDB2_ERROR_NOT_FOUND,
+                    1177 => MDB2_ERROR_NOSUCHTABLE,
+                    1213 => MDB2_ERROR_DEADLOCK,
+                    1216 => MDB2_ERROR_CONSTRAINT,
+                    1217 => MDB2_ERROR_CONSTRAINT,
+                    1227 => MDB2_ERROR_ACCESS_VIOLATION,
+                    1235 => MDB2_ERROR_CANNOT_CREATE,
+                    1299 => MDB2_ERROR_INVALID_DATE,
+                    1300 => MDB2_ERROR_INVALID,
+                    1304 => MDB2_ERROR_ALREADY_EXISTS,
+                    1305 => MDB2_ERROR_NOT_FOUND,
+                    1306 => MDB2_ERROR_CANNOT_DROP,
+                    1307 => MDB2_ERROR_CANNOT_CREATE,
+                    1334 => MDB2_ERROR_CANNOT_ALTER,
+                    1339 => MDB2_ERROR_NOT_FOUND,
+                    1356 => MDB2_ERROR_INVALID,
+                    1359 => MDB2_ERROR_ALREADY_EXISTS,
+                    1360 => MDB2_ERROR_NOT_FOUND,
+                    1363 => MDB2_ERROR_NOT_FOUND,
+                    1365 => MDB2_ERROR_DIVZERO,
+                    1451 => MDB2_ERROR_CONSTRAINT,
+                    1452 => MDB2_ERROR_CONSTRAINT,
+                    1542 => MDB2_ERROR_CANNOT_DROP,
+                    1546 => MDB2_ERROR_CONSTRAINT,
+                    1582 => MDB2_ERROR_CONSTRAINT,
+                    2003 => MDB2_ERROR_CONNECT_FAILED,
+                    2019 => MDB2_ERROR_INVALID,
+                );
+            }
+            if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
+                $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
+                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
+                $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
+            } else {
+                // Doing this in case mode changes during runtime.
+                $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
+                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
+                $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
+            }
+            if (isset($ecode_map[$native_code])) {
+                $error = $ecode_map[$native_code];
+            }
+        }
+        return array($error, $native_code, $native_msg);
+    }
+
+    // }}}
+    // {{{ escape()
+
+    /**
+     * Quotes a string so it can be safely used in a query. It will quote
+     * the text so it can safely be used within a query.
+     *
+     * @param   string  the input string to quote
+     * @param   bool    escape wildcards
+     *
+     * @return  string  quoted string
+     *
+     * @access  public
+     */
+    function escape($text, $escape_wildcards = false)
+    {
+        if ($escape_wildcards) {
+            $text = $this->escapePattern($text);
+        }
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+        $text = @mysqli_real_escape_string($connection, $text);
+        return $text;
+    }
+
+    // }}}
+    // {{{ beginTransaction()
+
+    /**
+     * Start a transaction or set a savepoint.
+     *
+     * @param   string  name of a savepoint to set
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function beginTransaction($savepoint = null)
+    {
+        $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        $this->_getServerCapabilities();
+        if (null !== $savepoint) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            if (!$this->in_transaction) {
+                return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                    'savepoint cannot be released when changes are auto committed', __FUNCTION__);
+            }
+            $query = 'SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        }
+        if ($this->in_transaction) {
+            return MDB2_OK;  //nothing to do
+        }
+        $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0';
+        $result = $this->_doQuery($query, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        $this->in_transaction = true;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commit the database changes done during a transaction that is in
+     * progress or release a savepoint. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after committing the pending changes.
+     *
+     * @param   string  name of a savepoint to release
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function commit($savepoint = null)
+    {
+        $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        if (!$this->in_transaction) {
+            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
+        }
+        if (null !== $savepoint) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            $server_info = $this->getServerVersion();
+            if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
+                return MDB2_OK;
+            }
+            $query = 'RELEASE SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        }
+
+        if (!$this->supports('transactions')) {
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'transactions are not supported', __FUNCTION__);
+        }
+
+        $result = $this->_doQuery('COMMIT', true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!$this->start_transaction) {
+            $query = 'SET AUTOCOMMIT = 1';
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $this->in_transaction = false;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Cancel any database changes done during a transaction or since a specific
+     * savepoint that is in progress. This function may only be called when
+     * auto-committing is disabled, otherwise it will fail. Therefore, a new
+     * transaction is implicitly started after canceling the pending changes.
+     *
+     * @param   string  name of a savepoint to rollback to
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     */
+    function rollback($savepoint = null)
+    {
+        $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
+        if (!$this->in_transaction) {
+            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
+                'rollback cannot be done changes are auto committed', __FUNCTION__);
+        }
+        if (null !== $savepoint) {
+            if (!$this->supports('savepoints')) {
+                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                    'savepoints are not supported', __FUNCTION__);
+            }
+            $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
+            return $this->_doQuery($query, true);
+        }
+
+        $query = 'ROLLBACK';
+        $result = $this->_doQuery($query, true);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        if (!$this->start_transaction) {
+            $query = 'SET AUTOCOMMIT = 1';
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        $this->in_transaction = false;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ function setTransactionIsolation()
+
+    /**
+     * Set the transacton isolation level.
+     *
+     * @param   string  standard isolation level
+     *                  READ UNCOMMITTED (allows dirty reads)
+     *                  READ COMMITTED (prevents dirty reads)
+     *                  REPEATABLE READ (prevents nonrepeatable reads)
+     *                  SERIALIZABLE (prevents phantom reads)
+     * @param   array some transaction options:
+     *                  'wait' => 'WAIT' | 'NO WAIT'
+     *                  'rw'   => 'READ WRITE' | 'READ ONLY'
+     *
+     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
+     *
+     * @access  public
+     * @since   2.1.1
+     */
+    function setTransactionIsolation($isolation, $options = array())
+    {
+        $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
+        if (!$this->supports('transactions')) {
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'transactions are not supported', __FUNCTION__);
+        }
+        switch ($isolation) {
+        case 'READ UNCOMMITTED':
+        case 'READ COMMITTED':
+        case 'REPEATABLE READ':
+        case 'SERIALIZABLE':
+            break;
+        default:
+            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
+                'isolation level is not supported: '.$isolation, __FUNCTION__);
+        }
+
+        $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation";
+        return $this->_doQuery($query, true);
+    }
+
+    // }}}
+    // {{{ _doConnect()
+
+    /**
+     * do the grunt work of the connect
+     *
+     * @return connection on success or MDB2 Error Object on failure
+     * @access protected
+     */
+    function _doConnect($username, $password, $persistent = false)
+    {
+        if (!extension_loaded($this->phptype)) {
+            return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
+        }
+
+        $connection = @mysqli_init();
+        if (!empty($this->dsn['charset']) && defined('MYSQLI_SET_CHARSET_NAME')) {
+            @mysqli_options($connection, MYSQLI_SET_CHARSET_NAME, $this->dsn['charset']);
+        }
+
+        if ($this->options['ssl']) {
+            @mysqli_ssl_set(
+                $connection,
+                empty($this->dsn['key'])    ? null : $this->dsn['key'],
+                empty($this->dsn['cert'])   ? null : $this->dsn['cert'],
+                empty($this->dsn['ca'])     ? null : $this->dsn['ca'],
+                empty($this->dsn['capath']) ? null : $this->dsn['capath'],
+                empty($this->dsn['cipher']) ? null : $this->dsn['cipher']
+            );
+        }
+
+        if (!@mysqli_real_connect(
+            $connection,
+            $this->dsn['hostspec'],
+            $username,
+            $password,
+            $this->database_name,
+            $this->dsn['port'],
+            $this->dsn['socket']
+        )) {
+            if (($err = @mysqli_connect_error()) != '') {
+                return $this->raiseError(null,
+                    null, null, $err, __FUNCTION__);
+            } else {
+                return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
+                    'unable to establish a connection', __FUNCTION__);
+            }
+        }
+
+        if (!empty($this->dsn['charset']) && !defined('MYSQLI_SET_CHARSET_NAME')) {
+            $result = $this->setCharset($this->dsn['charset'], $connection);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+
+        return $connection;
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database
+     *
+     * @return true on success, MDB2 Error Object on failure
+     */
+    function connect()
+    {
+        if (is_object($this->connection)) {
+            //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0) {
+            if (MDB2::areEquals($this->connected_dsn, $this->dsn)) {
+                return MDB2_OK;
+            }
+            $this->connection = 0;
+        }
+
+        $connection = $this->_doConnect(
+            $this->dsn['username'],
+            $this->dsn['password']
+        );
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $this->connection = $connection;
+        $this->connected_dsn = $this->dsn;
+        $this->connected_database_name = $this->database_name;
+        $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
+
+        $this->_getServerCapabilities();
+
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ setCharset()
+
+    /**
+     * Set the charset on the current connection
+     *
+     * @param string    charset (or array(charset, collation))
+     * @param resource  connection handle
+     *
+     * @return true on success, MDB2 Error Object on failure
+     */
+    function setCharset($charset, $connection = null)
+    {
+        if (null === $connection) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        $collation = null;
+        if (is_array($charset) && 2 == count($charset)) {
+            $collation = array_pop($charset);
+            $charset   = array_pop($charset);
+        }
+        $client_info = mysqli_get_client_version();
+        if (OS_WINDOWS && ((40111 > $client_info) ||
+            ((50000 <= $client_info) && (50006 > $client_info)))
+        ) {
+            $query = "SET NAMES '".mysqli_real_escape_string($connection, $charset)."'";
+            if (null !== $collation) {
+                $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'";
+            }
+            return $this->_doQuery($query, true, $connection);
+        }
+        if (!$result = mysqli_set_charset($connection, $charset)) {
+            $err = $this->raiseError(null, null, null,
+                'Could not set client character set', __FUNCTION__);
+            return $err;
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ databaseExists()
+
+    /**
+     * check if given database name is exists?
+     *
+     * @param string $name    name of the database that should be checked
+     *
+     * @return mixed true/false on success, a MDB2 error on failure
+     * @access public
+     */
+    function databaseExists($name)
+    {
+        $connection = $this->_doConnect($this->dsn['username'],
+                                        $this->dsn['password']);
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $result = @mysqli_select_db($connection, $name);
+        @mysqli_close($connection);
+
+        return $result;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Log out and disconnect from the database.
+     *
+     * @param  boolean $force if the disconnect should be forced even if the
+     *                        connection is opened persistently
+     * @return mixed true on success, false if not connected and error
+     *                object on error
+     * @access public
+     */
+    function disconnect($force = true)
+    {
+        if (is_object($this->connection)) {
+            if ($this->in_transaction) {
+                $dsn = $this->dsn;
+                $database_name = $this->database_name;
+                $persistent = $this->options['persistent'];
+                $this->dsn = $this->connected_dsn;
+                $this->database_name = $this->connected_database_name;
+                $this->options['persistent'] = $this->opened_persistent;
+                $this->rollback();
+                $this->dsn = $dsn;
+                $this->database_name = $database_name;
+                $this->options['persistent'] = $persistent;
+            }
+
+            if ($force) {
+                $ok = @mysqli_close($this->connection);
+                if (!$ok) {
+                    return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED,
+                           null, null, null, __FUNCTION__);
+                }
+            }
+        } else {
+            return false;
+        }
+        return parent::disconnect($force);
+    }
+
+    // }}}
+    // {{{ standaloneQuery()
+
+   /**
+     * execute a query as DBA
+     *
+     * @param string $query the SQL query
+     * @param mixed   $types  array that contains the types of the columns in
+     *                        the result set
+     * @param boolean $is_manip  if the query is a manipulation query
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function standaloneQuery($query, $types = null, $is_manip = false)
+    {
+        $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
+        $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
+        $connection = $this->_doConnect($user, $pass);
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
+
+        $result = $this->_doQuery($query, $is_manip, $connection, $this->database_name);
+        if (!MDB2::isError($result)) {
+            $result = $this->_affectedRows($connection, $result);
+        }
+
+        @mysqli_close($connection);
+        return $result;
+    }
+
+    // }}}
+    // {{{ _doQuery()
+
+    /**
+     * Execute a query
+     * @param string $query  query
+     * @param boolean $is_manip  if the query is a manipulation query
+     * @param resource $connection
+     * @param string $database_name
+     * @return result or error object
+     * @access protected
+     */
+    function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
+    {
+        $this->last_query = $query;
+        $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        if ($this->options['disable_query']) {
+            $result = $is_manip ? 0 : null;
+            return $result;
+        }
+
+        if (null === $connection) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        if (null === $database_name) {
+            $database_name = $this->database_name;
+        }
+
+        if ($database_name) {
+            if ($database_name != $this->connected_database_name) {
+                if (!@mysqli_select_db($connection, $database_name)) {
+                    $err = $this->raiseError(null, null, null,
+                        'Could not select the database: '.$database_name, __FUNCTION__);
+                    return $err;
+                }
+                $this->connected_database_name = $database_name;
+            }
+        }
+
+        if ($this->options['multi_query']) {
+            $result = mysqli_multi_query($connection, $query);
+        } else {
+            $resultmode = $this->options['result_buffering'] ? MYSQLI_USE_RESULT : MYSQLI_USE_RESULT;
+            $result = mysqli_query($connection, $query);
+        }
+
+        if (!$result) {
+            // Store now because standaloneQuery throws off $this->connection.
+            $this->_query_errno = mysqli_errno($connection);
+            if (0 !== $this->_query_errno) {
+                $this->_query_error = mysqli_error($connection);
+                $err = $this->raiseError(null, null, null,
+                    'Could not execute statement', __FUNCTION__);
+                return $err;
+            }
+        }
+
+        if ($this->options['multi_query']) {
+            if ($this->options['result_buffering']) {
+                if (!($result = @mysqli_store_result($connection))) {
+                    $err = $this->raiseError(null, null, null,
+                        'Could not get the first result from a multi query', __FUNCTION__);
+                    return $err;
+                }
+            } elseif (!($result = @mysqli_use_result($connection))) {
+                $err = $this->raiseError(null, null, null,
+                        'Could not get the first result from a multi query', __FUNCTION__);
+                return $err;
+            }
+        }
+
+        $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
+        return $result;
+    }
+
+    // }}}
+    // {{{ _affectedRows()
+
+    /**
+     * Returns the number of rows affected
+     *
+     * @param resource $result
+     * @param resource $connection
+     * @return mixed MDB2 Error Object or the number of rows affected
+     * @access private
+     */
+    function _affectedRows($connection, $result = null)
+    {
+        if (null === $connection) {
+            $connection = $this->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+        }
+        return @mysqli_affected_rows($connection);
+    }
+
+    // }}}
+    // {{{ _modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * @param string $query  query to modify
+     * @param boolean $is_manip  if it is a DML query
+     * @param integer $limit  limit the number of rows
+     * @param integer $offset  start reading from given offset
+     * @return string modified query
+     * @access protected
+     */
+    function _modifyQuery($query, $is_manip, $limit, $offset)
+    {
+        if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
+            // "DELETE FROM table" gives 0 affected rows in MySQL.
+            // This little hack lets you know how many rows were deleted.
+            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+                                      'DELETE FROM \1 WHERE 1=1', $query);
+            }
+        }
+        if ($limit > 0
+            && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
+        ) {
+            $query = rtrim($query);
+            if (substr($query, -1) == ';') {
+                $query = substr($query, 0, -1);
+            }
+
+            // LIMIT doesn't always come last in the query
+            // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
+            $after = '';
+            if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
+                $after = $matches[0];
+                $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
+            } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
+               $after = $matches[0];
+               $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
+            } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
+               $after = $matches[0];
+               $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
+            }
+
+            if ($is_manip) {
+                return $query . " LIMIT $limit" . $after;
+            } else {
+                return $query . " LIMIT $offset, $limit" . $after;
+            }
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ getServerVersion()
+
+    /**
+     * return version information about the server
+     *
+     * @param bool   $native  determines if the raw version string should be returned
+     * @return mixed array/string with version information or MDB2 error object
+     * @access public
+     */
+    function getServerVersion($native = false)
+    {
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+        if ($this->connected_server_info) {
+            $server_info = $this->connected_server_info;
+        } else {
+            $server_info = @mysqli_get_server_info($connection);
+        }
+        if (!$server_info) {
+            return $this->raiseError(null, null, null,
+                'Could not get server information', __FUNCTION__);
+        }
+        // cache server_info
+        $this->connected_server_info = $server_info;
+        if (!$native) {
+            $tmp = explode('.', $server_info, 3);
+            if (isset($tmp[2]) && strpos($tmp[2], '-')) {
+                $tmp2 = explode('-', @$tmp[2], 2);
+            } else {
+                $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
+                $tmp2[1] = null;
+            }
+            $server_info = array(
+                'major' => isset($tmp[0]) ? $tmp[0] : null,
+                'minor' => isset($tmp[1]) ? $tmp[1] : null,
+                'patch' => $tmp2[0],
+                'extra' => $tmp2[1],
+                'native' => $server_info,
+            );
+        }
+        return $server_info;
+    }
+
+    // }}}
+    // {{{ _getServerCapabilities()
+
+    /**
+     * Fetch some information about the server capabilities
+     * (transactions, subselects, prepared statements, etc).
+     *
+     * @access private
+     */
+    function _getServerCapabilities()
+    {
+        if (!$this->server_capabilities_checked) {
+            $this->server_capabilities_checked = true;
+
+            //set defaults
+            $this->supported['sub_selects'] = 'emulated';
+            $this->supported['prepared_statements'] = 'emulated';
+            $this->supported['triggers'] = false;
+            $this->start_transaction = false;
+            $this->varchar_max_length = 255;
+
+            $server_info = $this->getServerVersion();
+            if (is_array($server_info)) {
+                $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'];
+
+                if (!version_compare($server_version, '4.1.0', '<')) {
+                    $this->supported['sub_selects'] = true;
+                    $this->supported['prepared_statements'] = true;
+                }
+
+                // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB)
+                if (version_compare($server_version, '4.1.0', '>=')) {
+                    if (version_compare($server_version, '4.1.1', '<')) {
+                        $this->supported['savepoints'] = false;
+                    }
+                } elseif (version_compare($server_version, '4.0.14', '<')) {
+                    $this->supported['savepoints'] = false;
+                }
+
+                if (!version_compare($server_version, '4.0.11', '<')) {
+                    $this->start_transaction = true;
+                }
+
+                if (!version_compare($server_version, '5.0.3', '<')) {
+                    $this->varchar_max_length = 65532;
+                }
+
+                if (!version_compare($server_version, '5.0.2', '<')) {
+                    $this->supported['triggers'] = true;
+                }
+            }
+        }
+    }
+
+    // }}}
+    // {{{ function _skipUserDefinedVariable($query, $position)
+
+    /**
+     * Utility method, used by prepare() to avoid misinterpreting MySQL user
+     * defined variables (SELECT @x:=5) for placeholders.
+     * Check if the placeholder is a false positive, i.e. if it is an user defined
+     * variable instead. If so, skip it and advance the position, otherwise
+     * return the current position, which is valid
+     *
+     * @param string $query
+     * @param integer $position current string cursor position
+     * @return integer $new_position
+     * @access protected
+     */
+    function _skipUserDefinedVariable($query, $position)
+    {
+        $found = strpos(strrev(substr($query, 0, $position)), '@');
+        if (false === $found) {
+            return $position;
+        }
+        $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
+        $substring = substr($query, $pos, $position - $pos + 2);
+        if (preg_match('/^@\w+\s*:=$/', $substring)) {
+            return $position + 1; //found an user defined variable: skip it
+        }
+        return $position;
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute().
+     * With some database backends, this is emulated.
+     * prepare() requires a generic query as string like
+     * 'INSERT INTO numbers VALUES(?,?)' or
+     * 'INSERT INTO numbers VALUES(:foo,:bar)'.
+     * The ? and :name and are placeholders which can be set using
+     * bindParam() and the query can be sent off using the execute() method.
+     * The allowed format for :name can be set with the 'bindname_format' option.
+     *
+     * @param string $query the query to prepare
+     * @param mixed   $types  array that contains the types of the placeholders
+     * @param mixed   $result_types  array that contains the types of the columns in
+     *                        the result set or MDB2_PREPARE_RESULT, if set to
+     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
+     * @param mixed   $lobs   key (field) value (parameter) pair for all lob placeholders
+     * @return mixed resource handle for the prepared query on success, a MDB2
+     *        error on failure
+     * @access public
+     * @see bindParam, execute
+     */
+    function prepare($query, $types = null, $result_types = null, $lobs = array())
+    {
+        // connect to get server capabilities (http://pear.php.net/bugs/16147)
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        if ($this->options['emulate_prepared']
+            || $this->supported['prepared_statements'] !== true
+        ) {
+            return parent::prepare($query, $types, $result_types, $lobs);
+        }
+        $is_manip = ($result_types === MDB2_PREPARE_MANIP);
+        $offset = $this->offset;
+        $limit = $this->limit;
+        $this->offset = $this->limit = 0;
+        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
+        $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
+        if ($result) {
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+            $query = $result;
+        }
+        $placeholder_type_guess = $placeholder_type = null;
+        $question = '?';
+        $colon = ':';
+        $positions = array();
+        $position = 0;
+        while ($position < strlen($query)) {
+            $q_position = strpos($query, $question, $position);
+            $c_position = strpos($query, $colon, $position);
+            if ($q_position && $c_position) {
+                $p_position = min($q_position, $c_position);
+            } elseif ($q_position) {
+                $p_position = $q_position;
+            } elseif ($c_position) {
+                $p_position = $c_position;
+            } else {
+                break;
+            }
+            if (null === $placeholder_type) {
+                $placeholder_type_guess = $query[$p_position];
+            }
+
+            $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
+            if (MDB2::isError($new_pos)) {
+                return $new_pos;
+            }
+            if ($new_pos != $position) {
+                $position = $new_pos;
+                continue; //evaluate again starting from the new position
+            }
+
+            //make sure this is not part of an user defined variable
+            $new_pos = $this->_skipUserDefinedVariable($query, $position);
+            if ($new_pos != $position) {
+                $position = $new_pos;
+                continue; //evaluate again starting from the new position
+            }
+
+            if ($query[$position] == $placeholder_type_guess) {
+                if (null === $placeholder_type) {
+                    $placeholder_type = $query[$p_position];
+                    $question = $colon = $placeholder_type;
+                }
+                if ($placeholder_type == ':') {
+                    $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
+                    $parameter = preg_replace($regexp, '\\1', $query);
+                    if ($parameter === '') {
+                        $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
+                            'named parameter name must match "bindname_format" option', __FUNCTION__);
+                        return $err;
+                    }
+                    $positions[$p_position] = $parameter;
+                    $query = substr_replace($query, '?', $position, strlen($parameter)+1);
+                } else {
+                    $positions[$p_position] = count($positions);
+                }
+                $position = $p_position + 1;
+            } else {
+                $position = $p_position;
+            }
+        }
+
+        if (!$is_manip) {
+            static $prep_statement_counter = 1;
+            $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand()));
+            $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']);
+            $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
+
+            $statement = $this->_doQuery($query, true, $connection);
+            if (MDB2::isError($statement)) {
+                return $statement;
+            }
+            $statement = $statement_name;
+        } else {
+            $statement = @mysqli_prepare($connection, $query);
+            if (!$statement) {
+                $err = $this->raiseError(null, null, null,
+                    'Unable to create prepared statement handle', __FUNCTION__);
+                return $err;
+            }
+        }
+
+        $class_name = 'MDB2_Statement_'.$this->phptype;
+        $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
+        $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
+        return $obj;
+    }
+
+    // }}}
+    // {{{ replace()
+
+    /**
+     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
+     * query, except that if there is already a row in the table with the same
+     * key field values, the old row is deleted before the new row is inserted.
+     *
+     * The REPLACE type of query does not make part of the SQL standards. Since
+     * practically only MySQL implements it natively, this type of query is
+     * emulated through this method for other DBMS using standard types of
+     * queries inside a transaction to assure the atomicity of the operation.
+     *
+     * @access public
+     *
+     * @param string $table name of the table on which the REPLACE query will
+     *  be executed.
+     * @param array $fields associative array that describes the fields and the
+     *  values that will be inserted or updated in the specified table. The
+     *  indexes of the array are the names of all the fields of the table. The
+     *  values of the array are also associative arrays that describe the
+     *  values and other properties of the table fields.
+     *
+     *  Here follows a list of field properties that need to be specified:
+     *
+     *    value:
+     *          Value to be assigned to the specified field. This value may be
+     *          of specified in database independent type format as this
+     *          function can perform the necessary datatype conversions.
+     *
+     *    Default:
+     *          this property is required unless the Null property
+     *          is set to 1.
+     *
+     *    type
+     *          Name of the type of the field. Currently, all types Metabase
+     *          are supported except for clob and blob.
+     *
+     *    Default: no type conversion
+     *
+     *    null
+     *          Boolean property that indicates that the value for this field
+     *          should be set to null.
+     *
+     *          The default value for fields missing in INSERT queries may be
+     *          specified the definition of a table. Often, the default value
+     *          is already null, but since the REPLACE may be emulated using
+     *          an UPDATE query, make sure that all fields of the table are
+     *          listed in this function argument array.
+     *
+     *    Default: 0
+     *
+     *    key
+     *          Boolean property that indicates that this field should be
+     *          handled as a primary key or at least as part of the compound
+     *          unique index of the table that will determine the row that will
+     *          updated if it exists or inserted a new row otherwise.
+     *
+     *          This function will fail if no key field is specified or if the
+     *          value of a key field is set to null because fields that are
+     *          part of unique index they may not be null.
+     *
+     *    Default: 0
+     *
+     * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     */
+    function replace($table, $fields)
+    {
+        $count = count($fields);
+        $query = $values = '';
+        $keys = $colnum = 0;
+        for (reset($fields); $colnum < $count; next($fields), $colnum++) {
+            $name = key($fields);
+            if ($colnum > 0) {
+                $query .= ',';
+                $values.= ',';
+            }
+            $query.= $this->quoteIdentifier($name, true);
+            if (isset($fields[$name]['null']) && $fields[$name]['null']) {
+                $value = 'NULL';
+            } else {
+                $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
+                $value = $this->quote($fields[$name]['value'], $type);
+                if (MDB2::isError($value)) {
+                    return $value;
+                }
+            }
+            $values.= $value;
+            if (isset($fields[$name]['key']) && $fields[$name]['key']) {
+                if ($value === 'NULL') {
+                    return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                        'key value '.$name.' may not be NULL', __FUNCTION__);
+                }
+                $keys++;
+            }
+        }
+        if ($keys == 0) {
+            return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
+                'not specified which fields are keys', __FUNCTION__);
+        }
+
+        $connection = $this->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        $table = $this->quoteIdentifier($table, true);
+        $query = "REPLACE INTO $table ($query) VALUES ($values)";
+        $result = $this->_doQuery($query, true, $connection);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $this->_affectedRows($connection, $result);
+    }
+
+    // }}}
+    // {{{ nextID()
+
+    /**
+     * Returns the next free id of a sequence
+     *
+     * @param string $seq_name name of the sequence
+     * @param boolean $ondemand when true the sequence is
+     *                          automatic created, if it
+     *                          not exists
+     *
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function nextID($seq_name, $ondemand = true)
+    {
+        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
+        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
+        $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
+        $this->pushErrorHandling(PEAR_ERROR_RETURN);
+        $this->expectError(MDB2_ERROR_NOSUCHTABLE);
+        $result = $this->_doQuery($query, true);
+        $this->popExpect();
+        $this->popErrorHandling();
+        if (MDB2::isError($result)) {
+            if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
+                $this->loadModule('Manager', null, true);
+                $result = $this->manager->createSequence($seq_name);
+                if (MDB2::isError($result)) {
+                    return $this->raiseError($result, null, null,
+                        'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
+                } else {
+                    return $this->nextID($seq_name, false);
+                }
+            }
+            return $result;
+        }
+        $value = $this->lastInsertID();
+        if (is_numeric($value)) {
+            $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
+            $result = $this->_doQuery($query, true);
+            if (MDB2::isError($result)) {
+                $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
+            }
+        }
+        return $value;
+    }
+
+    // }}}
+    // {{{ lastInsertID()
+
+    /**
+     * Returns the autoincrement ID if supported or $id or fetches the current
+     * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
+     *
+     * @param string $table name of the table into which a new row was inserted
+     * @param string $field name of the field into which a new row was inserted
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function lastInsertID($table = null, $field = null)
+    {
+        // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
+        // not casting to integer to handle BIGINT http://pear.php.net/bugs/bug.php?id=17650
+        return $this->queryOne('SELECT LAST_INSERT_ID()');
+    }
+
+    // }}}
+    // {{{ currID()
+
+    /**
+     * Returns the current id of a sequence
+     *
+     * @param string $seq_name name of the sequence
+     * @return mixed MDB2 Error Object or id
+     * @access public
+     */
+    function currID($seq_name)
+    {
+        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
+        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
+        $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
+        return $this->queryOne($query, 'integer');
+    }
+}
+
+/**
+ * MDB2 MySQLi result driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Result_mysqli extends MDB2_Result_Common
+{
+    // }}}
+    // {{{ fetchRow()
+
+    /**
+     * Fetch a row and insert the data into an existing array.
+     *
+     * @param int       $fetchmode  how the array data should be indexed
+     * @param int    $rownum    number of the row where the data can be found
+     * @return int data array on success, a MDB2 error on failure
+     * @access public
+     */
+    function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if (null !== $rownum) {
+            $seek = $this->seek($rownum);
+            if (MDB2::isError($seek)) {
+                return $seek;
+            }
+        }
+        if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->db->fetchmode;
+        }
+        if (   $fetchmode == MDB2_FETCHMODE_ASSOC
+            || $fetchmode == MDB2_FETCHMODE_OBJECT
+        ) {
+            $row = @mysqli_fetch_assoc($this->result);
+            if (is_array($row)
+                && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
+            ) {
+                $row = array_change_key_case($row, $this->db->options['field_case']);
+            }
+        } else {
+           $row = @mysqli_fetch_row($this->result);
+        }
+
+        if (!$row) {
+            if (false === $this->result) {
+                $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+                return $err;
+            }
+            return null;
+        }
+        $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
+        $rtrim = false;
+        if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
+            if (empty($this->types)) {
+                $mode += MDB2_PORTABILITY_RTRIM;
+            } else {
+                $rtrim = true;
+            }
+        }
+        if ($mode) {
+            $this->db->_fixResultArrayValues($row, $mode);
+        }
+        if (   (   $fetchmode != MDB2_FETCHMODE_ASSOC
+                && $fetchmode != MDB2_FETCHMODE_OBJECT)
+            && !empty($this->types)
+        ) {
+            $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
+        } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC
+                || $fetchmode == MDB2_FETCHMODE_OBJECT)
+            && !empty($this->types_assoc)
+        ) {
+            $row = $this->db->datatype->convertResultRow($this->types_assoc, $row, $rtrim);
+        }
+        if (!empty($this->values)) {
+            $this->_assignBindColumns($row);
+        }
+        if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
+            $object_class = $this->db->options['fetch_class'];
+            if ($object_class == 'stdClass') {
+                $row = (object) $row;
+            } else {
+                $rowObj = new $object_class($row);
+                $row = $rowObj;
+            }
+        }
+        ++$this->rownum;
+        return $row;
+    }
+
+    // }}}
+    // {{{ _getColumnNames()
+
+    /**
+     * Retrieve the names of columns returned by the DBMS in a query result.
+     *
+     * @return  mixed   Array variable that holds the names of columns as keys
+     *                  or an MDB2 error on failure.
+     *                  Some DBMS may not return any columns when the result set
+     *                  does not contain any rows.
+     * @access private
+     */
+    function _getColumnNames()
+    {
+        $columns = array();
+        $numcols = $this->numCols();
+        if (MDB2::isError($numcols)) {
+            return $numcols;
+        }
+        for ($column = 0; $column < $numcols; $column++) {
+            $column_info = @mysqli_fetch_field_direct($this->result, $column);
+            $columns[$column_info->name] = $column;
+        }
+        if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+            $columns = array_change_key_case($columns, $this->db->options['field_case']);
+        }
+        return $columns;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Count the number of columns returned by the DBMS in a query result.
+     *
+     * @return mixed integer value with the number of columns, a MDB2 error
+     *                       on failure
+     * @access public
+     */
+    function numCols()
+    {
+        $cols = @mysqli_num_fields($this->result);
+        if (null === $cols) {
+            if (false === $this->result) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            }
+            if (null === $this->result) {
+                return count($this->types);
+            }
+            return $this->db->raiseError(null, null, null,
+                'Could not get column count', __FUNCTION__);
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal result pointer to the next available result
+     *
+     * @return true on success, false if there is no more result set or an error object on failure
+     * @access public
+     */
+    function nextResult()
+    {
+        $connection = $this->db->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        if (!@mysqli_more_results($connection)) {
+            return false;
+        }
+        if (!@mysqli_next_result($connection)) {
+            return false;
+        }
+        if (!($this->result = @mysqli_use_result($connection))) {
+            return false;
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Free the internal resources associated with result.
+     *
+     * @return boolean true on success, false if result is invalid
+     * @access public
+     */
+    function free()
+    {
+        do {
+            if (is_object($this->result) && $this->db->connection) {
+                $free = @mysqli_free_result($this->result);
+                if (false === $free) {
+                    return $this->db->raiseError(null, null, null,
+                        'Could not free result', __FUNCTION__);
+                }
+            }
+        } while ($this->result = $this->nextResult());
+
+        $this->result = false;
+        return MDB2_OK;
+    }
+}
+
+/**
+ * MDB2 MySQLi buffered result driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_BufferedResult_mysqli extends MDB2_Result_mysqli
+{
+    // }}}
+    // {{{ seek()
+
+    /**
+     * Seek to a specific row in a result set
+     *
+     * @param int    $rownum    number of the row where the data can be found
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function seek($rownum = 0)
+    {
+        if ($this->rownum != ($rownum - 1) && !@mysqli_data_seek($this->result, $rownum)) {
+            if (false === $this->result) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            }
+            if (null === $this->result) {
+                return MDB2_OK;
+            }
+            return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
+                'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
+        }
+        $this->rownum = $rownum - 1;
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ valid()
+
+    /**
+     * Check if the end of the result set has been reached
+     *
+     * @return mixed true or false on sucess, a MDB2 error on failure
+     * @access public
+     */
+    function valid()
+    {
+        $numrows = $this->numRows();
+        if (MDB2::isError($numrows)) {
+            return $numrows;
+        }
+        return $this->rownum < ($numrows - 1);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Returns the number of rows in a result object
+     *
+     * @return mixed MDB2 Error Object or the number of rows
+     * @access public
+     */
+    function numRows()
+    {
+        $rows = @mysqli_num_rows($this->result);
+        if (null === $rows) {
+            if (false === $this->result) {
+                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                    'resultset has already been freed', __FUNCTION__);
+            }
+            if (null === $this->result) {
+                return 0;
+            }
+            return $this->db->raiseError(null, null, null,
+                'Could not get row count', __FUNCTION__);
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal result pointer to the next available result
+     *
+     * @param a valid result resource
+     * @return true on success, false if there is no more result set or an error object on failure
+     * @access public
+     */
+    function nextResult()
+    {
+        $connection = $this->db->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        if (!@mysqli_more_results($connection)) {
+            return false;
+        }
+        if (!@mysqli_next_result($connection)) {
+            return false;
+        }
+        if (!($this->result = @mysqli_store_result($connection))) {
+            return false;
+        }
+        return MDB2_OK;
+    }
+}
+
+/**
+ * MDB2 MySQLi statement driver
+ *
+ * @package MDB2
+ * @category Database
+ * @author  Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Statement_mysqli extends MDB2_Statement_Common
+{
+    // {{{ _execute()
+
+    /**
+     * Execute a prepared query statement helper method.
+     *
+     * @param mixed $result_class string which specifies which result class to use
+     * @param mixed $result_wrap_class string which specifies which class to wrap results in
+     *
+     * @return mixed MDB2_Result or integer (affected rows) on success,
+     *               a MDB2 error on failure
+     * @access private
+     */
+    function _execute($result_class = true, $result_wrap_class = true)
+    {
+        if (null === $this->statement) {
+            $result = parent::_execute($result_class, $result_wrap_class);
+            return $result;
+        }
+        $this->db->last_query = $this->query;
+        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
+        if ($this->db->getOption('disable_query')) {
+            $result = $this->is_manip ? 0 : null;
+            return $result;
+        }
+
+        $connection = $this->db->getConnection();
+        if (MDB2::isError($connection)) {
+            return $connection;
+        }
+
+        if (!is_object($this->statement)) {
+            $query = 'EXECUTE '.$this->statement;
+        }
+        if (!empty($this->positions)) {
+            $paramReferences = array();
+            $parameters = array(0 => $this->statement, 1 => '');
+            $lobs = array();
+            $i = 0;
+            foreach ($this->positions as $parameter) {
+                if (!array_key_exists($parameter, $this->values)) {
+                    return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
+                        'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
+                }
+                $value = $this->values[$parameter];
+                $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
+                if (!is_object($this->statement)) {
+                    if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) {
+                        if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
+                            if ($match[1] == 'file://') {
+                                $value = $match[2];
+                            }
+                            $value = @fopen($value, 'r');
+                            $close = true;
+                        }
+                        if (is_resource($value)) {
+                            $data = '';
+                            while (!@feof($value)) {
+                                $data.= @fread($value, $this->db->options['lob_buffer_length']);
+                            }
+                            if ($close) {
+                                @fclose($value);
+                            }
+                            $value = $data;
+                        }
+                    }
+                    $quoted = $this->db->quote($value, $type);
+                    if (MDB2::isError($quoted)) {
+                        return $quoted;
+                    }
+                    $param_query = 'SET @'.$parameter.' = '.$quoted;
+                    $result = $this->db->_doQuery($param_query, true, $connection);
+                    if (MDB2::isError($result)) {
+                        return $result;
+                    }
+                } else {
+                    if (is_resource($value) || $type == 'clob' || $type == 'blob') {
+                        $paramReferences[$i] = null;
+                        // mysqli_stmt_bind_param() requires parameters to be passed by reference
+                        $parameters[] =& $paramReferences[$i];
+                        $parameters[1].= 'b';
+                        $lobs[$i] = $parameter;
+                    } else {
+                        $paramReferences[$i] = $this->db->quote($value, $type, false);
+                        if (MDB2::isError($paramReferences[$i])) {
+                            return $paramReferences[$i];
+                        }
+                        // mysqli_stmt_bind_param() requires parameters to be passed by reference
+                        $parameters[] =& $paramReferences[$i];
+                        $parameters[1].= $this->db->datatype->mapPrepareDatatype($type);
+                    }
+                    ++$i;
+                }
+            }
+
+            if (!is_object($this->statement)) {
+                $query.= ' USING @'.implode(', @', array_values($this->positions));
+            } else {
+                $result = call_user_func_array('mysqli_stmt_bind_param', $parameters);
+                if (false === $result) {
+                    $err = $this->db->raiseError(null, null, null,
+                        'Unable to bind parameters', __FUNCTION__);
+                    return $err;
+                }
+
+                foreach ($lobs as $i => $parameter) {
+                    $value = $this->values[$parameter];
+                    $close = false;
+                    if (!is_resource($value)) {
+                        $close = true;
+                        if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
+                            if ($match[1] == 'file://') {
+                                $value = $match[2];
+                            }
+                            $value = @fopen($value, 'r');
+                        } else {
+                            $fp = @tmpfile();
+                            @fwrite($fp, $value);
+                            @rewind($fp);
+                            $value = $fp;
+                        }
+                    }
+                    while (!@feof($value)) {
+                        $data = @fread($value, $this->db->options['lob_buffer_length']);
+                        @mysqli_stmt_send_long_data($this->statement, $i, $data);
+                    }
+                    if ($close) {
+                        @fclose($value);
+                    }
+                }
+            }
+        }
+
+        if (!is_object($this->statement)) {
+            $result = $this->db->_doQuery($query, $this->is_manip, $connection);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+
+            if ($this->is_manip) {
+                $affected_rows = $this->db->_affectedRows($connection, $result);
+                return $affected_rows;
+            }
+
+            $result = $this->db->_wrapResult($result, $this->result_types,
+                $result_class, $result_wrap_class, $this->limit, $this->offset);
+        } else {
+            if (!mysqli_stmt_execute($this->statement)) {
+                $err = $this->db->raiseError(null, null, null,
+                    'Unable to execute statement', __FUNCTION__);
+                return $err;
+            }
+
+            if ($this->is_manip) {
+                $affected_rows = @mysqli_stmt_affected_rows($this->statement);
+                return $affected_rows;
+            }
+
+            if ($this->db->options['result_buffering']) {
+                @mysqli_stmt_store_result($this->statement);
+            }
+
+            $result = $this->db->_wrapResult($this->statement, $this->result_types,
+                $result_class, $result_wrap_class, $this->limit, $this->offset);
+        }
+
+        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
+        return $result;
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Release resources allocated for the specified prepared query.
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function free()
+    {
+        if (null === $this->positions) {
+            return $this->db->raiseError(MDB2_ERROR, null, null,
+                'Prepared statement has already been freed', __FUNCTION__);
+        }
+        $result = MDB2_OK;
+
+        if (is_object($this->statement)) {
+            if (!@mysqli_stmt_close($this->statement)) {
+                $result = $this->db->raiseError(null, null, null,
+                    'Could not free statement', __FUNCTION__);
+            }
+        } elseif (null !== $this->statement) {
+            $connection = $this->db->getConnection();
+            if (MDB2::isError($connection)) {
+                return $connection;
+            }
+
+            $query = 'DEALLOCATE PREPARE '.$this->statement;
+            $result = $this->db->_doQuery($query, true, $connection);
+        }
+
+        parent::free();
+        return $result;
+   }
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Extended.php b/WEB-INF/lib/pear/MDB2/Extended.php
new file mode 100644 (file)
index 0000000..ed47ab9
--- /dev/null
@@ -0,0 +1,723 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Extended.php 327310 2012-08-27 15:16:18Z danielc $
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+
+/**
+ * Used by autoPrepare()
+ */
+define('MDB2_AUTOQUERY_INSERT', 1);
+define('MDB2_AUTOQUERY_UPDATE', 2);
+define('MDB2_AUTOQUERY_DELETE', 3);
+define('MDB2_AUTOQUERY_SELECT', 4);
+
+/**
+ * MDB2_Extended: class which adds several high level methods to MDB2
+ *
+ * @package MDB2
+ * @category Database
+ * @author Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Extended extends MDB2_Module_Common
+{
+    // {{{ autoPrepare()
+
+    /**
+     * Generate an insert, update or delete query and call prepare() on it
+     *
+     * @param string table
+     * @param array the fields names
+     * @param int type of query to build
+     *                          MDB2_AUTOQUERY_INSERT
+     *                          MDB2_AUTOQUERY_UPDATE
+     *                          MDB2_AUTOQUERY_DELETE
+     *                          MDB2_AUTOQUERY_SELECT
+     * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement)
+     * @param array that contains the types of the placeholders
+     * @param mixed array that contains the types of the columns in
+     *                        the result set or MDB2_PREPARE_RESULT, if set to
+     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
+     *
+     * @return resource handle for the query
+     * @see buildManipSQL
+     * @access public
+     */
+    function autoPrepare($table, $table_fields, $mode = MDB2_AUTOQUERY_INSERT,
+        $where = false, $types = null, $result_types = MDB2_PREPARE_MANIP)
+    {
+        $query = $this->buildManipSQL($table, $table_fields, $mode, $where);
+        if (MDB2::isError($query)) {
+            return $query;
+        }
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+        $lobs = array();
+        foreach ((array)$types as $param => $type) {
+            if (($type == 'clob') || ($type == 'blob')) {
+                $lobs[$param] = $table_fields[$param];
+            }
+        }
+        return $db->prepare($query, $types, $result_types, $lobs);
+    }
+
+    // }}}
+    // {{{ autoExecute()
+
+    /**
+     * Generate an insert, update or delete query and call prepare() and execute() on it
+     *
+     * @param string name of the table
+     * @param array assoc ($key=>$value) where $key is a field name and $value its value
+     * @param int type of query to build
+     *                          MDB2_AUTOQUERY_INSERT
+     *                          MDB2_AUTOQUERY_UPDATE
+     *                          MDB2_AUTOQUERY_DELETE
+     *                          MDB2_AUTOQUERY_SELECT
+     * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement)
+     * @param array that contains the types of the placeholders
+     * @param string which specifies which result class to use
+     * @param mixed  array that contains the types of the columns in
+     *                        the result set or MDB2_PREPARE_RESULT, if set to
+     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
+     *
+     * @return bool|MDB2_Error true on success, a MDB2 error on failure
+     * @see buildManipSQL
+     * @see autoPrepare
+     * @access public
+    */
+    function autoExecute($table, $fields_values, $mode = MDB2_AUTOQUERY_INSERT,
+        $where = false, $types = null, $result_class = true, $result_types = MDB2_PREPARE_MANIP)
+    {
+        $fields_values = (array)$fields_values;
+        if ($mode == MDB2_AUTOQUERY_SELECT) {
+            if (is_array($result_types)) {
+                $keys = array_keys($result_types);
+            } elseif (!empty($fields_values)) {
+                $keys = $fields_values;
+            } else {
+                $keys = array();
+            }
+        } else {
+            $keys = array_keys($fields_values);
+        }
+        $params = array_values($fields_values);
+        if (empty($params)) {
+            $query = $this->buildManipSQL($table, $keys, $mode, $where);
+
+            $db = $this->getDBInstance();
+            if (MDB2::isError($db)) {
+                return $db;
+            }
+            if ($mode == MDB2_AUTOQUERY_SELECT) {
+                $result = $db->query($query, $result_types, $result_class);
+            } else {
+                $result = $db->exec($query);
+            }
+        } else {
+            $stmt = $this->autoPrepare($table, $keys, $mode, $where, $types, $result_types);
+            if (MDB2::isError($stmt)) {
+                return $stmt;
+            }
+            $result = $stmt->execute($params, $result_class);
+            $stmt->free();
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ buildManipSQL()
+
+    /**
+     * Make automaticaly an sql query for prepare()
+     *
+     * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), MDB2_AUTOQUERY_INSERT)
+     *           will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?)
+     * NB : - This belongs more to a SQL Builder class, but this is a simple facility
+     *      - Be carefull ! If you don't give a $where param with an UPDATE/DELETE query, all
+     *        the records of the table will be updated/deleted !
+     *
+     * @param string name of the table
+     * @param ordered array containing the fields names
+     * @param int type of query to build
+     *                          MDB2_AUTOQUERY_INSERT
+     *                          MDB2_AUTOQUERY_UPDATE
+     *                          MDB2_AUTOQUERY_DELETE
+     *                          MDB2_AUTOQUERY_SELECT
+     * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement)
+     *
+     * @return string sql query for prepare()
+     * @access public
+     */
+    function buildManipSQL($table, $table_fields, $mode, $where = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if ($db->options['quote_identifier']) {
+            $table = $db->quoteIdentifier($table);
+        }
+
+        if (!empty($table_fields) && $db->options['quote_identifier']) {
+            foreach ($table_fields as $key => $field) {
+                $table_fields[$key] = $db->quoteIdentifier($field);
+            }
+        }
+
+        if ((false !== $where) && (null !== $where)) {
+            if (is_array($where)) {
+                $where = implode(' AND ', $where);
+            }
+            $where = ' WHERE '.$where;
+        }
+
+        switch ($mode) {
+        case MDB2_AUTOQUERY_INSERT:
+            if (empty($table_fields)) {
+                return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'Insert requires table fields', __FUNCTION__);
+            }
+            $cols = implode(', ', $table_fields);
+            $values = '?'.str_repeat(', ?', (count($table_fields) - 1));
+            return 'INSERT INTO '.$table.' ('.$cols.') VALUES ('.$values.')';
+            break;
+        case MDB2_AUTOQUERY_UPDATE:
+            if (empty($table_fields)) {
+                return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
+                'Update requires table fields', __FUNCTION__);
+            }
+            $set = implode(' = ?, ', $table_fields).' = ?';
+            $sql = 'UPDATE '.$table.' SET '.$set.$where;
+            return $sql;
+            break;
+        case MDB2_AUTOQUERY_DELETE:
+            $sql = 'DELETE FROM '.$table.$where;
+            return $sql;
+            break;
+        case MDB2_AUTOQUERY_SELECT:
+            $cols = !empty($table_fields) ? implode(', ', $table_fields) : '*';
+            $sql = 'SELECT '.$cols.' FROM '.$table.$where;
+            return $sql;
+            break;
+        }
+        return $db->raiseError(MDB2_ERROR_SYNTAX, null, null,
+                'Non existant mode', __FUNCTION__);
+    }
+
+    // }}}
+    // {{{ limitQuery()
+
+    /**
+     * Generates a limited query
+     *
+     * @param string query
+     * @param array that contains the types of the columns in the result set
+     * @param integer the numbers of rows to fetch
+     * @param integer the row to start to fetching
+     * @param string which specifies which result class to use
+     * @param mixed   string which specifies which class to wrap results in
+     *
+     * @return MDB2_Result|MDB2_Error result set on success, a MDB2 error on failure
+     * @access public
+     */
+    function limitQuery($query, $types, $limit, $offset = 0, $result_class = true,
+        $result_wrap_class = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        $result = $db->setLimit($limit, $offset);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+        return $db->query($query, $types, $result_class, $result_wrap_class);
+    }
+
+    // }}}
+    // {{{ execParam()
+
+    /**
+     * Execute a parameterized DML statement.
+     *
+     * @param string the SQL query
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     *
+     * @return int|MDB2_Error affected rows on success, a MDB2 error on failure
+     * @access public
+     */
+    function execParam($query, $params = array(), $param_types = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        if (empty($params)) {
+            return $db->exec($query);
+        }
+
+        $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (MDB2::isError($result)) {
+            return $result;
+        }
+
+        $stmt->free();
+        return $result;
+    }
+
+    // }}}
+    // {{{ getOne()
+
+    /**
+     * Fetch the first column of the first row of data returned from a query.
+     * Takes care of doing the query and freeing the results when finished.
+     *
+     * @param string the SQL query
+     * @param string that contains the type of the column in the result set
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     * @param int|string which column to return
+     *
+     * @return scalar|MDB2_Error data on success, a MDB2 error on failure
+     * @access public
+     */
+    function getOne($query, $type = null, $params = array(),
+        $param_types = null, $colnum = 0)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        settype($type, 'array');
+        if (empty($params)) {
+            return $db->queryOne($query, $type, $colnum);
+        }
+
+        $stmt = $db->prepare($query, $param_types, $type);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $one = $result->fetchOne($colnum);
+        $stmt->free();
+        $result->free();
+        return $one;
+    }
+
+    // }}}
+    // {{{ getRow()
+
+    /**
+     * Fetch the first row of data returned from a query.  Takes care
+     * of doing the query and freeing the results when finished.
+     *
+     * @param string the SQL query
+     * @param array that contains the types of the columns in the result set
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     * @param int the fetch mode to use
+     *
+     * @return array|MDB2_Error data on success, a MDB2 error on failure
+     * @access public
+     */
+    function getRow($query, $types = null, $params = array(),
+        $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        if (empty($params)) {
+            return $db->queryRow($query, $types, $fetchmode);
+        }
+
+        $stmt = $db->prepare($query, $param_types, $types);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $row = $result->fetchRow($fetchmode);
+        $stmt->free();
+        $result->free();
+        return $row;
+    }
+
+    // }}}
+    // {{{ getCol()
+
+    /**
+     * Fetch a single column from a result set and return it as an
+     * indexed array.
+     *
+     * @param string the SQL query
+     * @param string that contains the type of the column in the result set
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     * @param int|string which column to return
+     *
+     * @return array|MDB2_Error data on success, a MDB2 error on failure
+     * @access public
+     */
+    function getCol($query, $type = null, $params = array(),
+        $param_types = null, $colnum = 0)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        settype($type, 'array');
+        if (empty($params)) {
+            return $db->queryCol($query, $type, $colnum);
+        }
+
+        $stmt = $db->prepare($query, $param_types, $type);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $col = $result->fetchCol($colnum);
+        $stmt->free();
+        $result->free();
+        return $col;
+    }
+
+    // }}}
+    // {{{ getAll()
+
+    /**
+     * Fetch all the rows returned from a query.
+     *
+     * @param string the SQL query
+     * @param array that contains the types of the columns in the result set
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     * @param int the fetch mode to use
+     * @param bool if set to true, the $all will have the first
+     *       column as its first dimension
+     * @param bool $force_array used only when the query returns exactly
+     *       two columns. If true, the values of the returned array will be
+     *       one-element arrays instead of scalars.
+     * @param bool $group if true, the values of the returned array is
+     *       wrapped in another array.  If the same key value (in the first
+     *       column) repeats itself, the values will be appended to this array
+     *       instead of overwriting the existing values.
+     *
+     * @return array|MDB2_Error data on success, a MDB2 error on failure
+     * @access public
+     */
+    function getAll($query, $types = null, $params = array(),
+        $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT,
+        $rekey = false, $force_array = false, $group = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        if (empty($params)) {
+            return $db->queryAll($query, $types, $fetchmode, $rekey, $force_array, $group);
+        }
+
+        $stmt = $db->prepare($query, $param_types, $types);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group);
+        $stmt->free();
+        $result->free();
+        return $all;
+    }
+
+    // }}}
+    // {{{ getAssoc()
+
+    /**
+     * Fetch the entire result set of a query and return it as an
+     * associative array using the first column as the key.
+     *
+     * If the result set contains more than two columns, the value
+     * will be an array of the values from column 2-n.  If the result
+     * set contains only two columns, the returned value will be a
+     * scalar with the value of the second column (unless forced to an
+     * array with the $force_array parameter).  A MDB2 error code is
+     * returned on errors.  If the result set contains fewer than two
+     * columns, a MDB2_ERROR_TRUNCATED error is returned.
+     *
+     * For example, if the table 'mytable' contains:
+     * <pre>
+     *   ID      TEXT       DATE
+     * --------------------------------
+     *   1       'one'      944679408
+     *   2       'two'      944679408
+     *   3       'three'    944679408
+     * </pre>
+     * Then the call getAssoc('SELECT id,text FROM mytable') returns:
+     * <pre>
+     *    array(
+     *      '1' => 'one',
+     *      '2' => 'two',
+     *      '3' => 'three',
+     *    )
+     * </pre>
+     * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns:
+     * <pre>
+     *    array(
+     *      '1' => array('one', '944679408'),
+     *      '2' => array('two', '944679408'),
+     *      '3' => array('three', '944679408')
+     *    )
+     * </pre>
+     *
+     * If the more than one row occurs with the same value in the
+     * first column, the last row overwrites all previous ones by
+     * default.  Use the $group parameter if you don't want to
+     * overwrite like this.  Example:
+     * <pre>
+     * getAssoc('SELECT category,id,name FROM mytable', null, null
+     *           MDB2_FETCHMODE_ASSOC, false, true) returns:
+     *    array(
+     *      '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             ),
+     *      '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             )
+     *    )
+     * </pre>
+     *
+     * Keep in mind that database functions in PHP usually return string
+     * values for results regardless of the database's internal type.
+     *
+     * @param string the SQL query
+     * @param array that contains the types of the columns in the result set
+     * @param array if supplied, prepare/execute will be used
+     *       with this array as execute parameters
+     * @param array that contains the types of the values defined in $params
+     * @param bool $force_array used only when the query returns
+     * exactly two columns.  If TRUE, the values of the returned array
+     * will be one-element arrays instead of scalars.
+     * @param bool $group if TRUE, the values of the returned array
+     *       is wrapped in another array.  If the same key value (in the first
+     *       column) repeats itself, the values will be appended to this array
+     *       instead of overwriting the existing values.
+     *
+     * @return array|MDB2_Error data on success, a MDB2 error on failure
+     * @access public
+     */
+    function getAssoc($query, $types = null, $params = array(), $param_types = null,
+        $fetchmode = MDB2_FETCHMODE_DEFAULT, $force_array = false, $group = false)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        settype($params, 'array');
+        if (empty($params)) {
+            return $db->queryAll($query, $types, $fetchmode, true, $force_array, $group);
+        }
+
+        $stmt = $db->prepare($query, $param_types, $types);
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+
+        $result = $stmt->execute($params);
+        if (!MDB2::isResultCommon($result)) {
+            return $result;
+        }
+
+        $all = $result->fetchAll($fetchmode, true, $force_array, $group);
+        $stmt->free();
+        $result->free();
+        return $all;
+    }
+
+    // }}}
+    // {{{ executeMultiple()
+
+    /**
+     * This function does several execute() calls on the same statement handle.
+     * $params must be an array indexed numerically from 0, one execute call is
+     * done for every 'row' in the array.
+     *
+     * If an error occurs during execute(), executeMultiple() does not execute
+     * the unfinished rows, but rather returns that error.
+     *
+     * @param resource query handle from prepare()
+     * @param array numeric array containing the data to insert into the query
+     *
+     * @return bool|MDB2_Error true on success, a MDB2 error on failure
+     * @access public
+     * @see prepare(), execute()
+     */
+    function executeMultiple($stmt, $params = null)
+    {
+        if (MDB2::isError($stmt)) {
+            return $stmt;
+        }
+        for ($i = 0, $j = count($params); $i < $j; $i++) {
+            $result = $stmt->execute($params[$i]);
+            if (MDB2::isError($result)) {
+                return $result;
+            }
+        }
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ getBeforeID()
+
+    /**
+     * Returns the next free id of a sequence if the RDBMS
+     * does not support auto increment
+     *
+     * @param string name of the table into which a new row was inserted
+     * @param string name of the field into which a new row was inserted
+     * @param bool when true the sequence is automatic created, if it not exists
+     * @param bool if the returned value should be quoted
+     *
+     * @return int|MDB2_Error id on success, a MDB2 error on failure
+     * @access public
+     */
+    function getBeforeID($table, $field = null, $ondemand = true, $quote = true)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if ($db->supports('auto_increment') !== true) {
+            $seq = $table.(empty($field) ? '' : '_'.$field);
+            $id = $db->nextID($seq, $ondemand);
+            if (!$quote || MDB2::isError($id)) {
+                return $id;
+            }
+            return $db->quote($id, 'integer');
+        } elseif (!$quote) {
+            return null;
+        }
+        return 'NULL';
+    }
+
+    // }}}
+    // {{{ getAfterID()
+
+    /**
+     * Returns the autoincrement ID if supported or $id
+     *
+     * @param mixed value as returned by getBeforeId()
+     * @param string name of the table into which a new row was inserted
+     * @param string name of the field into which a new row was inserted
+     *
+     * @return int|MDB2_Error id on success, a MDB2 error on failure
+     * @access public
+     */
+    function getAfterID($id, $table, $field = null)
+    {
+        $db = $this->getDBInstance();
+        if (MDB2::isError($db)) {
+            return $db;
+        }
+
+        if ($db->supports('auto_increment') !== true) {
+            return $id;
+        }
+        return $db->lastInsertID($table, $field);
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/MDB2/Iterator.php b/WEB-INF/lib/pear/MDB2/Iterator.php
new file mode 100644 (file)
index 0000000..17a3ac2
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP version 5                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Iterator.php 327310 2012-08-27 15:16:18Z danielc $
+
+/**
+ * PHP5 Iterator
+ *
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_Iterator implements Iterator
+{
+    protected $fetchmode;
+    /**
+     * @var MDB2_Result_Common
+     */
+    protected $result;
+    protected $row;
+
+    // {{{ constructor
+
+    /**
+     * Constructor
+     */
+    public function __construct(MDB2_Result_Common $result, $fetchmode = MDB2_FETCHMODE_DEFAULT)
+    {
+        $this->result = $result;
+        $this->fetchmode = $fetchmode;
+    }
+    // }}}
+
+    // {{{ seek()
+
+    /**
+     * Seek forward to a specific row in a result set
+     *
+     * @param int number of the row where the data can be found
+     *
+     * @return void
+     * @access public
+     */
+    public function seek($rownum)
+    {
+        $this->row = null;
+        if ($this->result) {
+            $this->result->seek($rownum);
+        }
+    }
+    // }}}
+
+    // {{{ next()
+
+    /**
+     * Fetch next row of data
+     *
+     * @return void
+     * @access public
+     */
+    public function next()
+    {
+        $this->row = null;
+    }
+    // }}}
+
+    // {{{ current()
+
+    /**
+     * return a row of data
+     *
+     * @return void
+     * @access public
+     */
+    public function current()
+    {
+        if (null === $this->row) {
+            $row = $this->result->fetchRow($this->fetchmode);
+            if (MDB2::isError($row)) {
+                $row = false;
+            }
+            $this->row = $row;
+        }
+        return $this->row;
+    }
+    // }}}
+
+    // {{{ valid()
+
+    /**
+     * Check if the end of the result set has been reached
+     *
+     * @return bool true/false, false is also returned on failure
+     * @access public
+     */
+    public function valid()
+    {
+        return (bool)$this->current();
+    }
+    // }}}
+
+    // {{{ free()
+
+    /**
+     * Free the internal resources associated with result.
+     *
+     * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid
+     * @access public
+     */
+    public function free()
+    {
+        if ($this->result) {
+            return $this->result->free();
+        }
+        $this->result = false;
+        $this->row = null;
+        return false;
+    }
+    // }}}
+
+    // {{{ key()
+
+    /**
+     * Returns the row number
+     *
+     * @return int|bool|MDB2_Error true on success, false|MDB2_Error if result is invalid
+     * @access public
+     */
+    public function key()
+    {
+        if ($this->result) {
+            return $this->result->rowCount();
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{ rewind()
+
+    /**
+     * Seek to the first row in a result set
+     *
+     * @return void
+     * @access public
+     */
+    public function rewind()
+    {
+    }
+    // }}}
+
+    // {{{ destructor
+
+    /**
+     * Destructor
+     */
+    public function __destruct()
+    {
+        $this->free();
+    }
+    // }}}
+}
+
+/**
+ * PHP5 buffered Iterator
+ *
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_BufferedIterator extends MDB2_Iterator implements SeekableIterator
+{
+    // {{{ valid()
+
+    /**
+     * Check if the end of the result set has been reached
+     *
+     * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid
+     * @access public
+     */
+    public function valid()
+    {
+        if ($this->result) {
+            return $this->result->valid();
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{count()
+
+    /**
+     * Returns the number of rows in a result object
+     *
+     * @return int|MDB2_Error number of rows, false|MDB2_Error if result is invalid
+     * @access public
+     */
+    public function count()
+    {
+        if ($this->result) {
+            return $this->result->numRows();
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{ rewind()
+
+    /**
+     * Seek to the first row in a result set
+     *
+     * @return void
+     * @access public
+     */
+    public function rewind()
+    {
+        $this->seek(0);
+    }
+    // }}}
+}
+
+?>
diff --git a/WEB-INF/lib/pear/MDB2/LOB.php b/WEB-INF/lib/pear/MDB2/LOB.php
new file mode 100644 (file)
index 0000000..ff2342d
--- /dev/null
@@ -0,0 +1,264 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PHP version 5                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Stig. S. Bakken, Lukas Smith                                         |
+// | All rights reserved.                                                 |
+// +----------------------------------------------------------------------+
+// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
+// | API as well as database abstraction for PHP applications.            |
+// | This LICENSE is in the BSD license style.                            |
+// |                                                                      |
+// | Redistribution and use in source and binary forms, with or without   |
+// | modification, are permitted provided that the following conditions   |
+// | are met:                                                             |
+// |                                                                      |
+// | Redistributions of source code must retain the above copyright       |
+// | notice, this list of conditions and the following disclaimer.        |
+// |                                                                      |
+// | Redistributions in binary form must reproduce the above copyright    |
+// | notice, this list of conditions and the following disclaimer in the  |
+// | documentation and/or other materials provided with the distribution. |
+// |                                                                      |
+// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
+// | Lukas Smith nor the names of his contributors may be used to endorse |
+// | or promote products derived from this software without specific prior|
+// | written permission.                                                  |
+// |                                                                      |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
+// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
+// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
+// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
+// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
+// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
+// | POSSIBILITY OF SUCH DAMAGE.                                          |
+// +----------------------------------------------------------------------+
+// | Author: Lukas Smith <smith@pooteeweet.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: LOB.php 222350 2006-10-25 11:52:21Z lsmith $
+
+/**
+ * @package  MDB2
+ * @category Database
+ * @author   Lukas Smith <smith@pooteeweet.org>
+ */
+
+require_once 'MDB2.php';
+
+/**
+ * MDB2_LOB: user land stream wrapper implementation for LOB support
+ *
+ * @package MDB2
+ * @category Database
+ * @author Lukas Smith <smith@pooteeweet.org>
+ */
+class MDB2_LOB
+{
+    /**
+     * contains the key to the global MDB2 instance array of the associated
+     * MDB2 instance
+     *
+     * @var integer
+     * @access protected
+     */
+    var $db_index;
+
+    /**
+     * contains the key to the global MDB2_LOB instance array of the associated
+     * MDB2_LOB instance
+     *
+     * @var integer
+     * @access protected
+     */
+    var $lob_index;
+
+    // {{{ stream_open()
+
+    /**
+     * open stream
+     *
+     * @param string specifies the URL that was passed to fopen()
+     * @param string the mode used to open the file
+     * @param int holds additional flags set by the streams API
+     * @param string not used
+     *
+     * @return bool
+     * @access public
+     */
+    function stream_open($path, $mode, $options, &$opened_path)
+    {
+        if (!preg_match('/^rb?\+?$/', $mode)) {
+            return false;
+        }
+        $url = parse_url($path);
+        if (empty($url['host'])) {
+            return false;
+        }
+        $this->db_index = (int)$url['host'];
+        if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            return false;
+        }
+        $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+        $this->lob_index = (int)$url['user'];
+        if (!isset($db->datatype->lobs[$this->lob_index])) {
+            return false;
+        }
+        return true;
+    }
+    // }}}
+
+    // {{{ stream_read()
+
+    /**
+     * read stream
+     *
+     * @param int number of bytes to read
+     *
+     * @return string
+     * @access public
+     */
+    function stream_read($count)
+    {
+        if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+            $db->datatype->_retrieveLOB($db->datatype->lobs[$this->lob_index]);
+
+            $data = $db->datatype->_readLOB($db->datatype->lobs[$this->lob_index], $count);
+            $length = strlen($data);
+            if ($length == 0) {
+                $db->datatype->lobs[$this->lob_index]['endOfLOB'] = true;
+            }
+            $db->datatype->lobs[$this->lob_index]['position'] += $length;
+            return $data;
+        }
+    }
+    // }}}
+
+    // {{{ stream_write()
+
+    /**
+     * write stream, note implemented
+     *
+     * @param string data
+     *
+     * @return int
+     * @access public
+     */
+    function stream_write($data)
+    {
+        return 0;
+    }
+    // }}}
+
+    // {{{ stream_tell()
+
+    /**
+     * return the current position
+     *
+     * @return int current position
+     * @access public
+     */
+    function stream_tell()
+    {
+        if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+            return $db->datatype->lobs[$this->lob_index]['position'];
+        }
+    }
+    // }}}
+
+    // {{{ stream_eof()
+
+    /**
+     * Check if stream reaches EOF
+     *
+     * @return bool
+     * @access public
+     */
+    function stream_eof()
+    {
+        if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            return true;
+        }
+
+        $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+        $result = $db->datatype->_endOfLOB($db->datatype->lobs[$this->lob_index]);
+        if (version_compare(phpversion(), "5.0", ">=")
+            && version_compare(phpversion(), "5.1", "<")
+        ) {
+            return !$result;
+        }
+        return $result;
+    }
+    // }}}
+
+    // {{{ stream_seek()
+
+    /**
+     * Seek stream, not implemented
+     *
+     * @param int offset
+     * @param int whence
+     *
+     * @return bool
+     * @access public
+     */
+    function stream_seek($offset, $whence)
+    {
+        return false;
+    }
+    // }}}
+
+    // {{{ stream_stat()
+
+    /**
+     * return information about stream
+     *
+     * @access public
+     */
+    function stream_stat()
+    {
+        if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+            return array(
+              'db_index' => $this->db_index,
+              'lob_index' => $this->lob_index,
+            );
+        }
+    }
+    // }}}
+
+    // {{{ stream_close()
+
+    /**
+     * close stream
+     *
+     * @access public
+     */
+    function stream_close()
+    {
+        if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
+            $db =& $GLOBALS['_MDB2_databases'][$this->db_index];
+            if (isset($db->datatype->lobs[$this->lob_index])) {
+                $db->datatype->_destroyLOB($db->datatype->lobs[$this->lob_index]);
+                unset($db->datatype->lobs[$this->lob_index]);
+            }
+        }
+    }
+    // }}}
+}
+
+// register streams wrapper
+if (!stream_wrapper_register("MDB2LOB", "MDB2_LOB")) {
+    MDB2::raiseError();
+    return false;
+}
+
+?>
diff --git a/WEB-INF/lib/pear/Mail.php b/WEB-INF/lib/pear/Mail.php
new file mode 100644 (file)
index 0000000..75132ac
--- /dev/null
@@ -0,0 +1,270 @@
+<?php
+/**
+ *  PEAR's Mail:: interface.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2002-2007, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck@horde.org>
+ * @copyright   1997-2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * PEAR's Mail:: interface. Defines the interface for implementing
+ * mailers under the PEAR hierarchy, and provides supporting functions
+ * useful in multiple mailer backends.
+ *
+ * @access public
+ * @version $Revision: 294747 $
+ * @package Mail
+ */
+class Mail
+{
+    /**
+     * Line terminator used for separating header lines.
+     * @var string
+     */
+    var $sep = "\r\n";
+
+    /**
+     * Provides an interface for generating Mail:: objects of various
+     * types
+     *
+     * @param string $driver The kind of Mail:: object to instantiate.
+     * @param array  $params The parameters to pass to the Mail:: object.
+     * @return object Mail a instance of the driver class or if fails a PEAR Error
+     * @access public
+     */
+    function &factory($driver, $params = array())
+    {
+        $driver = strtolower($driver);
+        @include_once 'Mail/' . $driver . '.php';
+        $class = 'Mail_' . $driver;
+        if (class_exists($class)) {
+            $mailer = new $class($params);
+            return $mailer;
+        } else {
+            return PEAR::raiseError('Unable to find class for driver ' . $driver);
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using php's built-in mail()
+     * command.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     *
+     * @access public
+     * @deprecated use Mail_mail::send instead
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // if we're passed an array of recipients, implode it.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // get the Subject out of the headers array so that we can
+        // pass it as a seperate argument to mail().
+        $subject = '';
+        if (isset($headers['Subject'])) {
+            $subject = $headers['Subject'];
+            unset($headers['Subject']);
+        }
+
+        // flatten the headers out.
+        list(, $text_headers) = Mail::prepareHeaders($headers);
+
+        return mail($recipients, $subject, $body, $text_headers);
+    }
+
+    /**
+     * Sanitize an array of mail headers by removing any additional header
+     * strings present in a legitimate header's value.  The goal of this
+     * filter is to prevent mail injection attacks.
+     *
+     * @param array $headers The associative array of headers to sanitize.
+     *
+     * @access private
+     */
+    function _sanitizeHeaders(&$headers)
+    {
+        foreach ($headers as $key => $value) {
+            $headers[$key] =
+                preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
+                             null, $value);
+        }
+    }
+
+    /**
+     * Take an array of mail headers and return a string containing
+     * text usable in sending a message.
+     *
+     * @param array $headers The array of headers to prepare, in an associative
+     *              array, where the array key is the header name (ie,
+     *              'Subject'), and the array value is the header
+     *              value (ie, 'test'). The header produced from those
+     *              values would be 'Subject: test'.
+     *
+     * @return mixed Returns false if it encounters a bad address,
+     *               otherwise returns an array containing two
+     *               elements: Any From: address found in the headers,
+     *               and the plain text version of the headers.
+     * @access private
+     */
+    function prepareHeaders($headers)
+    {
+        $lines = array();
+        $from = null;
+
+        foreach ($headers as $key => $value) {
+            if (strcasecmp($key, 'From') === 0) {
+                include_once 'Mail/RFC822.php';
+                $parser = new Mail_RFC822();
+                $addresses = $parser->parseAddressList($value, 'localhost', false);
+                if (is_a($addresses, 'PEAR_Error')) {
+                    return $addresses;
+                }
+
+                $from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
+
+                // Reject envelope From: addresses with spaces.
+                if (strstr($from, ' ')) {
+                    return false;
+                }
+
+                $lines[] = $key . ': ' . $value;
+            } elseif (strcasecmp($key, 'Received') === 0) {
+                $received = array();
+                if (is_array($value)) {
+                    foreach ($value as $line) {
+                        $received[] = $key . ': ' . $line;
+                    }
+                }
+                else {
+                    $received[] = $key . ': ' . $value;
+                }
+                // Put Received: headers at the top.  Spam detectors often
+                // flag messages with Received: headers after the Subject:
+                // as spam.
+                $lines = array_merge($received, $lines);
+            } else {
+                // If $value is an array (i.e., a list of addresses), convert
+                // it to a comma-delimited string of its elements (addresses).
+                if (is_array($value)) {
+                    $value = implode(', ', $value);
+                }
+                $lines[] = $key . ': ' . $value;
+            }
+        }
+
+        return array($from, join($this->sep, $lines));
+    }
+
+    /**
+     * Take a set of recipients and parse them, returning an array of
+     * bare addresses (forward paths) that can be passed to sendmail
+     * or an smtp server with the rcpt to: command.
+     *
+     * @param mixed Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid.
+     *
+     * @return mixed An array of forward paths (bare addresses) or a PEAR_Error
+     *               object if the address list could not be parsed.
+     * @access private
+     */
+    function parseRecipients($recipients)
+    {
+        include_once 'Mail/RFC822.php';
+
+        // if we're passed an array, assume addresses are valid and
+        // implode them before parsing.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // Parse recipients, leaving out all personal info. This is
+        // for smtp recipients, etc. All relevant personal information
+        // should already be in the headers.
+        $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false);
+
+        // If parseAddressList() returned a PEAR_Error object, just return it.
+        if (is_a($addresses, 'PEAR_Error')) {
+            return $addresses;
+        }
+
+        $recipients = array();
+        if (is_array($addresses)) {
+            foreach ($addresses as $ob) {
+                $recipients[] = $ob->mailbox . '@' . $ob->host;
+            }
+        }
+
+        return $recipients;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/RFC822.php b/WEB-INF/lib/pear/Mail/RFC822.php
new file mode 100644 (file)
index 0000000..58d3646
--- /dev/null
@@ -0,0 +1,951 @@
+<?php
+/**
+ * RFC 822 Email address list validation Utility
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2001-2010, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Richard Heyes <richard@phpguru.org>
+ * @author      Chuck Hagenbuch <chuck@horde.org
+ * @copyright   2001-2010 Richard Heyes
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: RFC822.php 294749 2010-02-08 08:22:25Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * RFC 822 Email address list validation Utility
+ *
+ * What is it?
+ *
+ * This class will take an address string, and parse it into it's consituent
+ * parts, be that either addresses, groups, or combinations. Nested groups
+ * are not supported. The structure it returns is pretty straight forward,
+ * and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
+ * print_r() to view the structure.
+ *
+ * How do I use it?
+ *
+ * $address_string = 'My Group: "Richard" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
+ * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
+ * print_r($structure);
+ *
+ * @author  Richard Heyes <richard@phpguru.org>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @version $Revision: 294749 $
+ * @license BSD
+ * @package Mail
+ */
+class Mail_RFC822 {
+
+    /**
+     * The address being parsed by the RFC822 object.
+     * @var string $address
+     */
+    var $address = '';
+
+    /**
+     * The default domain to use for unqualified addresses.
+     * @var string $default_domain
+     */
+    var $default_domain = 'localhost';
+
+    /**
+     * Should we return a nested array showing groups, or flatten everything?
+     * @var boolean $nestGroups
+     */
+    var $nestGroups = true;
+
+    /**
+     * Whether or not to validate atoms for non-ascii characters.
+     * @var boolean $validate
+     */
+    var $validate = true;
+
+    /**
+     * The array of raw addresses built up as we parse.
+     * @var array $addresses
+     */
+    var $addresses = array();
+
+    /**
+     * The final array of parsed address information that we build up.
+     * @var array $structure
+     */
+    var $structure = array();
+
+    /**
+     * The current error message, if any.
+     * @var string $error
+     */
+    var $error = null;
+
+    /**
+     * An internal counter/pointer.
+     * @var integer $index
+     */
+    var $index = null;
+
+    /**
+     * The number of groups that have been found in the address list.
+     * @var integer $num_groups
+     * @access public
+     */
+    var $num_groups = 0;
+
+    /**
+     * A variable so that we can tell whether or not we're inside a
+     * Mail_RFC822 object.
+     * @var boolean $mailRFC822
+     */
+    var $mailRFC822 = true;
+
+    /**
+    * A limit after which processing stops
+    * @var int $limit
+    */
+    var $limit = null;
+
+    /**
+     * Sets up the object. The address must either be set here or when
+     * calling parseAddressList(). One or the other.
+     *
+     * @access public
+     * @param string  $address         The address(es) to validate.
+     * @param string  $default_domain  Default domain/host etc. If not supplied, will be set to localhost.
+     * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
+     * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+     *
+     * @return object Mail_RFC822 A new Mail_RFC822 object.
+     */
+    function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+    {
+        if (isset($address))        $this->address        = $address;
+        if (isset($default_domain)) $this->default_domain = $default_domain;
+        if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
+        if (isset($validate))       $this->validate       = $validate;
+        if (isset($limit))          $this->limit          = $limit;
+    }
+
+    /**
+     * Starts the whole process. The address must either be set here
+     * or when creating the object. One or the other.
+     *
+     * @access public
+     * @param string  $address         The address(es) to validate.
+     * @param string  $default_domain  Default domain/host etc.
+     * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
+     * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+     *
+     * @return array A structured array of addresses.
+     */
+    function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+    {
+        if (!isset($this) || !isset($this->mailRFC822)) {
+            $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
+            return $obj->parseAddressList();
+        }
+
+        if (isset($address))        $this->address        = $address;
+        if (isset($default_domain)) $this->default_domain = $default_domain;
+        if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
+        if (isset($validate))       $this->validate       = $validate;
+        if (isset($limit))          $this->limit          = $limit;
+
+        $this->structure  = array();
+        $this->addresses  = array();
+        $this->error      = null;
+        $this->index      = null;
+
+        // Unfold any long lines in $this->address.
+        $this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
+        $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
+
+        while ($this->address = $this->_splitAddresses($this->address));
+
+        if ($this->address === false || isset($this->error)) {
+            require_once 'PEAR.php';
+            return PEAR::raiseError($this->error);
+        }
+
+        // Validate each address individually.  If we encounter an invalid
+        // address, stop iterating and return an error immediately.
+        foreach ($this->addresses as $address) {
+            $valid = $this->_validateAddress($address);
+
+            if ($valid === false || isset($this->error)) {
+                require_once 'PEAR.php';
+                return PEAR::raiseError($this->error);
+            }
+
+            if (!$this->nestGroups) {
+                $this->structure = array_merge($this->structure, $valid);
+            } else {
+                $this->structure[] = $valid;
+            }
+        }
+
+        return $this->structure;
+    }
+
+    /**
+     * Splits an address into separate addresses.
+     *
+     * @access private
+     * @param string $address The addresses to split.
+     * @return boolean Success or failure.
+     */
+    function _splitAddresses($address)
+    {
+        if (!empty($this->limit) && count($this->addresses) == $this->limit) {
+            return '';
+        }
+
+        if ($this->_isGroup($address) && !isset($this->error)) {
+            $split_char = ';';
+            $is_group   = true;
+        } elseif (!isset($this->error)) {
+            $split_char = ',';
+            $is_group   = false;
+        } elseif (isset($this->error)) {
+            return false;
+        }
+
+        // Split the string based on the above ten or so lines.
+        $parts  = explode($split_char, $address);
+        $string = $this->_splitCheck($parts, $split_char);
+
+        // If a group...
+        if ($is_group) {
+            // If $string does not contain a colon outside of
+            // brackets/quotes etc then something's fubar.
+
+            // First check there's a colon at all:
+            if (strpos($string, ':') === false) {
+                $this->error = 'Invalid address: ' . $string;
+                return false;
+            }
+
+            // Now check it's outside of brackets/quotes:
+            if (!$this->_splitCheck(explode(':', $string), ':')) {
+                return false;
+            }
+
+            // We must have a group at this point, so increase the counter:
+            $this->num_groups++;
+        }
+
+        // $string now contains the first full address/group.
+        // Add to the addresses array.
+        $this->addresses[] = array(
+                                   'address' => trim($string),
+                                   'group'   => $is_group
+                                   );
+
+        // Remove the now stored address from the initial line, the +1
+        // is to account for the explode character.
+        $address = trim(substr($address, strlen($string) + 1));
+
+        // If the next char is a comma and this was a group, then
+        // there are more addresses, otherwise, if there are any more
+        // chars, then there is another address.
+        if ($is_group && substr($address, 0, 1) == ','){
+            $address = trim(substr($address, 1));
+            return $address;
+
+        } elseif (strlen($address) > 0) {
+            return $address;
+
+        } else {
+            return '';
+        }
+
+        // If you got here then something's off
+        return false;
+    }
+
+    /**
+     * Checks for a group at the start of the string.
+     *
+     * @access private
+     * @param string $address The address to check.
+     * @return boolean Whether or not there is a group at the start of the string.
+     */
+    function _isGroup($address)
+    {
+        // First comma not in quotes, angles or escaped:
+        $parts  = explode(',', $address);
+        $string = $this->_splitCheck($parts, ',');
+
+        // Now we have the first address, we can reliably check for a
+        // group by searching for a colon that's not escaped or in
+        // quotes or angle brackets.
+        if (count($parts = explode(':', $string)) > 1) {
+            $string2 = $this->_splitCheck($parts, ':');
+            return ($string2 !== $string);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * A common function that will check an exploded string.
+     *
+     * @access private
+     * @param array $parts The exloded string.
+     * @param string $char  The char that was exploded on.
+     * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
+     */
+    function _splitCheck($parts, $char)
+    {
+        $string = $parts[0];
+
+        for ($i = 0; $i < count($parts); $i++) {
+            if ($this->_hasUnclosedQuotes($string)
+                || $this->_hasUnclosedBrackets($string, '<>')
+                || $this->_hasUnclosedBrackets($string, '[]')
+                || $this->_hasUnclosedBrackets($string, '()')
+                || substr($string, -1) == '\\') {
+                if (isset($parts[$i + 1])) {
+                    $string = $string . $char . $parts[$i + 1];
+                } else {
+                    $this->error = 'Invalid address spec. Unclosed bracket or quotes';
+                    return false;
+                }
+            } else {
+                $this->index = $i;
+                break;
+            }
+        }
+
+        return $string;
+    }
+
+    /**
+     * Checks if a string has unclosed quotes or not.
+     *
+     * @access private
+     * @param string $string  The string to check.
+     * @return boolean  True if there are unclosed quotes inside the string,
+     *                  false otherwise.
+     */
+    function _hasUnclosedQuotes($string)
+    {
+        $string = trim($string);
+        $iMax = strlen($string);
+        $in_quote = false;
+        $i = $slashes = 0;
+
+        for (; $i < $iMax; ++$i) {
+            switch ($string[$i]) {
+            case '\\':
+                ++$slashes;
+                break;
+
+            case '"':
+                if ($slashes % 2 == 0) {
+                    $in_quote = !$in_quote;
+                }
+                // Fall through to default action below.
+
+            default:
+                $slashes = 0;
+                break;
+            }
+        }
+
+        return $in_quote;
+    }
+
+    /**
+     * Checks if a string has an unclosed brackets or not. IMPORTANT:
+     * This function handles both angle brackets and square brackets;
+     *
+     * @access private
+     * @param string $string The string to check.
+     * @param string $chars  The characters to check for.
+     * @return boolean True if there are unclosed brackets inside the string, false otherwise.
+     */
+    function _hasUnclosedBrackets($string, $chars)
+    {
+        $num_angle_start = substr_count($string, $chars[0]);
+        $num_angle_end   = substr_count($string, $chars[1]);
+
+        $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
+        $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
+
+        if ($num_angle_start < $num_angle_end) {
+            $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
+            return false;
+        } else {
+            return ($num_angle_start > $num_angle_end);
+        }
+    }
+
+    /**
+     * Sub function that is used only by hasUnclosedBrackets().
+     *
+     * @access private
+     * @param string $string The string to check.
+     * @param integer &$num    The number of occurences.
+     * @param string $char   The character to count.
+     * @return integer The number of occurences of $char in $string, adjusted for backslashes.
+     */
+    function _hasUnclosedBracketsSub($string, &$num, $char)
+    {
+        $parts = explode($char, $string);
+        for ($i = 0; $i < count($parts); $i++){
+            if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
+                $num--;
+            if (isset($parts[$i + 1]))
+                $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
+        }
+
+        return $num;
+    }
+
+    /**
+     * Function to begin checking the address.
+     *
+     * @access private
+     * @param string $address The address to validate.
+     * @return mixed False on failure, or a structured array of address information on success.
+     */
+    function _validateAddress($address)
+    {
+        $is_group = false;
+        $addresses = array();
+
+        if ($address['group']) {
+            $is_group = true;
+
+            // Get the group part of the name
+            $parts     = explode(':', $address['address']);
+            $groupname = $this->_splitCheck($parts, ':');
+            $structure = array();
+
+            // And validate the group part of the name.
+            if (!$this->_validatePhrase($groupname)){
+                $this->error = 'Group name did not validate.';
+                return false;
+            } else {
+                // Don't include groups if we are not nesting
+                // them. This avoids returning invalid addresses.
+                if ($this->nestGroups) {
+                    $structure = new stdClass;
+                    $structure->groupname = $groupname;
+                }
+            }
+
+            $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
+        }
+
+        // If a group then split on comma and put into an array.
+        // Otherwise, Just put the whole address in an array.
+        if ($is_group) {
+            while (strlen($address['address']) > 0) {
+                $parts       = explode(',', $address['address']);
+                $addresses[] = $this->_splitCheck($parts, ',');
+                $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
+            }
+        } else {
+            $addresses[] = $address['address'];
+        }
+
+        // Check that $addresses is set, if address like this:
+        // Groupname:;
+        // Then errors were appearing.
+        if (!count($addresses)){
+            $this->error = 'Empty group.';
+            return false;
+        }
+
+        // Trim the whitespace from all of the address strings.
+        array_map('trim', $addresses);
+
+        // Validate each mailbox.
+        // Format could be one of: name <geezer@domain.com>
+        //                         geezer@domain.com
+        //                         geezer
+        // ... or any other format valid by RFC 822.
+        for ($i = 0; $i < count($addresses); $i++) {
+            if (!$this->validateMailbox($addresses[$i])) {
+                if (empty($this->error)) {
+                    $this->error = 'Validation failed for: ' . $addresses[$i];
+                }
+                return false;
+            }
+        }
+
+        // Nested format
+        if ($this->nestGroups) {
+            if ($is_group) {
+                $structure->addresses = $addresses;
+            } else {
+                $structure = $addresses[0];
+            }
+
+        // Flat format
+        } else {
+            if ($is_group) {
+                $structure = array_merge($structure, $addresses);
+            } else {
+                $structure = $addresses;
+            }
+        }
+
+        return $structure;
+    }
+
+    /**
+     * Function to validate a phrase.
+     *
+     * @access private
+     * @param string $phrase The phrase to check.
+     * @return boolean Success or failure.
+     */
+    function _validatePhrase($phrase)
+    {
+        // Splits on one or more Tab or space.
+        $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
+
+        $phrase_parts = array();
+        while (count($parts) > 0){
+            $phrase_parts[] = $this->_splitCheck($parts, ' ');
+            for ($i = 0; $i < $this->index + 1; $i++)
+                array_shift($parts);
+        }
+
+        foreach ($phrase_parts as $part) {
+            // If quoted string:
+            if (substr($part, 0, 1) == '"') {
+                if (!$this->_validateQuotedString($part)) {
+                    return false;
+                }
+                continue;
+            }
+
+            // Otherwise it's an atom:
+            if (!$this->_validateAtom($part)) return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Function to validate an atom which from rfc822 is:
+     * atom = 1*<any CHAR except specials, SPACE and CTLs>
+     *
+     * If validation ($this->validate) has been turned off, then
+     * validateAtom() doesn't actually check anything. This is so that you
+     * can split a list of addresses up before encoding personal names
+     * (umlauts, etc.), for example.
+     *
+     * @access private
+     * @param string $atom The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateAtom($atom)
+    {
+        if (!$this->validate) {
+            // Validation has been turned off; assume the atom is okay.
+            return true;
+        }
+
+        // Check for any char from ASCII 0 - ASCII 127
+        if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
+            return false;
+        }
+
+        // Check for specials:
+        if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
+            return false;
+        }
+
+        // Check for control characters (ASCII 0-31):
+        if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Function to validate quoted string, which is:
+     * quoted-string = <"> *(qtext/quoted-pair) <">
+     *
+     * @access private
+     * @param string $qstring The string to check
+     * @return boolean Success or failure.
+     */
+    function _validateQuotedString($qstring)
+    {
+        // Leading and trailing "
+        $qstring = substr($qstring, 1, -1);
+
+        // Perform check, removing quoted characters first.
+        return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
+    }
+
+    /**
+     * Function to validate a mailbox, which is:
+     * mailbox =   addr-spec         ; simple address
+     *           / phrase route-addr ; name and route-addr
+     *
+     * @access public
+     * @param string &$mailbox The string to check.
+     * @return boolean Success or failure.
+     */
+    function validateMailbox(&$mailbox)
+    {
+        // A couple of defaults.
+        $phrase  = '';
+        $comment = '';
+        $comments = array();
+
+        // Catch any RFC822 comments and store them separately.
+        $_mailbox = $mailbox;
+        while (strlen(trim($_mailbox)) > 0) {
+            $parts = explode('(', $_mailbox);
+            $before_comment = $this->_splitCheck($parts, '(');
+            if ($before_comment != $_mailbox) {
+                // First char should be a (.
+                $comment    = substr(str_replace($before_comment, '', $_mailbox), 1);
+                $parts      = explode(')', $comment);
+                $comment    = $this->_splitCheck($parts, ')');
+                $comments[] = $comment;
+
+                // +2 is for the brackets
+                $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2);
+            } else {
+                break;
+            }
+        }
+
+        foreach ($comments as $comment) {
+            $mailbox = str_replace("($comment)", '', $mailbox);
+        }
+
+        $mailbox = trim($mailbox);
+
+        // Check for name + route-addr
+        if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
+            $parts  = explode('<', $mailbox);
+            $name   = $this->_splitCheck($parts, '<');
+
+            $phrase     = trim($name);
+            $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
+
+            if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
+                return false;
+            }
+
+        // Only got addr-spec
+        } else {
+            // First snip angle brackets if present.
+            if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
+                $addr_spec = substr($mailbox, 1, -1);
+            } else {
+                $addr_spec = $mailbox;
+            }
+
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        }
+
+        // Construct the object that will be returned.
+        $mbox = new stdClass();
+
+        // Add the phrase (even if empty) and comments
+        $mbox->personal = $phrase;
+        $mbox->comment  = isset($comments) ? $comments : array();
+
+        if (isset($route_addr)) {
+            $mbox->mailbox = $route_addr['local_part'];
+            $mbox->host    = $route_addr['domain'];
+            $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
+        } else {
+            $mbox->mailbox = $addr_spec['local_part'];
+            $mbox->host    = $addr_spec['domain'];
+        }
+
+        $mailbox = $mbox;
+        return true;
+    }
+
+    /**
+     * This function validates a route-addr which is:
+     * route-addr = "<" [route] addr-spec ">"
+     *
+     * Angle brackets have already been removed at the point of
+     * getting to this function.
+     *
+     * @access private
+     * @param string $route_addr The string to check.
+     * @return mixed False on failure, or an array containing validated address/route information on success.
+     */
+    function _validateRouteAddr($route_addr)
+    {
+        // Check for colon.
+        if (strpos($route_addr, ':') !== false) {
+            $parts = explode(':', $route_addr);
+            $route = $this->_splitCheck($parts, ':');
+        } else {
+            $route = $route_addr;
+        }
+
+        // If $route is same as $route_addr then the colon was in
+        // quotes or brackets or, of course, non existent.
+        if ($route === $route_addr){
+            unset($route);
+            $addr_spec = $route_addr;
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        } else {
+            // Validate route part.
+            if (($route = $this->_validateRoute($route)) === false) {
+                return false;
+            }
+
+            $addr_spec = substr($route_addr, strlen($route . ':'));
+
+            // Validate addr-spec part.
+            if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+                return false;
+            }
+        }
+
+        if (isset($route)) {
+            $return['adl'] = $route;
+        } else {
+            $return['adl'] = '';
+        }
+
+        $return = array_merge($return, $addr_spec);
+        return $return;
+    }
+
+    /**
+     * Function to validate a route, which is:
+     * route = 1#("@" domain) ":"
+     *
+     * @access private
+     * @param string $route The string to check.
+     * @return mixed False on failure, or the validated $route on success.
+     */
+    function _validateRoute($route)
+    {
+        // Split on comma.
+        $domains = explode(',', trim($route));
+
+        foreach ($domains as $domain) {
+            $domain = str_replace('@', '', trim($domain));
+            if (!$this->_validateDomain($domain)) return false;
+        }
+
+        return $route;
+    }
+
+    /**
+     * Function to validate a domain, though this is not quite what
+     * you expect of a strict internet domain.
+     *
+     * domain = sub-domain *("." sub-domain)
+     *
+     * @access private
+     * @param string $domain The string to check.
+     * @return mixed False on failure, or the validated domain on success.
+     */
+    function _validateDomain($domain)
+    {
+        // Note the different use of $subdomains and $sub_domains
+        $subdomains = explode('.', $domain);
+
+        while (count($subdomains) > 0) {
+            $sub_domains[] = $this->_splitCheck($subdomains, '.');
+            for ($i = 0; $i < $this->index + 1; $i++)
+                array_shift($subdomains);
+        }
+
+        foreach ($sub_domains as $sub_domain) {
+            if (!$this->_validateSubdomain(trim($sub_domain)))
+                return false;
+        }
+
+        // Managed to get here, so return input.
+        return $domain;
+    }
+
+    /**
+     * Function to validate a subdomain:
+     *   subdomain = domain-ref / domain-literal
+     *
+     * @access private
+     * @param string $subdomain The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateSubdomain($subdomain)
+    {
+        if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
+            if (!$this->_validateDliteral($arr[1])) return false;
+        } else {
+            if (!$this->_validateAtom($subdomain)) return false;
+        }
+
+        // Got here, so return successful.
+        return true;
+    }
+
+    /**
+     * Function to validate a domain literal:
+     *   domain-literal =  "[" *(dtext / quoted-pair) "]"
+     *
+     * @access private
+     * @param string $dliteral The string to check.
+     * @return boolean Success or failure.
+     */
+    function _validateDliteral($dliteral)
+    {
+        return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
+    }
+
+    /**
+     * Function to validate an addr-spec.
+     *
+     * addr-spec = local-part "@" domain
+     *
+     * @access private
+     * @param string $addr_spec The string to check.
+     * @return mixed False on failure, or the validated addr-spec on success.
+     */
+    function _validateAddrSpec($addr_spec)
+    {
+        $addr_spec = trim($addr_spec);
+
+        // Split on @ sign if there is one.
+        if (strpos($addr_spec, '@') !== false) {
+            $parts      = explode('@', $addr_spec);
+            $local_part = $this->_splitCheck($parts, '@');
+            $domain     = substr($addr_spec, strlen($local_part . '@'));
+
+        // No @ sign so assume the default domain.
+        } else {
+            $local_part = $addr_spec;
+            $domain     = $this->default_domain;
+        }
+
+        if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
+        if (($domain     = $this->_validateDomain($domain)) === false) return false;
+
+        // Got here so return successful.
+        return array('local_part' => $local_part, 'domain' => $domain);
+    }
+
+    /**
+     * Function to validate the local part of an address:
+     *   local-part = word *("." word)
+     *
+     * @access private
+     * @param string $local_part
+     * @return mixed False on failure, or the validated local part on success.
+     */
+    function _validateLocalPart($local_part)
+    {
+        $parts = explode('.', $local_part);
+        $words = array();
+
+        // Split the local_part into words.
+        while (count($parts) > 0){
+            $words[] = $this->_splitCheck($parts, '.');
+            for ($i = 0; $i < $this->index + 1; $i++) {
+                array_shift($parts);
+            }
+        }
+
+        // Validate each word.
+        foreach ($words as $word) {
+            // If this word contains an unquoted space, it is invalid. (6.2.4)
+            if (strpos($word, ' ') && $word[0] !== '"')
+            {
+                return false;
+            }
+
+            if ($this->_validatePhrase(trim($word)) === false) return false;
+        }
+
+        // Managed to get here, so return the input.
+        return $local_part;
+    }
+
+    /**
+     * Returns an approximate count of how many addresses are in the
+     * given string. This is APPROXIMATE as it only splits based on a
+     * comma which has no preceding backslash. Could be useful as
+     * large amounts of addresses will end up producing *large*
+     * structures when used with parseAddressList().
+     *
+     * @param  string $data Addresses to count
+     * @return int          Approximate count
+     */
+    function approximateCount($data)
+    {
+        return count(preg_split('/(?<!\\\\),/', $data));
+    }
+
+    /**
+     * This is a email validating function separate to the rest of the
+     * class. It simply validates whether an email is of the common
+     * internet form: <user>@<domain>. This can be sufficient for most
+     * people. Optional stricter mode can be utilised which restricts
+     * mailbox characters allowed to alphanumeric, full stop, hyphen
+     * and underscore.
+     *
+     * @param  string  $data   Address to check
+     * @param  boolean $strict Optional stricter mode
+     * @return mixed           False if it fails, an indexed array
+     *                         username/domain if it matches
+     */
+    function isValidInetAddress($data, $strict = false)
+    {
+        $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
+        if (preg_match($regex, trim($data), $matches)) {
+            return array($matches[1], $matches[2]);
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/mail.php b/WEB-INF/lib/pear/Mail/mail.php
new file mode 100644 (file)
index 0000000..a8b4b5d
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * internal PHP-mail() implementation of the PEAR Mail:: interface.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck@horde.org> 
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * internal PHP-mail() implementation of the PEAR Mail:: interface.
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_mail extends Mail {
+
+    /**
+     * Any arguments to pass to the mail() function.
+     * @var string
+     */
+    var $_params = '';
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_mail:: object based on the parameters
+     * passed in.
+     *
+     * @param array $params Extra arguments for the mail() function.
+     */
+    function Mail_mail($params = null)
+    {
+        // The other mail implementations accept parameters as arrays.
+        // In the interest of being consistent, explode an array into
+        // a string of parameter arguments.
+        if (is_array($params)) {
+            $this->_params = join(' ', $params);
+        } else {
+            $this->_params = $params;
+        }
+
+        /* Because the mail() function may pass headers as command
+         * line arguments, we can't guarantee the use of the standard
+         * "\r\n" separator.  Instead, we use the system's native line
+         * separator. */
+        if (defined('PHP_EOL')) {
+            $this->sep = PHP_EOL;
+        } else {
+            $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+        }
+    }
+
+    /**
+     * Implements Mail_mail::send() function using php's built-in mail()
+     * command.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     *
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // If we're passed an array of recipients, implode it.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
+        }
+
+        // Get the Subject out of the headers array so that we can
+        // pass it as a seperate argument to mail().
+        $subject = '';
+        if (isset($headers['Subject'])) {
+            $subject = $headers['Subject'];
+            unset($headers['Subject']);
+        }
+
+        // Also remove the To: header.  The mail() function will add its own
+        // To: header based on the contents of $recipients.
+        unset($headers['To']);
+
+        // Flatten the headers out.
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list(, $text_headers) = $headerElements;
+
+        // We only use mail()'s optional fifth parameter if the additional
+        // parameters have been provided and we're not running in safe mode.
+        if (empty($this->_params) || ini_get('safe_mode')) {
+            $result = mail($recipients, $subject, $body, $text_headers);
+        } else {
+            $result = mail($recipients, $subject, $body, $text_headers,
+                           $this->_params);
+        }
+
+        // If the mail() function returned failure, we need to create a
+        // PEAR_Error object and return it instead of the boolean result.
+        if ($result === false) {
+            $result = PEAR::raiseError('mail() returned failure');
+        }
+
+        return $result;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/mock.php b/WEB-INF/lib/pear/Mail/mock.php
new file mode 100644 (file)
index 0000000..61570ba
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Mock implementation
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Chuck Hagenbuch <chuck@horde.org> 
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * Mock implementation of the PEAR Mail:: interface for testing.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_mock extends Mail {
+
+    /**
+     * Array of messages that have been sent with the mock.
+     *
+     * @var array
+     * @access public
+     */
+    var $sentMessages = array();
+
+    /**
+     * Callback before sending mail.
+     *
+     * @var callback
+     */
+    var $_preSendCallback;
+
+    /**
+     * Callback after sending mai.
+     *
+     * @var callback
+     */
+    var $_postSendCallback;
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_mock:: object based on the parameters
+     * passed in. It looks for the following parameters, both optional:
+     *     preSendCallback   Called before an email would be sent.
+     *     postSendCallback  Called after an email would have been sent.
+     *
+     * @param array Hash containing any parameters.
+     * @access public
+     */
+    function Mail_mock($params)
+    {
+        if (isset($params['preSendCallback']) &&
+            is_callable($params['preSendCallback'])) {
+            $this->_preSendCallback = $params['preSendCallback'];
+        }
+
+        if (isset($params['postSendCallback']) &&
+            is_callable($params['postSendCallback'])) {
+            $this->_postSendCallback = $params['postSendCallback'];
+        }
+    }
+
+    /**
+     * Implements Mail_mock::send() function. Silently discards all
+     * mail.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if ($this->_preSendCallback) {
+            call_user_func_array($this->_preSendCallback,
+                                 array(&$this, $recipients, $headers, $body));
+        }
+
+        $entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body);
+        $this->sentMessages[] = $entry;
+
+        if ($this->_postSendCallback) {
+            call_user_func_array($this->_postSendCallback,
+                                 array(&$this, $recipients, $headers, $body));
+        }
+
+        return true;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/null.php b/WEB-INF/lib/pear/Mail/null.php
new file mode 100644 (file)
index 0000000..f8d5827
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Null implementation of the PEAR Mail interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010 Phil Kernick
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    Mail
+ * @package     Mail
+ * @author      Phil Kernick <philk@rotfl.com.au>
+ * @copyright   2010 Phil Kernick
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/**
+ * Null implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_null extends Mail {
+
+    /**
+     * Implements Mail_null::send() function. Silently discards all
+     * mail.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        return true;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/sendmail.php b/WEB-INF/lib/pear/Mail/sendmail.php
new file mode 100644 (file)
index 0000000..b056575
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license,      |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck@horde.org>                            |
+// +----------------------------------------------------------------------+
+
+/**
+ * Sendmail implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294744 $
+ */
+class Mail_sendmail extends Mail {
+
+    /**
+     * The location of the sendmail or sendmail wrapper binary on the
+     * filesystem.
+     * @var string
+     */
+    var $sendmail_path = '/usr/sbin/sendmail';
+
+    /**
+     * Any extra command-line parameters to pass to the sendmail or
+     * sendmail wrapper binary.
+     * @var string
+     */
+    var $sendmail_args = '-i';
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_sendmail:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     sendmail_path    The location of the sendmail binary on the
+     *                      filesystem. Defaults to '/usr/sbin/sendmail'.
+     *
+     *     sendmail_args    Any extra parameters to pass to the sendmail
+     *                      or sendmail wrapper binary.
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @param array $params Hash containing any parameters different from the
+     *              defaults.
+     * @access public
+     */
+    function Mail_sendmail($params)
+    {
+        if (isset($params['sendmail_path'])) {
+            $this->sendmail_path = $params['sendmail_path'];
+        }
+        if (isset($params['sendmail_args'])) {
+            $this->sendmail_args = $params['sendmail_args'];
+        }
+
+        /*
+         * Because we need to pass message headers to the sendmail program on
+         * the commandline, we can't guarantee the use of the standard "\r\n"
+         * separator.  Instead, we use the system's native line separator.
+         */
+        if (defined('PHP_EOL')) {
+            $this->sep = PHP_EOL;
+        } else {
+            $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using the sendmail
+     * command-line binary.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (ie, 'Subject'), and the array value
+     *              is the header value (ie, 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               Mime parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+        $recipients = implode(' ', array_map('escapeshellarg', $recipients));
+
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list($from, $text_headers) = $headerElements;
+
+        /* Since few MTAs are going to allow this header to be forged
+         * unless it's in the MAIL FROM: exchange, we'll use
+         * Return-Path instead of From: if it's set. */
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+
+        if (!isset($from)) {
+            return PEAR::raiseError('No from address given.');
+        } elseif (strpos($from, ' ') !== false ||
+                  strpos($from, ';') !== false ||
+                  strpos($from, '&') !== false ||
+                  strpos($from, '`') !== false) {
+            return PEAR::raiseError('From address specified with dangerous characters.');
+        }
+
+        $from = escapeshellarg($from); // Security bug #16200
+
+        $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
+        if (!$mail) {
+            return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
+        }
+
+        // Write the headers following by two newlines: one to end the headers
+        // section and a second to separate the headers block from the body.
+        fputs($mail, $text_headers . $this->sep . $this->sep);
+
+        fputs($mail, $body);
+        $result = pclose($mail);
+        if (version_compare(phpversion(), '4.2.3') == -1) {
+            // With older php versions, we need to shift the pclose
+            // result to get the exit code.
+            $result = $result >> 8 & 0xFF;
+        }
+
+        if ($result != 0) {
+            return PEAR::raiseError('sendmail returned error code ' . $result,
+                                    $result);
+        }
+
+        return true;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/smtp.php b/WEB-INF/lib/pear/Mail/smtp.php
new file mode 100644 (file)
index 0000000..52ea602
--- /dev/null
@@ -0,0 +1,444 @@
+<?php
+/**
+ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010, Chuck Hagenbuch
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category    HTTP
+ * @package     HTTP_Request
+ * @author      Jon Parise <jon@php.net> 
+ * @author      Chuck Hagenbuch <chuck@horde.org>
+ * @copyright   2010 Chuck Hagenbuch
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link        http://pear.php.net/package/Mail/
+ */
+
+/** Error: Failed to create a Net_SMTP object */
+define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
+
+/** Error: Failed to connect to SMTP server */
+define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
+
+/** Error: SMTP authentication failure */
+define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
+
+/** Error: No From: address has been provided */
+define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
+
+/** Error: Failed to set sender */
+define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
+
+/** Error: Failed to add recipient */
+define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
+
+/** Error: Failed to send data */
+define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
+
+/**
+ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ * @access public
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_smtp extends Mail {
+
+    /**
+     * SMTP connection object.
+     *
+     * @var object
+     * @access private
+     */
+    var $_smtp = null;
+
+    /**
+     * The list of service extension parameters to pass to the Net_SMTP
+     * mailFrom() command.
+     * @var array
+     */
+    var $_extparams = array();
+
+    /**
+     * The SMTP host to connect to.
+     * @var string
+     */
+    var $host = 'localhost';
+
+    /**
+     * The port the SMTP server is on.
+     * @var integer
+     */
+    var $port = 25;
+
+    /**
+     * Should SMTP authentication be used?
+     *
+     * This value may be set to true, false or the name of a specific
+     * authentication method.
+     *
+     * If the value is set to true, the Net_SMTP package will attempt to use
+     * the best authentication method advertised by the remote SMTP server.
+     *
+     * @var mixed
+     */
+    var $auth = false;
+
+    /**
+     * The username to use if the SMTP server requires authentication.
+     * @var string
+     */
+    var $username = '';
+
+    /**
+     * The password to use if the SMTP server requires authentication.
+     * @var string
+     */
+    var $password = '';
+
+    /**
+     * Hostname or domain that will be sent to the remote SMTP server in the
+     * HELO / EHLO message.
+     *
+     * @var string
+     */
+    var $localhost = 'localhost';
+
+    /**
+     * SMTP connection timeout value.  NULL indicates no timeout.
+     *
+     * @var integer
+     */
+    var $timeout = null;
+
+    /**
+     * Turn on Net_SMTP debugging?
+     *
+     * @var boolean $debug
+     */
+    var $debug = false;
+
+    /**
+     * Indicates whether or not the SMTP connection should persist over
+     * multiple calls to the send() method.
+     *
+     * @var boolean
+     */
+    var $persist = false;
+
+    /**
+     * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
+     * supports it. This speeds up delivery over high-latency connections. By
+     * default, use the default value supplied by Net_SMTP.
+     * @var bool
+     */
+    var $pipelining;
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_smtp:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     host        The server to connect to. Defaults to localhost.
+     *     port        The port to connect to. Defaults to 25.
+     *     auth        SMTP authentication.  Defaults to none.
+     *     username    The username to use for SMTP auth. No default.
+     *     password    The password to use for SMTP auth. No default.
+     *     localhost   The local hostname / domain. Defaults to localhost.
+     *     timeout     The SMTP connection timeout. Defaults to none.
+     *     verp        Whether to use VERP or not. Defaults to false.
+     *                 DEPRECATED as of 1.2.0 (use setMailParams()).
+     *     debug       Activate SMTP debug mode? Defaults to false.
+     *     persist     Should the SMTP connection persist?
+     *     pipelining  Use SMTP command pipelining
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @param array Hash containing any parameters different from the
+     *              defaults.
+     * @access public
+     */
+    function Mail_smtp($params)
+    {
+        if (isset($params['host'])) $this->host = $params['host'];
+        if (isset($params['port'])) $this->port = $params['port'];
+        if (isset($params['auth'])) $this->auth = $params['auth'];
+        if (isset($params['username'])) $this->username = $params['username'];
+        if (isset($params['password'])) $this->password = $params['password'];
+        if (isset($params['localhost'])) $this->localhost = $params['localhost'];
+        if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+        if (isset($params['debug'])) $this->debug = (bool)$params['debug'];
+        if (isset($params['persist'])) $this->persist = (bool)$params['persist'];
+        if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining'];
+
+        // Deprecated options
+        if (isset($params['verp'])) {
+            $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
+        }
+
+        register_shutdown_function(array(&$this, '_Mail_smtp'));
+    }
+
+    /**
+     * Destructor implementation to ensure that we disconnect from any
+     * potentially-alive persistent SMTP connections.
+     */
+    function _Mail_smtp()
+    {
+        $this->disconnect();
+    }
+
+    /**
+     * Implements Mail::send() function using SMTP.
+     *
+     * @param mixed $recipients Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid. This may contain recipients not
+     *              specified in the headers, for Bcc:, resending
+     *              messages, etc.
+     *
+     * @param array $headers The array of headers to send with the mail, in an
+     *              associative array, where the array key is the
+     *              header name (e.g., 'Subject'), and the array value
+     *              is the header value (e.g., 'test'). The header
+     *              produced from those values would be 'Subject:
+     *              test'.
+     *
+     * @param string $body The full text of the message body, including any
+     *               MIME parts, etc.
+     *
+     * @return mixed Returns true on success, or a PEAR_Error
+     *               containing a descriptive error message on
+     *               failure.
+     * @access public
+     */
+    function send($recipients, $headers, $body)
+    {
+        /* If we don't already have an SMTP object, create one. */
+        $result = &$this->getSMTPObject();
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $this->_sanitizeHeaders($headers);
+
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            $this->_smtp->rset();
+            return $headerElements;
+        }
+        list($from, $textHeaders) = $headerElements;
+
+        /* Since few MTAs are going to allow this header to be forged
+         * unless it's in the MAIL FROM: exchange, we'll use
+         * Return-Path instead of From: if it's set. */
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+
+        if (!isset($from)) {
+            $this->_smtp->rset();
+            return PEAR::raiseError('No From: address has been provided',
+                                    PEAR_MAIL_SMTP_ERROR_FROM);
+        }
+
+        $params = null;
+        if (!empty($this->_extparams)) {
+            foreach ($this->_extparams as $key => $val) {
+                $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
+            }
+        }
+        if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
+            $error = $this->_error("Failed to set sender: $from", $res);
+            $this->_smtp->rset();
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
+        }
+
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            $this->_smtp->rset();
+            return $recipients;
+        }
+
+        foreach ($recipients as $recipient) {
+            $res = $this->_smtp->rcptTo($recipient);
+            if (is_a($res, 'PEAR_Error')) {
+                $error = $this->_error("Failed to add recipient: $recipient", $res);
+                $this->_smtp->rset();
+                return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
+            }
+        }
+
+        /* Send the message's headers and the body as SMTP data. */
+        $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body);
+               list(,$args) = $this->_smtp->getResponse();
+
+               if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
+                       $this->queued_as = $queued[1];
+               }
+
+               /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to.
+                * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */
+               $this->greeting = $this->_smtp->getGreeting();
+
+        if (is_a($res, 'PEAR_Error')) {
+            $error = $this->_error('Failed to send data', $res);
+            $this->_smtp->rset();
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
+        }
+
+        /* If persistent connections are disabled, destroy our SMTP object. */
+        if ($this->persist === false) {
+            $this->disconnect();
+        }
+
+        return true;
+    }
+
+    /**
+     * Connect to the SMTP server by instantiating a Net_SMTP object.
+     *
+     * @return mixed Returns a reference to the Net_SMTP object on success, or
+     *               a PEAR_Error containing a descriptive error message on
+     *               failure.
+     *
+     * @since  1.2.0
+     * @access public
+     */
+    function &getSMTPObject()
+    {
+        if (is_object($this->_smtp) !== false) {
+            return $this->_smtp;
+        }
+
+        include_once 'Net/SMTP.php';
+        $this->_smtp = &new Net_SMTP($this->host,
+                                     $this->port,
+                                     $this->localhost);
+
+        /* If we still don't have an SMTP object at this point, fail. */
+        if (is_object($this->_smtp) === false) {
+            return PEAR::raiseError('Failed to create a Net_SMTP object',
+                                    PEAR_MAIL_SMTP_ERROR_CREATE);
+        }
+
+        /* Configure the SMTP connection. */
+        if ($this->debug) {
+            $this->_smtp->setDebug(true);
+        }
+
+        /* Attempt to connect to the configured SMTP server. */
+        if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
+            $error = $this->_error('Failed to connect to ' .
+                                   $this->host . ':' . $this->port,
+                                   $res);
+            return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
+        }
+
+        /* Attempt to authenticate if authentication has been enabled. */
+        if ($this->auth) {
+            $method = is_string($this->auth) ? $this->auth : '';
+
+            if (PEAR::isError($res = $this->_smtp->auth($this->username,
+                                                        $this->password,
+                                                        $method))) {
+                $error = $this->_error("$method authentication failure",
+                                       $res);
+                $this->_smtp->rset();
+                return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
+            }
+        }
+
+        return $this->_smtp;
+    }
+
+    /**
+     * Add parameter associated with a SMTP service extension.
+     *
+     * @param string Extension keyword.
+     * @param string Any value the keyword needs.
+     *
+     * @since 1.2.0
+     * @access public
+     */
+    function addServiceExtensionParameter($keyword, $value = null)
+    {
+        $this->_extparams[$keyword] = $value;
+    }
+
+    /**
+     * Disconnect and destroy the current SMTP connection.
+     *
+     * @return boolean True if the SMTP connection no longer exists.
+     *
+     * @since  1.1.9
+     * @access public
+     */
+    function disconnect()
+    {
+        /* If we have an SMTP object, disconnect and destroy it. */
+        if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
+            $this->_smtp = null;
+        }
+
+        /* We are disconnected if we no longer have an SMTP object. */
+        return ($this->_smtp === null);
+    }
+
+    /**
+     * Build a standardized string describing the current SMTP error.
+     *
+     * @param string $text  Custom string describing the error context.
+     * @param object $error Reference to the current PEAR_Error object.
+     *
+     * @return string       A string describing the current SMTP error.
+     *
+     * @since  1.1.7
+     * @access private
+     */
+    function _error($text, &$error)
+    {
+        /* Split the SMTP response into a code and a response string. */
+        list($code, $response) = $this->_smtp->getResponse();
+
+        /* Build our standardized error string. */
+        return $text
+            . ' [SMTP: ' . $error->getMessage()
+            . " (code: $code, response: $response)]";
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Mail/smtpmx.php b/WEB-INF/lib/pear/Mail/smtpmx.php
new file mode 100644 (file)
index 0000000..f0b6940
--- /dev/null
@@ -0,0 +1,502 @@
+<?PHP
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * SMTP MX
+ *
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2010, gERD Schaufelberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ *   products derived from this software without specific prior written
+ *   permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category   Mail
+ * @package    Mail_smtpmx
+ * @author     gERD Schaufelberger <gerd@php-tools.net>
+ * @copyright  2010 gERD Schaufelberger
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $
+ * @link       http://pear.php.net/package/Mail/
+ */
+
+require_once 'Net/SMTP.php';
+
+/**
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ *
+ * @access public
+ * @author  gERD Schaufelberger <gerd@php-tools.net>
+ * @package Mail
+ * @version $Revision: 294747 $
+ */
+class Mail_smtpmx extends Mail {
+
+    /**
+     * SMTP connection object.
+     *
+     * @var object
+     * @access private
+     */
+    var $_smtp = null;
+
+    /**
+     * The port the SMTP server is on.
+     * @var integer
+     * @see getservicebyname()
+     */
+    var $port = 25;
+
+    /**
+     * Hostname or domain that will be sent to the remote SMTP server in the
+     * HELO / EHLO message.
+     *
+     * @var string
+     * @see posix_uname()
+     */
+    var $mailname = 'localhost';
+
+    /**
+     * SMTP connection timeout value.  NULL indicates no timeout.
+     *
+     * @var integer
+     */
+    var $timeout = 10;
+
+    /**
+     * use either PEAR:Net_DNS or getmxrr
+     *
+     * @var boolean
+     */
+    var $withNetDns = true;
+
+    /**
+     * PEAR:Net_DNS_Resolver
+     *
+     * @var object
+     */
+    var $resolver;
+
+    /**
+     * Whether to use VERP or not. If not a boolean, the string value
+     * will be used as the VERP separators.
+     *
+     * @var mixed boolean or string
+     */
+    var $verp = false;
+
+    /**
+     * Whether to use VRFY or not.
+     *
+     * @var boolean $vrfy
+     */
+    var $vrfy = false;
+
+    /**
+     * Switch to test mode - don't send emails for real
+     *
+     * @var boolean $debug
+     */
+    var $test = false;
+
+    /**
+     * Turn on Net_SMTP debugging?
+     *
+     * @var boolean $peardebug
+     */
+    var $debug = false;
+
+    /**
+     * internal error codes
+     *
+     * translate internal error identifier to PEAR-Error codes and human
+     * readable messages.
+     *
+     * @var boolean $debug
+     * @todo as I need unique error-codes to identify what exactly went wrond
+     *       I did not use intergers as it should be. Instead I added a "namespace"
+     *       for each code. This avoids conflicts with error codes from different
+     *       classes. How can I use unique error codes and stay conform with PEAR?
+     */
+    var $errorCode = array(
+        'not_connected' => array(
+            'code'  => 1,
+            'msg'   => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
+        ),
+        'failed_vrfy_rcpt' => array(
+            'code'  => 2,
+            'msg'   => 'Recipient "{RCPT}" could not be veryfied.'
+        ),
+        'failed_set_from' => array(
+            'code'  => 3,
+            'msg'   => 'Failed to set sender: {FROM}.'
+        ),
+        'failed_set_rcpt' => array(
+            'code'  => 4,
+            'msg'   => 'Failed to set recipient: {RCPT}.'
+        ),
+        'failed_send_data' => array(
+            'code'  => 5,
+            'msg'   => 'Failed to send mail to: {RCPT}.'
+        ),
+        'no_from' => array(
+            'code'  => 5,
+            'msg'   => 'No from address has be provided.'
+        ),
+        'send_data' => array(
+            'code'  => 7,
+            'msg'   => 'Failed to create Net_SMTP object.'
+        ),
+        'no_mx' => array(
+            'code'  => 8,
+            'msg'   => 'No MX-record for {RCPT} found.'
+        ),
+        'no_resolver' => array(
+            'code'  => 9,
+            'msg'   => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
+        ),
+        'failed_rset' => array(
+            'code'  => 10,
+            'msg'   => 'RSET command failed, SMTP-connection corrupt.'
+        ),
+    );
+
+    /**
+     * Constructor.
+     *
+     * Instantiates a new Mail_smtp:: object based on the parameters
+     * passed in. It looks for the following parameters:
+     *     mailname    The name of the local mail system (a valid hostname which matches the reverse lookup)
+     *     port        smtp-port - the default comes from getservicebyname() and should work fine
+     *     timeout     The SMTP connection timeout. Defaults to 30 seconds.
+     *     vrfy        Whether to use VRFY or not. Defaults to false.
+     *     verp        Whether to use VERP or not. Defaults to false.
+     *     test        Activate test mode? Defaults to false.
+     *     debug       Activate SMTP and Net_DNS debug mode? Defaults to false.
+     *     netdns      whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
+     *
+     * If a parameter is present in the $params array, it replaces the
+     * default.
+     *
+     * @access public
+     * @param array Hash containing any parameters different from the
+     *              defaults.
+     * @see _Mail_smtpmx()
+     */
+    function __construct($params)
+    {
+        if (isset($params['mailname'])) {
+            $this->mailname = $params['mailname'];
+        } else {
+            // try to find a valid mailname
+            if (function_exists('posix_uname')) {
+                $uname = posix_uname();
+                $this->mailname = $uname['nodename'];
+            }
+        }
+
+        // port number
+        if (isset($params['port'])) {
+            $this->_port = $params['port'];
+        } else {
+            $this->_port = getservbyname('smtp', 'tcp');
+        }
+
+        if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+        if (isset($params['verp'])) $this->verp = $params['verp'];
+        if (isset($params['test'])) $this->test = $params['test'];
+        if (isset($params['peardebug'])) $this->test = $params['peardebug'];
+        if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
+    }
+
+    /**
+     * Constructor wrapper for PHP4
+     *
+     * @access public
+     * @param array Hash containing any parameters different from the defaults
+     * @see __construct()
+     */
+    function Mail_smtpmx($params)
+    {
+        $this->__construct($params);
+        register_shutdown_function(array(&$this, '__destruct'));
+    }
+
+    /**
+     * Destructor implementation to ensure that we disconnect from any
+     * potentially-alive persistent SMTP connections.
+     */
+    function __destruct()
+    {
+        if (is_object($this->_smtp)) {
+            $this->_smtp->disconnect();
+            $this->_smtp = null;
+        }
+    }
+
+    /**
+     * Implements Mail::send() function using SMTP direct delivery
+     *
+     * @access public
+     * @param mixed $recipients in RFC822 style or array
+     * @param array $headers The array of headers to send with the mail.
+     * @param string $body The full text of the message body,
+     * @return mixed Returns true on success, or a PEAR_Error
+     */
+    function send($recipients, $headers, $body)
+    {
+        if (!is_array($headers)) {
+            return PEAR::raiseError('$headers must be an array');
+        }
+
+        $result = $this->_sanitizeHeaders($headers);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        // Prepare headers
+        $headerElements = $this->prepareHeaders($headers);
+        if (is_a($headerElements, 'PEAR_Error')) {
+            return $headerElements;
+        }
+        list($from, $textHeaders) = $headerElements;
+
+        // use 'Return-Path' if possible
+        if (!empty($headers['Return-Path'])) {
+            $from = $headers['Return-Path'];
+        }
+        if (!isset($from)) {
+            return $this->_raiseError('no_from');
+        }
+
+        // Prepare recipients
+        $recipients = $this->parseRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+
+        foreach ($recipients as $rcpt) {
+            list($user, $host) = explode('@', $rcpt);
+
+            $mx = $this->_getMx($host);
+            if (is_a($mx, 'PEAR_Error')) {
+                return $mx;
+            }
+
+            if (empty($mx)) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('no_mx', $info);
+            }
+
+            $connected = false;
+            foreach ($mx as $mserver => $mpriority) {
+                $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
+
+                // configure the SMTP connection.
+                if ($this->debug) {
+                    $this->_smtp->setDebug(true);
+                }
+
+                // attempt to connect to the configured SMTP server.
+                $res = $this->_smtp->connect($this->timeout);
+                if (is_a($res, 'PEAR_Error')) {
+                    $this->_smtp = null;
+                    continue;
+                }
+
+                // connection established
+                if ($res) {
+                    $connected = true;
+                    break;
+                }
+            }
+
+            if (!$connected) {
+                $info = array(
+                    'host' => implode(', ', array_keys($mx)),
+                    'port' => $this->port,
+                    'rcpt' => $rcpt,
+                );
+                return $this->_raiseError('not_connected', $info);
+            }
+
+            // Verify recipient
+            if ($this->vrfy) {
+                $res = $this->_smtp->vrfy($rcpt);
+                if (is_a($res, 'PEAR_Error')) {
+                    $info = array('rcpt' => $rcpt);
+                    return $this->_raiseError('failed_vrfy_rcpt', $info);
+                }
+            }
+
+            // mail from:
+            $args['verp'] = $this->verp;
+            $res = $this->_smtp->mailFrom($from, $args);
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('from' => $from);
+                return $this->_raiseError('failed_set_from', $info);
+            }
+
+            // rcpt to:
+            $res = $this->_smtp->rcptTo($rcpt);
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('failed_set_rcpt', $info);
+            }
+
+            // Don't send anything in test mode
+            if ($this->test) {
+                $result = $this->_smtp->rset();
+                $res = $this->_smtp->rset();
+                if (is_a($res, 'PEAR_Error')) {
+                    return $this->_raiseError('failed_rset');
+                }
+
+                $this->_smtp->disconnect();
+                $this->_smtp = null;
+                return true;
+            }
+
+            // Send data
+            $res = $this->_smtp->data("$textHeaders\r\n$body");
+            if (is_a($res, 'PEAR_Error')) {
+                $info = array('rcpt' => $rcpt);
+                return $this->_raiseError('failed_send_data', $info);
+            }
+
+            $this->_smtp->disconnect();
+            $this->_smtp = null;
+        }
+
+        return true;
+    }
+
+    /**
+     * Recieve mx rexords for a spciefied host
+     *
+     * The MX records
+     *
+     * @access private
+     * @param string $host mail host
+     * @return mixed sorted
+     */
+    function _getMx($host)
+    {
+        $mx = array();
+
+        if ($this->withNetDns) {
+            $res = $this->_loadNetDns();
+            if (is_a($res, 'PEAR_Error')) {
+                return $res;
+            }
+
+            $response = $this->resolver->query($host, 'MX');
+            if (!$response) {
+                return false;
+            }
+
+            foreach ($response->answer as $rr) {
+                if ($rr->type == 'MX') {
+                    $mx[$rr->exchange] = $rr->preference;
+                }
+            }
+        } else {
+            $mxHost = array();
+            $mxWeight = array();
+
+            if (!getmxrr($host, $mxHost, $mxWeight)) {
+                return false;
+            }
+            for ($i = 0; $i < count($mxHost); ++$i) {
+                $mx[$mxHost[$i]] = $mxWeight[$i];
+            }
+        }
+
+        asort($mx);
+        return $mx;
+    }
+
+    /**
+     * initialize PEAR:Net_DNS_Resolver
+     *
+     * @access private
+     * @return boolean true on success
+     */
+    function _loadNetDns()
+    {
+        if (is_object($this->resolver)) {
+            return true;
+        }
+
+        if (!include_once 'Net/DNS.php') {
+            return $this->_raiseError('no_resolver');
+        }
+
+        $this->resolver = new Net_DNS_Resolver();
+        if ($this->debug) {
+            $this->resolver->test = 1;
+        }
+
+        return true;
+    }
+
+    /**
+     * raise standardized error
+     *
+     * include additional information in error message
+     *
+     * @access private
+     * @param string $id maps error ids to codes and message
+     * @param array $info optional information in associative array
+     * @see _errorCode
+     */
+    function _raiseError($id, $info = array())
+    {
+        $code = $this->errorCode[$id]['code'];
+        $msg = $this->errorCode[$id]['msg'];
+
+        // include info to messages
+        if (!empty($info)) {
+            $search = array();
+            $replace = array();
+
+            foreach ($info as $key => $value) {
+                array_push($search, '{' . strtoupper($key) . '}');
+                array_push($replace, $value);
+            }
+
+            $msg = str_replace($search, $replace, $msg);
+        }
+
+        return PEAR::raiseError($msg, $code);
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Net/SMTP.php b/WEB-INF/lib/pear/Net/SMTP.php
new file mode 100644 (file)
index 0000000..ea4b55e
--- /dev/null
@@ -0,0 +1,1188 @@
+<?php
+/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license,      |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Chuck Hagenbuch <chuck@horde.org>                           |
+// |          Jon Parise <jon@php.net>                                    |
+// |          Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
+// +----------------------------------------------------------------------+
+//
+// $Id: SMTP.php 293948 2010-01-24 21:46:00Z jon $
+
+require_once 'PEAR.php';
+require_once 'Net/Socket.php';
+
+/**
+ * Provides an implementation of the SMTP protocol using PEAR's
+ * Net_Socket:: class.
+ *
+ * @package Net_SMTP
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Jon Parise <jon@php.net>
+ * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
+ *
+ * @example basic.php   A basic implementation of the Net_SMTP package.
+ */
+class Net_SMTP
+{
+    /**
+     * The server to connect to.
+     * @var string
+     * @access public
+     */
+    var $host = 'localhost';
+
+    /**
+     * The port to connect to.
+     * @var int
+     * @access public
+     */
+    var $port = 25;
+
+    /**
+     * The value to give when sending EHLO or HELO.
+     * @var string
+     * @access public
+     */
+    var $localhost = 'localhost';
+
+    /**
+     * List of supported authentication methods, in preferential order.
+     * @var array
+     * @access public
+     */
+    var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
+
+    /**
+     * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
+     * server supports it.
+     *
+     * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
+     * somlFrom() and samlFrom() do not wait for a response from the
+     * SMTP server but return immediately.
+     *
+     * @var bool
+     * @access public
+     */
+    var $pipelining = false;
+
+    /**
+     * Number of pipelined commands.
+     * @var int
+     * @access private
+     */
+    var $_pipelined_commands = 0;
+
+    /**
+     * Should debugging output be enabled?
+     * @var boolean
+     * @access private
+     */
+    var $_debug = false;
+
+    /**
+     * Debug output handler.
+     * @var callback
+     * @access private
+     */
+    var $_debug_handler = null;
+
+    /**
+     * The socket resource being used to connect to the SMTP server.
+     * @var resource
+     * @access private
+     */
+    var $_socket = null;
+
+    /**
+     * The most recent server response code.
+     * @var int
+     * @access private
+     */
+    var $_code = -1;
+
+    /**
+     * The most recent server response arguments.
+     * @var array
+     * @access private
+     */
+    var $_arguments = array();
+
+    /**
+     * Stores the SMTP server's greeting string.
+     * @var string
+     * @access private
+     */
+    var $_greeting = null;
+
+    /**
+     * Stores detected features of the SMTP server.
+     * @var array
+     * @access private
+     */
+    var $_esmtp = array();
+
+    /**
+     * Instantiates a new Net_SMTP object, overriding any defaults
+     * with parameters that are passed in.
+     *
+     * If you have SSL support in PHP, you can connect to a server
+     * over SSL using an 'ssl://' prefix:
+     *
+     *   // 465 is a common smtps port.
+     *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
+     *   $smtp->connect();
+     *
+     * @param string  $host       The server to connect to.
+     * @param integer $port       The port to connect to.
+     * @param string  $localhost  The value to give when sending EHLO or HELO.
+     * @param boolean $pipeling   Use SMTP command pipelining
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = false)
+    {
+        if (isset($host)) {
+            $this->host = $host;
+        }
+        if (isset($port)) {
+            $this->port = $port;
+        }
+        if (isset($localhost)) {
+            $this->localhost = $localhost;
+        }
+        $this->pipelining = $pipelining;
+
+        $this->_socket = new Net_Socket();
+
+        /* Include the Auth_SASL package.  If the package is not
+         * available, we disable the authentication methods that
+         * depend upon it. */
+        if ((@include_once 'Auth/SASL.php') === false) {
+            $pos = array_search('DIGEST-MD5', $this->auth_methods);
+            unset($this->auth_methods[$pos]);
+            $pos = array_search('CRAM-MD5', $this->auth_methods);
+            unset($this->auth_methods[$pos]);
+        }
+    }
+
+    /**
+     * Set the value of the debugging flag.
+     *
+     * @param   boolean $debug      New value for the debugging flag.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function setDebug($debug, $handler = null)
+    {
+        $this->_debug = $debug;
+        $this->_debug_handler = $handler;
+    }
+
+    /**
+     * Write the given debug text to the current debug output handler.
+     *
+     * @param   string  $message    Debug mesage text.
+     *
+     * @access  private
+     * @since   1.3.3
+     */
+    function _debug($message)
+    {
+        if ($this->_debug) {
+            if ($this->_debug_handler) {
+                call_user_func_array($this->_debug_handler,
+                                     array(&$this, $message));
+            } else {
+                echo "DEBUG: $message\n";
+            }
+        }
+    }
+
+    /**
+     * Send the given string of data to the server.
+     *
+     * @param   string  $data       The string of data to send.
+     *
+     * @return  mixed   True on success or a PEAR_Error object on failure.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _send($data)
+    {
+        $this->_debug("Send: $data");
+
+        $error = $this->_socket->write($data);
+        if ($error === false || PEAR::isError($error)) {
+            $msg = ($error) ? $error->getMessage() : "unknown error";
+            return PEAR::raiseError("Failed to write to socket: $msg");
+        }
+
+        return true;
+    }
+
+    /**
+     * Send a command to the server with an optional string of
+     * arguments.  A carriage return / linefeed (CRLF) sequence will
+     * be appended to each command string before it is sent to the
+     * SMTP server - an error will be thrown if the command string
+     * already contains any newline characters. Use _send() for
+     * commands that must contain newlines.
+     *
+     * @param   string  $command    The SMTP command to send to the server.
+     * @param   string  $args       A string of optional arguments to append
+     *                              to the command.
+     *
+     * @return  mixed   The result of the _send() call.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _put($command, $args = '')
+    {
+        if (!empty($args)) {
+            $command .= ' ' . $args;
+        }
+
+        if (strcspn($command, "\r\n") !== strlen($command)) {
+            return PEAR::raiseError('Commands cannot contain newlines');
+        }
+
+        return $this->_send($command . "\r\n");
+    }
+
+    /**
+     * Read a reply from the SMTP server.  The reply consists of a response
+     * code and a response message.
+     *
+     * @param   mixed   $valid      The set of valid response codes.  These
+     *                              may be specified as an array of integer
+     *                              values or as a single integer value.
+     * @param   bool    $later      Do not parse the response now, but wait
+     *                              until the last command in the pipelined
+     *                              command group
+     *
+     * @return  mixed   True if the server returned a valid response code or
+     *                  a PEAR_Error object is an error condition is reached.
+     *
+     * @access  private
+     * @since   1.1.0
+     *
+     * @see     getResponse
+     */
+    function _parseResponse($valid, $later = false)
+    {
+        $this->_code = -1;
+        $this->_arguments = array();
+
+        if ($later) {
+            $this->_pipelined_commands++;
+            return true;
+        }
+
+        for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
+            while ($line = $this->_socket->readLine()) {
+                $this->_debug("Recv: $line");
+
+                /* If we receive an empty line, the connection has been closed. */
+                if (empty($line)) {
+                    $this->disconnect();
+                    return PEAR::raiseError('Connection was unexpectedly closed');
+                }
+
+                /* Read the code and store the rest in the arguments array. */
+                $code = substr($line, 0, 3);
+                $this->_arguments[] = trim(substr($line, 4));
+
+                /* Check the syntax of the response code. */
+                if (is_numeric($code)) {
+                    $this->_code = (int)$code;
+                } else {
+                    $this->_code = -1;
+                    break;
+                }
+
+                /* If this is not a multiline response, we're done. */
+                if (substr($line, 3, 1) != '-') {
+                    break;
+                }
+            }
+        }
+
+        $this->_pipelined_commands = 0;
+
+        /* Compare the server's response code with the valid code/codes. */
+        if (is_int($valid) && ($this->_code === $valid)) {
+            return true;
+        } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
+            return true;
+        }
+
+        return PEAR::raiseError('Invalid response code received from server',
+                                $this->_code);
+    }
+
+    /**
+     * Return a 2-tuple containing the last response from the SMTP server.
+     *
+     * @return  array   A two-element array: the first element contains the
+     *                  response code as an integer and the second element
+     *                  contains the response's arguments as a string.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function getResponse()
+    {
+        return array($this->_code, join("\n", $this->_arguments));
+    }
+
+    /**
+     * Return the SMTP server's greeting string.
+     *
+     * @return  string  A string containing the greeting string, or null if a 
+     *                  greeting has not been received.
+     *
+     * @access  public
+     * @since   1.3.3
+     */
+    function getGreeting()
+    {
+        return $this->_greeting;
+    }
+
+    /**
+     * Attempt to connect to the SMTP server.
+     *
+     * @param   int     $timeout    The timeout value (in seconds) for the
+     *                              socket connection.
+     * @param   bool    $persistent Should a persistent socket connection
+     *                              be used?
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function connect($timeout = null, $persistent = false)
+    {
+        $this->_greeting = null;
+        $result = $this->_socket->connect($this->host, $this->port,
+                                          $persistent, $timeout);
+        if (PEAR::isError($result)) {
+            return PEAR::raiseError('Failed to connect socket: ' .
+                                    $result->getMessage());
+        }
+
+        if (PEAR::isError($error = $this->_parseResponse(220))) {
+            return $error;
+        }
+
+        /* Extract and store a copy of the server's greeting string. */
+        list(, $this->_greeting) = $this->getResponse();
+
+        if (PEAR::isError($error = $this->_negotiate())) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to disconnect from the SMTP server.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function disconnect()
+    {
+        if (PEAR::isError($error = $this->_put('QUIT'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(221))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_socket->disconnect())) {
+            return PEAR::raiseError('Failed to disconnect socket: ' .
+                                    $error->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to send the EHLO command and obtain a list of ESMTP
+     * extensions available, and failing that just send HELO.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access private
+     * @since  1.1.0
+     */
+    function _negotiate()
+    {
+        if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
+            return $error;
+        }
+
+        if (PEAR::isError($this->_parseResponse(250))) {
+            /* If we receive a 503 response, we're already authenticated. */
+            if ($this->_code === 503) {
+                return true;
+            }
+
+            /* If the EHLO failed, try the simpler HELO command. */
+            if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
+                return $error;
+            }
+            if (PEAR::isError($this->_parseResponse(250))) {
+                return PEAR::raiseError('HELO was not accepted: ', $this->_code);
+            }
+
+            return true;
+        }
+
+        foreach ($this->_arguments as $argument) {
+            $verb = strtok($argument, ' ');
+            $arguments = substr($argument, strlen($verb) + 1,
+                                strlen($argument) - strlen($verb) - 1);
+            $this->_esmtp[$verb] = $arguments;
+        }
+
+        if (!isset($this->_esmtp['PIPELINING'])) {
+            $this->pipelining = false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the name of the best authentication method that the server
+     * has advertised.
+     *
+     * @return mixed    Returns a string containing the name of the best
+     *                  supported authentication method or a PEAR_Error object
+     *                  if a failure condition is encountered.
+     * @access private
+     * @since  1.1.0
+     */
+    function _getBestAuthMethod()
+    {
+        $available_methods = explode(' ', $this->_esmtp['AUTH']);
+
+        foreach ($this->auth_methods as $method) {
+            if (in_array($method, $available_methods)) {
+                return $method;
+            }
+        }
+
+        return PEAR::raiseError('No supported authentication methods');
+    }
+
+    /**
+     * Attempt to do SMTP authentication.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The requested authentication method.  If none is
+     *               specified, the best supported method will be used.
+     * @param bool   Flag indicating whether or not TLS should be attempted.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function auth($uid, $pwd , $method = '', $tls = true)
+    {
+        /* We can only attempt a TLS connection if one has been requested,
+         * we're running PHP 5.1.0 or later, have access to the OpenSSL 
+         * extension, are connected to an SMTP server which supports the 
+         * STARTTLS extension, and aren't already connected over a secure 
+         * (SSL) socket connection. */
+        if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') &&
+            extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) &&
+            strncasecmp($this->host, 'ssl://', 6) !== 0) {
+            /* Start the TLS connection attempt. */
+            if (PEAR::isError($result = $this->_put('STARTTLS'))) {
+                return $result;
+            }
+            if (PEAR::isError($result = $this->_parseResponse(220))) {
+                return $result;
+            }
+            if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
+                return $result;
+            } elseif ($result !== true) {
+                return PEAR::raiseError('STARTTLS failed');
+            }
+
+            /* Send EHLO again to recieve the AUTH string from the
+             * SMTP server. */
+            $this->_negotiate();
+        }
+
+        if (empty($this->_esmtp['AUTH'])) {
+            return PEAR::raiseError('SMTP server does not support authentication');
+        }
+
+        /* If no method has been specified, get the name of the best
+         * supported method advertised by the SMTP server. */
+        if (empty($method)) {
+            if (PEAR::isError($method = $this->_getBestAuthMethod())) {
+                /* Return the PEAR_Error object from _getBestAuthMethod(). */
+                return $method;
+            }
+        } else {
+            $method = strtoupper($method);
+            if (!in_array($method, $this->auth_methods)) {
+                return PEAR::raiseError("$method is not a supported authentication method");
+            }
+        }
+
+        switch ($method) {
+        case 'DIGEST-MD5':
+            $result = $this->_authDigest_MD5($uid, $pwd);
+            break;
+
+        case 'CRAM-MD5':
+            $result = $this->_authCRAM_MD5($uid, $pwd);
+            break;
+
+        case 'LOGIN':
+            $result = $this->_authLogin($uid, $pwd);
+            break;
+
+        case 'PLAIN':
+            $result = $this->_authPlain($uid, $pwd);
+            break;
+
+        default:
+            $result = PEAR::raiseError("$method is not a supported authentication method");
+            break;
+        }
+
+        /* If an error was encountered, return the PEAR_Error object. */
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the DIGEST-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authDigest_MD5($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $digest = &Auth_SASL::factory('digestmd5');
+        $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
+                                                       $this->host, "smtp"));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        /* We don't use the protocol's third step because SMTP doesn't
+         * allow subsequent authentication, so we just silently ignore
+         * it. */
+        if (PEAR::isError($error = $this->_put(''))) {
+            return $error;
+        }
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the CRAM-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authCRAM_MD5($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $cram = &Auth_SASL::factory('crammd5');
+        $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the LOGIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authLogin($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the PLAIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authPlain($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the HELO command.
+     *
+     * @param string The domain name to say we are.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function helo($domain)
+    {
+        if (PEAR::isError($error = $this->_put('HELO', $domain))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Return the list of SMTP service extensions advertised by the server.
+     *
+     * @return array The list of SMTP service extensions.
+     * @access public
+     * @since 1.3
+     */
+    function getServiceExtensions()
+    {
+        return $this->_esmtp;
+    }
+
+    /**
+     * Send the MAIL FROM: command.
+     *
+     * @param string $sender    The sender (reverse path) to set.
+     * @param string $params    String containing additional MAIL parameters,
+     *                          such as the NOTIFY flags defined by RFC 1891
+     *                          or the VERP protocol.
+     *
+     *                          If $params is an array, only the 'verp' option
+     *                          is supported.  If 'verp' is true, the XVERP
+     *                          parameter is appended to the MAIL command.  If
+     *                          the 'verp' value is a string, the full
+     *                          XVERP=value parameter is appended.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function mailFrom($sender, $params = null)
+    {
+        $args = "FROM:<$sender>";
+
+        /* Support the deprecated array form of $params. */
+        if (is_array($params) && isset($params['verp'])) {
+            /* XVERP */
+            if ($params['verp'] === true) {
+                $args .= ' XVERP';
+
+            /* XVERP=something */
+            } elseif (trim($params['verp'])) {
+                $args .= ' XVERP=' . $params['verp'];
+            }
+        } elseif (is_string($params)) {
+            $args .= ' ' . $params;
+        }
+
+        if (PEAR::isError($error = $this->_put('MAIL', $args))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the RCPT TO: command.
+     *
+     * @param string $recipient The recipient (forward path) to add.
+     * @param string $params    String containing additional RCPT parameters,
+     *                          such as the NOTIFY flags defined by RFC 1891.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access public
+     * @since  1.0
+     */
+    function rcptTo($recipient, $params = null)
+    {
+        $args = "TO:<$recipient>";
+        if (is_string($params)) {
+            $args .= ' ' . $params;
+        }
+
+        if (PEAR::isError($error = $this->_put('RCPT', $args))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Quote the data so that it meets SMTP standards.
+     *
+     * This is provided as a separate public function to facilitate
+     * easier overloading for the cases where it is desirable to
+     * customize the quoting behavior.
+     *
+     * @param string $data  The message text to quote. The string must be passed
+     *                      by reference, and the text will be modified in place.
+     *
+     * @access public
+     * @since  1.2
+     */
+    function quotedata(&$data)
+    {
+        /* Change Unix (\n) and Mac (\r) linefeeds into
+         * Internet-standard CRLF (\r\n) linefeeds. */
+        $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
+
+        /* Because a single leading period (.) signifies an end to the
+         * data, legitimate leading periods need to be "doubled"
+         * (e.g. '..'). */
+        $data = str_replace("\n.", "\n..", $data);
+    }
+
+    /**
+     * Send the DATA command.
+     *
+     * @param mixed $data     The message data, either as a string or an open
+     *                        file resource.
+     * @param string $headers The message headers.  If $headers is provided,
+     *                        $data is assumed to contain only body data.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function data($data, $headers = null)
+    {
+        /* Verify that $data is a supported type. */
+        if (!is_string($data) && !is_resource($data)) {
+            return PEAR::raiseError('Expected a string or file resource');
+        }
+
+        /* RFC 1870, section 3, subsection 3 states "a value of zero
+         * indicates that no fixed maximum message size is in force".
+         * Furthermore, it says that if "the parameter is omitted no
+         * information is conveyed about the server's fixed maximum
+         * message size". */
+        if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
+            /* Start by considering the size of the optional headers string.  
+             * We also account for the addition 4 character "\r\n\r\n"
+             * separator sequence. */
+            $size = (is_null($headers)) ? 0 : strlen($headers) + 4;
+
+            if (is_resource($data)) {
+                $stat = fstat($data);
+                if ($stat === false) {
+                    return PEAR::raiseError('Failed to get file size');
+                }
+                $size += $stat['size'];
+            } else {
+                $size += strlen($data);
+            }
+
+            if ($size >= $this->_esmtp['SIZE']) {
+                $this->disconnect();
+                return PEAR::raiseError('Message size exceeds server limit');
+            }
+        }
+
+        /* Initiate the DATA command. */
+        if (PEAR::isError($error = $this->_put('DATA'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(354))) {
+            return $error;
+        }
+
+        /* If we have a separate headers string, send it first. */
+        if (!is_null($headers)) {
+            $this->quotedata($headers);
+            if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) {
+                return $result;
+            }
+        }
+
+        /* Now we can send the message body data. */
+        if (is_resource($data)) {
+            /* Stream the contents of the file resource out over our socket 
+             * connection, line by line.  Each line must be run through the 
+             * quoting routine. */
+            while ($line = fgets($data, 1024)) {
+                $this->quotedata($line);
+                if (PEAR::isError($result = $this->_send($line))) {
+                    return $result;
+                }
+            }
+
+            /* Finally, send the DATA terminator sequence. */
+            if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) {
+                return $result;
+            }
+        } else {
+            /* Just send the entire quoted string followed by the DATA 
+             * terminator. */
+            $this->quotedata($data);
+            if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) {
+                return $result;
+            }
+        }
+
+        /* Verify that the data was successfully received by the server. */
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the SEND FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function sendFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for sendFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function send_from($path)
+    {
+        return sendFrom($path);
+    }
+
+    /**
+     * Send the SOML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function somlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for somlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function soml_from($path)
+    {
+        return somlFrom($path);
+    }
+
+    /**
+     * Send the SAML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function samlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for samlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function saml_from($path)
+    {
+        return samlFrom($path);
+    }
+
+    /**
+     * Send the RSET command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function rset()
+    {
+        if (PEAR::isError($error = $this->_put('RSET'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the VRFY command.
+     *
+     * @param string The string to verify
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function vrfy($string)
+    {
+        /* Note: 251 is also a valid response code */
+        if (PEAR::isError($error = $this->_put('VRFY', $string))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the NOOP command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function noop()
+    {
+        if (PEAR::isError($error = $this->_put('NOOP'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility method.  identifySender()'s functionality is
+     * now handled internally.
+     *
+     * @return  boolean     This method always return true.
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function identifySender()
+    {
+        return true;
+    }
+
+}
diff --git a/WEB-INF/lib/pear/Net/Socket.php b/WEB-INF/lib/pear/Net/Socket.php
new file mode 100644 (file)
index 0000000..73bb4dd
--- /dev/null
@@ -0,0 +1,592 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available 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: Stig Bakken <ssb@php.net>                                   |
+// |          Chuck Hagenbuch <chuck@horde.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Socket.php,v 1.38 2008/02/15 18:24:17 chagenbu Exp $
+
+require_once 'PEAR.php';
+
+define('NET_SOCKET_READ',  1);
+define('NET_SOCKET_WRITE', 2);
+define('NET_SOCKET_ERROR', 4);
+
+/**
+ * Generalized Socket class.
+ *
+ * @version 1.1
+ * @author Stig Bakken <ssb@php.net>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+class Net_Socket extends PEAR {
+
+    /**
+     * Socket file pointer.
+     * @var resource $fp
+     */
+    var $fp = null;
+
+    /**
+     * Whether the socket is blocking. Defaults to true.
+     * @var boolean $blocking
+     */
+    var $blocking = true;
+
+    /**
+     * Whether the socket is persistent. Defaults to false.
+     * @var boolean $persistent
+     */
+    var $persistent = false;
+
+    /**
+     * The IP address to connect to.
+     * @var string $addr
+     */
+    var $addr = '';
+
+    /**
+     * The port number to connect to.
+     * @var integer $port
+     */
+    var $port = 0;
+
+    /**
+     * Number of seconds to wait on socket connections before assuming
+     * there's no more data. Defaults to no timeout.
+     * @var integer $timeout
+     */
+    var $timeout = false;
+
+    /**
+     * Number of bytes to read at a time in readLine() and
+     * readAll(). Defaults to 2048.
+     * @var integer $lineLength
+     */
+    var $lineLength = 2048;
+
+    /**
+     * Connect to the specified port. If called when the socket is
+     * already connected, it disconnects and connects again.
+     *
+     * @param string  $addr        IP address or host name.
+     * @param integer $port        TCP port number.
+     * @param boolean $persistent  (optional) Whether the connection is
+     *                             persistent (kept open between requests
+     *                             by the web server).
+     * @param integer $timeout     (optional) How long to wait for data.
+     * @param array   $options     See options for stream_context_create.
+     *
+     * @access public
+     *
+     * @return boolean | PEAR_Error  True on success or a PEAR_Error on failure.
+     */
+    function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null)
+    {
+        if (is_resource($this->fp)) {
+            @fclose($this->fp);
+            $this->fp = null;
+        }
+
+        if (!$addr) {
+            return $this->raiseError('$addr cannot be empty');
+        } elseif (strspn($addr, '.0123456789') == strlen($addr) ||
+                  strstr($addr, '/') !== false) {
+            $this->addr = $addr;
+        } else {
+            $this->addr = @gethostbyname($addr);
+        }
+
+        $this->port = $port % 65536;
+
+        if ($persistent !== null) {
+            $this->persistent = $persistent;
+        }
+
+        if ($timeout !== null) {
+            $this->timeout = $timeout;
+        }
+
+        $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
+        $errno = 0;
+        $errstr = '';
+        $old_track_errors = @ini_set('track_errors', 1);
+        if ($options && function_exists('stream_context_create')) {
+            if ($this->timeout) {
+                $timeout = $this->timeout;
+            } else {
+                $timeout = 0;
+            }
+            $context = stream_context_create($options);
+
+            // Since PHP 5 fsockopen doesn't allow context specification
+            if (function_exists('stream_socket_client')) {
+                $flags = $this->persistent ? STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT;
+                $addr = $this->addr . ':' . $this->port;
+                $fp = stream_socket_client($addr, $errno, $errstr, $timeout, $flags, $context);
+            } else {
+                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context);
+            }
+        } else {
+            if ($this->timeout) {
+                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
+            } else {
+                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
+            }
+        }
+
+        if (!$fp) {
+            if ($errno == 0 && isset($php_errormsg)) {
+                $errstr = $php_errormsg;
+            }
+            @ini_set('track_errors', $old_track_errors);
+            return $this->raiseError($errstr, $errno);
+        }
+
+        @ini_set('track_errors', $old_track_errors);
+        $this->fp = $fp;
+
+        return $this->setBlocking($this->blocking);
+    }
+
+    /**
+     * Disconnects from the peer, closes the socket.
+     *
+     * @access public
+     * @return mixed true on success or a PEAR_Error instance otherwise
+     */
+    function disconnect()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        @fclose($this->fp);
+        $this->fp = null;
+        return true;
+    }
+
+    /**
+     * Find out if the socket is in blocking mode.
+     *
+     * @access public
+     * @return boolean  The current blocking mode.
+     */
+    function isBlocking()
+    {
+        return $this->blocking;
+    }
+
+    /**
+     * Sets whether the socket connection should be blocking or
+     * not. A read call to a non-blocking socket will return immediately
+     * if there is no data available, whereas it will block until there
+     * is data for blocking sockets.
+     *
+     * @param boolean $mode  True for blocking sockets, false for nonblocking.
+     * @access public
+     * @return mixed true on success or a PEAR_Error instance otherwise
+     */
+    function setBlocking($mode)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $this->blocking = $mode;
+        socket_set_blocking($this->fp, $this->blocking);
+        return true;
+    }
+
+    /**
+     * Sets the timeout value on socket descriptor,
+     * expressed in the sum of seconds and microseconds
+     *
+     * @param integer $seconds  Seconds.
+     * @param integer $microseconds  Microseconds.
+     * @access public
+     * @return mixed true on success or a PEAR_Error instance otherwise
+     */
+    function setTimeout($seconds, $microseconds)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return socket_set_timeout($this->fp, $seconds, $microseconds);
+    }
+
+    /**
+     * Sets the file buffering size on the stream.
+     * See php's stream_set_write_buffer for more information.
+     *
+     * @param integer $size     Write buffer size.
+     * @access public
+     * @return mixed on success or an PEAR_Error object otherwise
+     */
+    function setWriteBuffer($size)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $returned = stream_set_write_buffer($this->fp, $size);
+        if ($returned == 0) {
+            return true;
+        }
+        return $this->raiseError('Cannot set write buffer.');
+    }
+
+    /**
+     * Returns information about an existing socket resource.
+     * Currently returns four entries in the result array:
+     *
+     * <p>
+     * timed_out (bool) - The socket timed out waiting for data<br>
+     * blocked (bool) - The socket was blocked<br>
+     * eof (bool) - Indicates EOF event<br>
+     * unread_bytes (int) - Number of bytes left in the socket buffer<br>
+     * </p>
+     *
+     * @access public
+     * @return mixed Array containing information about existing socket resource or a PEAR_Error instance otherwise
+     */
+    function getStatus()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return socket_get_status($this->fp);
+    }
+
+    /**
+     * Get a specified line of data
+     *
+     * @access public
+     * @return $size bytes of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function gets($size)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return @fgets($this->fp, $size);
+    }
+
+    /**
+     * Read a specified amount of data. This is guaranteed to return,
+     * and has the added benefit of getting everything in one fread()
+     * chunk; if you know the size of the data you're getting
+     * beforehand, this is definitely the way to go.
+     *
+     * @param integer $size  The number of bytes to read from the socket.
+     * @access public
+     * @return $size bytes of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function read($size)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return @fread($this->fp, $size);
+    }
+
+    /**
+     * Write a specified amount of data.
+     *
+     * @param string  $data       Data to write.
+     * @param integer $blocksize  Amount of data to write at once.
+     *                            NULL means all at once.
+     *
+     * @access public
+     * @return mixed If the socket is not connected, returns an instance of PEAR_Error
+     *               If the write succeeds, returns the number of bytes written
+     *               If the write fails, returns false.
+     */
+    function write($data, $blocksize = null)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        if (is_null($blocksize) && !OS_WINDOWS) {
+            return @fwrite($this->fp, $data);
+        } else {
+            if (is_null($blocksize)) {
+                $blocksize = 1024;
+            }
+
+            $pos = 0;
+            $size = strlen($data);
+            while ($pos < $size) {
+                $written = @fwrite($this->fp, substr($data, $pos, $blocksize));
+                if ($written === false) {
+                    return false;
+                }
+                $pos += $written;
+            }
+
+            return $pos;
+        }
+    }
+
+    /**
+     * Write a line of data to the socket, followed by a trailing "\r\n".
+     *
+     * @access public
+     * @return mixed fputs result, or an error
+     */
+    function writeLine($data)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return fwrite($this->fp, $data . "\r\n");
+    }
+
+    /**
+     * Tests for end-of-file on a socket descriptor.
+     *
+     * Also returns true if the socket is disconnected.
+     *
+     * @access public
+     * @return bool
+     */
+    function eof()
+    {
+        return (!is_resource($this->fp) || feof($this->fp));
+    }
+
+    /**
+     * Reads a byte of data
+     *
+     * @access public
+     * @return 1 byte of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readByte()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        return ord(@fread($this->fp, 1));
+    }
+
+    /**
+     * Reads a word of data
+     *
+     * @access public
+     * @return 1 word of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readWord()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $buf = @fread($this->fp, 2);
+        return (ord($buf[0]) + (ord($buf[1]) << 8));
+    }
+
+    /**
+     * Reads an int of data
+     *
+     * @access public
+     * @return integer  1 int of data from the socket, or a PEAR_Error if
+     *                  not connected.
+     */
+    function readInt()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $buf = @fread($this->fp, 4);
+        return (ord($buf[0]) + (ord($buf[1]) << 8) +
+                (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
+    }
+
+    /**
+     * Reads a zero-terminated string of data
+     *
+     * @access public
+     * @return string, or a PEAR_Error if
+     *         not connected.
+     */
+    function readString()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $string = '';
+        while (($char = @fread($this->fp, 1)) != "\x00")  {
+            $string .= $char;
+        }
+        return $string;
+    }
+
+    /**
+     * Reads an IP Address and returns it in a dot formatted string
+     *
+     * @access public
+     * @return Dot formatted string, or a PEAR_Error if
+     *         not connected.
+     */
+    function readIPAddress()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $buf = @fread($this->fp, 4);
+        return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
+                       ord($buf[2]), ord($buf[3]));
+    }
+
+    /**
+     * Read until either the end of the socket or a newline, whichever
+     * comes first. Strips the trailing newline from the returned data.
+     *
+     * @access public
+     * @return All available data up to a newline, without that
+     *         newline, or until the end of the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readLine()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $line = '';
+        $timeout = time() + $this->timeout;
+        while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
+            $line .= @fgets($this->fp, $this->lineLength);
+            if (substr($line, -1) == "\n") {
+                return rtrim($line, "\r\n");
+            }
+        }
+        return $line;
+    }
+
+    /**
+     * Read until the socket closes, or until there is no more data in
+     * the inner PHP buffer. If the inner buffer is empty, in blocking
+     * mode we wait for at least 1 byte of data. Therefore, in
+     * blocking mode, if there is no data at all to be read, this
+     * function will never exit (unless the socket is closed on the
+     * remote end).
+     *
+     * @access public
+     *
+     * @return string  All data until the socket closes, or a PEAR_Error if
+     *                 not connected.
+     */
+    function readAll()
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $data = '';
+        while (!feof($this->fp)) {
+            $data .= @fread($this->fp, $this->lineLength);
+        }
+        return $data;
+    }
+
+    /**
+     * Runs the equivalent of the select() system call on the socket
+     * with a timeout specified by tv_sec and tv_usec.
+     *
+     * @param integer $state    Which of read/write/error to check for.
+     * @param integer $tv_sec   Number of seconds for timeout.
+     * @param integer $tv_usec  Number of microseconds for timeout.
+     *
+     * @access public
+     * @return False if select fails, integer describing which of read/write/error
+     *         are ready, or PEAR_Error if not connected.
+     */
+    function select($state, $tv_sec, $tv_usec = 0)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        $read = null;
+        $write = null;
+        $except = null;
+        if ($state & NET_SOCKET_READ) {
+            $read[] = $this->fp;
+        }
+        if ($state & NET_SOCKET_WRITE) {
+            $write[] = $this->fp;
+        }
+        if ($state & NET_SOCKET_ERROR) {
+            $except[] = $this->fp;
+        }
+        if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) {
+            return false;
+        }
+
+        $result = 0;
+        if (count($read)) {
+            $result |= NET_SOCKET_READ;
+        }
+        if (count($write)) {
+            $result |= NET_SOCKET_WRITE;
+        }
+        if (count($except)) {
+            $result |= NET_SOCKET_ERROR;
+        }
+        return $result;
+    }
+
+    /**
+     * Turns encryption on/off on a connected socket.
+     *
+     * @param bool    $enabled  Set this parameter to true to enable encryption
+     *                          and false to disable encryption.
+     * @param integer $type     Type of encryption. See
+     *                          http://se.php.net/manual/en/function.stream-socket-enable-crypto.php for values.
+     *
+     * @access public
+     * @return false on error, true on success and 0 if there isn't enough data and the
+     *         user should try again (non-blocking sockets only). A PEAR_Error object
+     *         is returned if the socket is not connected
+     */
+    function enableCrypto($enabled, $type)
+    {
+        if (version_compare(phpversion(), "5.1.0", ">=")) {
+            if (!is_resource($this->fp)) {
+                return $this->raiseError('not connected');
+            }
+            return @stream_socket_enable_crypto($this->fp, $enabled, $type);
+        } else {
+            return $this->raiseError('Net_Socket::enableCrypto() requires php version >= 5.1.0');
+        }
+    }
+
+}
diff --git a/WEB-INF/lib/pear/OS/Guess.php b/WEB-INF/lib/pear/OS/Guess.php
new file mode 100644 (file)
index 0000000..d3f2cc7
--- /dev/null
@@ -0,0 +1,338 @@
+<?php
+/**
+ * The OS_Guess class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Gregory Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Guess.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since PEAR 0.1
+ */
+
+// {{{ uname examples
+
+// php_uname() without args returns the same as 'uname -a', or a PHP-custom
+// string for Windows.
+// PHP versions prior to 4.3 return the uname of the host where PHP was built,
+// as of 4.3 it returns the uname of the host running the PHP code.
+//
+// PC RedHat Linux 7.1:
+// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
+//
+// PC Debian Potato:
+// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
+//
+// PC FreeBSD 3.3:
+// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000     root@example.com:/usr/src/sys/compile/CONFIG  i386
+//
+// PC FreeBSD 4.3:
+// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001     root@example.com:/usr/src/sys/compile/CONFIG  i386
+//
+// PC FreeBSD 4.5:
+// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb  6 23:59:23 CET 2002     root@example.com:/usr/src/sys/compile/CONFIG  i386
+//
+// PC FreeBSD 4.5 w/uname from GNU shellutils:
+// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb  i386 unknown
+//
+// HP 9000/712 HP-UX 10:
+// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
+//
+// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
+// HP-UX host B.10.10 A 9000/712 unknown
+//
+// IBM RS6000/550 AIX 4.3:
+// AIX host 3 4 000003531C00
+//
+// AIX 4.3 w/uname from GNU shellutils:
+// AIX host 3 4 000003531C00 unknown
+//
+// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
+// IRIX64 host 6.5 01091820 IP19 mips
+//
+// SGI Onyx IRIX 6.5:
+// IRIX64 host 6.5 01091820 IP19
+//
+// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
+// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
+//
+// SparcStation 20 Solaris 8:
+// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
+//
+// Mac OS X (Darwin)
+// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug  5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC  Power Macintosh
+//
+// Mac OS X early versions
+//
+
+// }}}
+
+/* TODO:
+ * - define endianness, to allow matchSignature("bigend") etc.
+ */
+
+/**
+ * Retrieves information about the current operating system
+ *
+ * This class uses php_uname() to grok information about the current OS
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Gregory Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class OS_Guess
+{
+    var $sysname;
+    var $nodename;
+    var $cpu;
+    var $release;
+    var $extra;
+
+    function OS_Guess($uname = null)
+    {
+        list($this->sysname,
+             $this->release,
+             $this->cpu,
+             $this->extra,
+             $this->nodename) = $this->parseSignature($uname);
+    }
+
+    function parseSignature($uname = null)
+    {
+        static $sysmap = array(
+            'HP-UX' => 'hpux',
+            'IRIX64' => 'irix',
+        );
+        static $cpumap = array(
+            'i586' => 'i386',
+            'i686' => 'i386',
+            'ppc' => 'powerpc',
+        );
+        if ($uname === null) {
+            $uname = php_uname();
+        }
+        $parts = preg_split('/\s+/', trim($uname));
+        $n = count($parts);
+
+        $release  = $machine = $cpu = '';
+        $sysname  = $parts[0];
+        $nodename = $parts[1];
+        $cpu      = $parts[$n-1];
+        $extra = '';
+        if ($cpu == 'unknown') {
+            $cpu = $parts[$n - 2];
+        }
+
+        switch ($sysname) {
+            case 'AIX' :
+                $release = "$parts[3].$parts[2]";
+                break;
+            case 'Windows' :
+                switch ($parts[1]) {
+                    case '95/98':
+                        $release = '9x';
+                        break;
+                    default:
+                        $release = $parts[1];
+                        break;
+                }
+                $cpu = 'i386';
+                break;
+            case 'Linux' :
+                $extra = $this->_detectGlibcVersion();
+                // use only the first two digits from the kernel version
+                $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
+                break;
+            case 'Mac' :
+                $sysname = 'darwin';
+                $nodename = $parts[2];
+                $release = $parts[3];
+                if ($cpu == 'Macintosh') {
+                    if ($parts[$n - 2] == 'Power') {
+                        $cpu = 'powerpc';
+                    }
+                }
+                break;
+            case 'Darwin' :
+                if ($cpu == 'Macintosh') {
+                    if ($parts[$n - 2] == 'Power') {
+                        $cpu = 'powerpc';
+                    }
+                }
+                $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
+                break;
+            default:
+                $release = preg_replace('/-.*/', '', $parts[2]);
+                break;
+        }
+
+        if (isset($sysmap[$sysname])) {
+            $sysname = $sysmap[$sysname];
+        } else {
+            $sysname = strtolower($sysname);
+        }
+        if (isset($cpumap[$cpu])) {
+            $cpu = $cpumap[$cpu];
+        }
+        return array($sysname, $release, $cpu, $extra, $nodename);
+    }
+
+    function _detectGlibcVersion()
+    {
+        static $glibc = false;
+        if ($glibc !== false) {
+            return $glibc; // no need to run this multiple times
+        }
+        $major = $minor = 0;
+        include_once "System.php";
+        // Use glibc's <features.h> header file to
+        // get major and minor version number:
+        if (@file_exists('/usr/include/features.h') &&
+              @is_readable('/usr/include/features.h')) {
+            if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
+                $features_file = fopen('/usr/include/features.h', 'rb');
+                while (!feof($features_file)) {
+                    $line = fgets($features_file, 8192);
+                    if (!$line || (strpos($line, '#define') === false)) {
+                        continue;
+                    }
+                    if (strpos($line, '__GLIBC__')) {
+                        // major version number #define __GLIBC__ version
+                        $line = preg_split('/\s+/', $line);
+                        $glibc_major = trim($line[2]);
+                        if (isset($glibc_minor)) {
+                            break;
+                        }
+                        continue;
+                    }
+
+                    if (strpos($line, '__GLIBC_MINOR__'))  {
+                        // got the minor version number
+                        // #define __GLIBC_MINOR__ version
+                        $line = preg_split('/\s+/', $line);
+                        $glibc_minor = trim($line[2]);
+                        if (isset($glibc_major)) {
+                            break;
+                        }
+                        continue;
+                    }
+                }
+                fclose($features_file);
+                if (!isset($glibc_major) || !isset($glibc_minor)) {
+                    return $glibc = '';
+                }
+                return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ;
+            } // no cpp
+
+            $tmpfile = System::mktemp("glibctest");
+            $fp = fopen($tmpfile, "w");
+            fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
+            fclose($fp);
+            $cpp = popen("/usr/bin/cpp $tmpfile", "r");
+            while ($line = fgets($cpp, 1024)) {
+                if ($line{0} == '#' || trim($line) == '') {
+                    continue;
+                }
+
+                if (list($major, $minor) = explode(' ', trim($line))) {
+                    break;
+                }
+            }
+            pclose($cpp);
+            unlink($tmpfile);
+        } // features.h
+
+        if (!($major && $minor) && @is_link('/lib/libc.so.6')) {
+            // Let's try reading the libc.so.6 symlink
+            if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) {
+                list($major, $minor) = explode('.', $matches[1]);
+            }
+        }
+
+        if (!($major && $minor)) {
+            return $glibc = '';
+        }
+
+        return $glibc = "glibc{$major}.{$minor}";
+    }
+
+    function getSignature()
+    {
+        if (empty($this->extra)) {
+            return "{$this->sysname}-{$this->release}-{$this->cpu}";
+        }
+        return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
+    }
+
+    function getSysname()
+    {
+        return $this->sysname;
+    }
+
+    function getNodename()
+    {
+        return $this->nodename;
+    }
+
+    function getCpu()
+    {
+        return $this->cpu;
+    }
+
+    function getRelease()
+    {
+        return $this->release;
+    }
+
+    function getExtra()
+    {
+        return $this->extra;
+    }
+
+    function matchSignature($match)
+    {
+        $fragments = is_array($match) ? $match : explode('-', $match);
+        $n = count($fragments);
+        $matches = 0;
+        if ($n > 0) {
+            $matches += $this->_matchFragment($fragments[0], $this->sysname);
+        }
+        if ($n > 1) {
+            $matches += $this->_matchFragment($fragments[1], $this->release);
+        }
+        if ($n > 2) {
+            $matches += $this->_matchFragment($fragments[2], $this->cpu);
+        }
+        if ($n > 3) {
+            $matches += $this->_matchFragment($fragments[3], $this->extra);
+        }
+        return ($matches == $n);
+    }
+
+    function _matchFragment($fragment, $value)
+    {
+        if (strcspn($fragment, '*?') < strlen($fragment)) {
+            $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/';
+            return preg_match($reg, $value);
+        }
+        return ($fragment == '*' || !strcasecmp($fragment, $value));
+    }
+
+}
+/*
+ * Local Variables:
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR.php b/WEB-INF/lib/pear/PEAR.php
new file mode 100644 (file)
index 0000000..2aa8525
--- /dev/null
@@ -0,0 +1,1063 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2010 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: PEAR.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN',     1);
+define('PEAR_ERROR_PRINT',      2);
+define('PEAR_ERROR_TRIGGER',    4);
+define('PEAR_ERROR_DIE',        8);
+define('PEAR_ERROR_CALLBACK',  16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+                    version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+    define('OS_WINDOWS', true);
+    define('OS_UNIX',    false);
+    define('PEAR_OS',    'Windows');
+} else {
+    define('OS_WINDOWS', false);
+    define('OS_UNIX',    true);
+    define('PEAR_OS',    'Unix'); // blatant assumption
+}
+
+$GLOBALS['_PEAR_default_error_mode']     = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options']  = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs']         = array();
+$GLOBALS['_PEAR_error_handler_stack']    = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes.  Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix).  Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters.  Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded.  If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @see        PEAR_Error
+ * @since      Class available since PHP 4.0.2
+ * @link        http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+    /**
+     * Whether to enable internal debug messages.
+     *
+     * @var     bool
+     * @access  private
+     */
+    var $_debug = false;
+
+    /**
+     * Default error mode for this object.
+     *
+     * @var     int
+     * @access  private
+     */
+    var $_default_error_mode = null;
+
+    /**
+     * Default error options used for this object when error mode
+     * is PEAR_ERROR_TRIGGER.
+     *
+     * @var     int
+     * @access  private
+     */
+    var $_default_error_options = null;
+
+    /**
+     * Default error handler (callback) for this object, if error mode is
+     * PEAR_ERROR_CALLBACK.
+     *
+     * @var     string
+     * @access  private
+     */
+    var $_default_error_handler = '';
+
+    /**
+     * Which class to use for error objects.
+     *
+     * @var     string
+     * @access  private
+     */
+    var $_error_class = 'PEAR_Error';
+
+    /**
+     * An array of expected errors.
+     *
+     * @var     array
+     * @access  private
+     */
+    var $_expected_errors = array();
+
+    /**
+     * Constructor.  Registers this object in
+     * $_PEAR_destructor_object_list for destructor emulation if a
+     * destructor object exists.
+     *
+     * @param string $error_class  (optional) which class to use for
+     *        error objects, defaults to PEAR_Error.
+     * @access public
+     * @return void
+     */
+    function PEAR($error_class = null)
+    {
+        $classname = strtolower(get_class($this));
+        if ($this->_debug) {
+            print "PEAR constructor called, class=$classname\n";
+        }
+
+        if ($error_class !== null) {
+            $this->_error_class = $error_class;
+        }
+
+        while ($classname && strcasecmp($classname, "pear")) {
+            $destructor = "_$classname";
+            if (method_exists($this, $destructor)) {
+                global $_PEAR_destructor_object_list;
+                $_PEAR_destructor_object_list[] = &$this;
+                if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+                    register_shutdown_function("_PEAR_call_destructors");
+                    $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+                }
+                break;
+            } else {
+                $classname = get_parent_class($classname);
+            }
+        }
+    }
+
+    /**
+     * Destructor (the emulated type of...).  Does nothing right now,
+     * but is included for forward compatibility, so subclass
+     * destructors should always call it.
+     *
+     * See the note in the class desciption about output from
+     * destructors.
+     *
+     * @access public
+     * @return void
+     */
+    function _PEAR() {
+        if ($this->_debug) {
+            printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+        }
+    }
+
+    /**
+    * If you have a class that's mostly/entirely static, and you need static
+    * properties, you can use this method to simulate them. Eg. in your method(s)
+    * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+    * You MUST use a reference, or they will not persist!
+    *
+    * @access public
+    * @param  string $class  The calling classname, to prevent clashes
+    * @param  string $var    The variable to retrieve.
+    * @return mixed   A reference to the variable. If not set it will be
+    *                 auto initialised to NULL.
+    */
+    function &getStaticProperty($class, $var)
+    {
+        static $properties;
+        if (!isset($properties[$class])) {
+            $properties[$class] = array();
+        }
+
+        if (!array_key_exists($var, $properties[$class])) {
+            $properties[$class][$var] = null;
+        }
+
+        return $properties[$class][$var];
+    }
+
+    /**
+    * Use this function to register a shutdown method for static
+    * classes.
+    *
+    * @access public
+    * @param  mixed $func  The function name (or array of class/method) to call
+    * @param  mixed $args  The arguments to pass to the function
+    * @return void
+    */
+    function registerShutdownFunc($func, $args = array())
+    {
+        // if we are called statically, there is a potential
+        // that no shutdown func is registered.  Bug #6445
+        if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+            register_shutdown_function("_PEAR_call_destructors");
+            $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+        }
+        $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+    }
+
+    /**
+     * Tell whether a value is a PEAR error.
+     *
+     * @param   mixed $data   the value to test
+     * @param   int   $code   if $data is an error object, return true
+     *                        only if $code is a string and
+     *                        $obj->getMessage() == $code or
+     *                        $code is an integer and $obj->getCode() == $code
+     * @access  public
+     * @return  bool    true if parameter is an error
+     */
+    function isError($data, $code = null)
+    {
+        if (!is_a($data, 'PEAR_Error')) {
+            return false;
+        }
+
+        if (is_null($code)) {
+            return true;
+        } elseif (is_string($code)) {
+            return $data->getMessage() == $code;
+        }
+
+        return $data->getCode() == $code;
+    }
+
+    /**
+     * Sets how errors generated by this object should be handled.
+     * Can be invoked both in objects and statically.  If called
+     * statically, setErrorHandling sets the default behaviour for all
+     * PEAR objects.  If called in an object, setErrorHandling sets
+     * the default behaviour for that object.
+     *
+     * @param int $mode
+     *        One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+     *        PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+     *        PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+     *
+     * @param mixed $options
+     *        When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+     *        of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+     *
+     *        When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+     *        to be the callback function or method.  A callback
+     *        function is a string with the name of the function, a
+     *        callback method is an array of two elements: the element
+     *        at index 0 is the object, and the element at index 1 is
+     *        the name of the method to call in the object.
+     *
+     *        When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+     *        a printf format string used when printing the error
+     *        message.
+     *
+     * @access public
+     * @return void
+     * @see PEAR_ERROR_RETURN
+     * @see PEAR_ERROR_PRINT
+     * @see PEAR_ERROR_TRIGGER
+     * @see PEAR_ERROR_DIE
+     * @see PEAR_ERROR_CALLBACK
+     * @see PEAR_ERROR_EXCEPTION
+     *
+     * @since PHP 4.0.5
+     */
+    function setErrorHandling($mode = null, $options = null)
+    {
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $setmode     = &$this->_default_error_mode;
+            $setoptions  = &$this->_default_error_options;
+        } else {
+            $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
+            $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
+        }
+
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $setmode = $mode;
+                $setoptions = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $setmode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $setoptions = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+    }
+
+    /**
+     * This method is used to tell which errors you expect to get.
+     * Expected errors are always returned with error mode
+     * PEAR_ERROR_RETURN.  Expected error codes are stored in a stack,
+     * and this method pushes a new element onto it.  The list of
+     * expected errors are in effect until they are popped off the
+     * stack with the popExpect() method.
+     *
+     * Note that this method can not be called statically
+     *
+     * @param mixed $code a single error code or an array of error codes to expect
+     *
+     * @return int     the new depth of the "expected errors" stack
+     * @access public
+     */
+    function expectError($code = '*')
+    {
+        if (is_array($code)) {
+            array_push($this->_expected_errors, $code);
+        } else {
+            array_push($this->_expected_errors, array($code));
+        }
+        return count($this->_expected_errors);
+    }
+
+    /**
+     * This method pops one element off the expected error codes
+     * stack.
+     *
+     * @return array   the list of error codes that were popped
+     */
+    function popExpect()
+    {
+        return array_pop($this->_expected_errors);
+    }
+
+    /**
+     * This method checks unsets an error code if available
+     *
+     * @param mixed error code
+     * @return bool true if the error code was unset, false otherwise
+     * @access private
+     * @since PHP 4.3.0
+     */
+    function _checkDelExpect($error_code)
+    {
+        $deleted = false;
+        foreach ($this->_expected_errors as $key => $error_array) {
+            if (in_array($error_code, $error_array)) {
+                unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+                $deleted = true;
+            }
+
+            // clean up empty arrays
+            if (0 == count($this->_expected_errors[$key])) {
+                unset($this->_expected_errors[$key]);
+            }
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * This method deletes all occurences of the specified element from
+     * the expected error codes stack.
+     *
+     * @param  mixed $error_code error code that should be deleted
+     * @return mixed list of error codes that were deleted or error
+     * @access public
+     * @since PHP 4.3.0
+     */
+    function delExpect($error_code)
+    {
+        $deleted = false;
+        if ((is_array($error_code) && (0 != count($error_code)))) {
+            // $error_code is a non-empty array here; we walk through it trying
+            // to unset all values
+            foreach ($error_code as $key => $error) {
+                $deleted =  $this->_checkDelExpect($error) ? true : false;
+            }
+
+            return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+        } elseif (!empty($error_code)) {
+            // $error_code comes alone, trying to unset it
+            if ($this->_checkDelExpect($error_code)) {
+                return true;
+            }
+
+            return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+        }
+
+        // $error_code is empty
+        return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+    }
+
+    /**
+     * This method is a wrapper that returns an instance of the
+     * configured error class with this object's default error
+     * handling applied.  If the $mode and $options parameters are not
+     * specified, the object's defaults are used.
+     *
+     * @param mixed $message a text error message or a PEAR error object
+     *
+     * @param int $code      a numeric error code (it is up to your class
+     *                  to define these if you want to use codes)
+     *
+     * @param int $mode      One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+     *                  PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+     *                  PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+     *
+     * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+     *                  specifies the PHP-internal error level (one of
+     *                  E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+     *                  If $mode is PEAR_ERROR_CALLBACK, this
+     *                  parameter specifies the callback function or
+     *                  method.  In other error modes this parameter
+     *                  is ignored.
+     *
+     * @param string $userinfo If you need to pass along for example debug
+     *                  information, this parameter is meant for that.
+     *
+     * @param string $error_class The returned error object will be
+     *                  instantiated from this class, if specified.
+     *
+     * @param bool $skipmsg If true, raiseError will only pass error codes,
+     *                  the error message parameter will be dropped.
+     *
+     * @access public
+     * @return object   a PEAR error object
+     * @see PEAR::setErrorHandling
+     * @since PHP 4.0.5
+     */
+    function &raiseError($message = null,
+                         $code = null,
+                         $mode = null,
+                         $options = null,
+                         $userinfo = null,
+                         $error_class = null,
+                         $skipmsg = false)
+    {
+        // The error is yet a PEAR error object
+        if (is_object($message)) {
+            $code        = $message->getCode();
+            $userinfo    = $message->getUserInfo();
+            $error_class = $message->getType();
+            $message->error_message_prefix = '';
+            $message     = $message->getMessage();
+        }
+
+        if (
+            isset($this) &&
+            isset($this->_expected_errors) &&
+            count($this->_expected_errors) > 0 &&
+            count($exp = end($this->_expected_errors))
+        ) {
+            if ($exp[0] == "*" ||
+                (is_int(reset($exp)) && in_array($code, $exp)) ||
+                (is_string(reset($exp)) && in_array($message, $exp))
+            ) {
+                $mode = PEAR_ERROR_RETURN;
+            }
+        }
+
+        // No mode given, try global ones
+        if ($mode === null) {
+            // Class error handler
+            if (isset($this) && isset($this->_default_error_mode)) {
+                $mode    = $this->_default_error_mode;
+                $options = $this->_default_error_options;
+            // Global error handler
+            } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+                $mode    = $GLOBALS['_PEAR_default_error_mode'];
+                $options = $GLOBALS['_PEAR_default_error_options'];
+            }
+        }
+
+        if ($error_class !== null) {
+            $ec = $error_class;
+        } elseif (isset($this) && isset($this->_error_class)) {
+            $ec = $this->_error_class;
+        } else {
+            $ec = 'PEAR_Error';
+        }
+
+        if (intval(PHP_VERSION) < 5) {
+            // little non-eval hack to fix bug #12147
+            include 'PEAR/FixPHP5PEARWarnings.php';
+            return $a;
+        }
+
+        if ($skipmsg) {
+            $a = new $ec($code, $mode, $options, $userinfo);
+        } else {
+            $a = new $ec($message, $code, $mode, $options, $userinfo);
+        }
+
+        return $a;
+    }
+
+    /**
+     * Simpler form of raiseError with fewer options.  In most cases
+     * message, code and userinfo are enough.
+     *
+     * @param mixed $message a text error message or a PEAR error object
+     *
+     * @param int $code      a numeric error code (it is up to your class
+     *                  to define these if you want to use codes)
+     *
+     * @param string $userinfo If you need to pass along for example debug
+     *                  information, this parameter is meant for that.
+     *
+     * @access public
+     * @return object   a PEAR error object
+     * @see PEAR::raiseError
+     */
+    function &throwError($message = null, $code = null, $userinfo = null)
+    {
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $a = &$this->raiseError($message, $code, null, null, $userinfo);
+            return $a;
+        }
+
+        $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+        return $a;
+    }
+
+    function staticPushErrorHandling($mode, $options = null)
+    {
+        $stack       = &$GLOBALS['_PEAR_error_handler_stack'];
+        $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
+        $def_options = &$GLOBALS['_PEAR_default_error_options'];
+        $stack[] = array($def_mode, $def_options);
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $def_mode = $mode;
+                $def_options = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $def_mode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $def_options = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+        $stack[] = array($mode, $options);
+        return true;
+    }
+
+    function staticPopErrorHandling()
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
+        $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
+        array_pop($stack);
+        list($mode, $options) = $stack[sizeof($stack) - 1];
+        array_pop($stack);
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $setmode = $mode;
+                $setoptions = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $setmode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $setoptions = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * Push a new error handler on top of the error handler options stack. With this
+     * you can easily override the actual error handler for some code and restore
+     * it later with popErrorHandling.
+     *
+     * @param mixed $mode (same as setErrorHandling)
+     * @param mixed $options (same as setErrorHandling)
+     *
+     * @return bool Always true
+     *
+     * @see PEAR::setErrorHandling
+     */
+    function pushErrorHandling($mode, $options = null)
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $def_mode    = &$this->_default_error_mode;
+            $def_options = &$this->_default_error_options;
+        } else {
+            $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
+            $def_options = &$GLOBALS['_PEAR_default_error_options'];
+        }
+        $stack[] = array($def_mode, $def_options);
+
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $this->setErrorHandling($mode, $options);
+        } else {
+            PEAR::setErrorHandling($mode, $options);
+        }
+        $stack[] = array($mode, $options);
+        return true;
+    }
+
+    /**
+    * Pop the last error handler used
+    *
+    * @return bool Always true
+    *
+    * @see PEAR::pushErrorHandling
+    */
+    function popErrorHandling()
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        array_pop($stack);
+        list($mode, $options) = $stack[sizeof($stack) - 1];
+        array_pop($stack);
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $this->setErrorHandling($mode, $options);
+        } else {
+            PEAR::setErrorHandling($mode, $options);
+        }
+        return true;
+    }
+
+    /**
+    * OS independant PHP extension load. Remember to take care
+    * on the correct extension name for case sensitive OSes.
+    *
+    * @param string $ext The extension name
+    * @return bool Success or not on the dl() call
+    */
+    function loadExtension($ext)
+    {
+        if (extension_loaded($ext)) {
+            return true;
+        }
+
+        // if either returns true dl() will produce a FATAL error, stop that
+        if (
+            function_exists('dl') === false ||
+            ini_get('enable_dl') != 1 ||
+            ini_get('safe_mode') == 1
+        ) {
+            return false;
+        }
+
+        if (OS_WINDOWS) {
+            $suffix = '.dll';
+        } elseif (PHP_OS == 'HP-UX') {
+            $suffix = '.sl';
+        } elseif (PHP_OS == 'AIX') {
+            $suffix = '.a';
+        } elseif (PHP_OS == 'OSX') {
+            $suffix = '.bundle';
+        } else {
+            $suffix = '.so';
+        }
+
+        return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+    }
+}
+
+if (PEAR_ZE2) {
+    include_once 'PEAR5.php';
+}
+
+function _PEAR_call_destructors()
+{
+    global $_PEAR_destructor_object_list;
+    if (is_array($_PEAR_destructor_object_list) &&
+        sizeof($_PEAR_destructor_object_list))
+    {
+        reset($_PEAR_destructor_object_list);
+        if (PEAR_ZE2) {
+            $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo');
+        } else {
+            $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
+        }
+
+        if ($destructLifoExists) {
+            $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+        }
+
+        while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+            $classname = get_class($objref);
+            while ($classname) {
+                $destructor = "_$classname";
+                if (method_exists($objref, $destructor)) {
+                    $objref->$destructor();
+                    break;
+                } else {
+                    $classname = get_parent_class($classname);
+                }
+            }
+        }
+        // Empty the object list to ensure that destructors are
+        // not called more than once.
+        $_PEAR_destructor_object_list = array();
+    }
+
+    // Now call the shutdown functions
+    if (
+        isset($GLOBALS['_PEAR_shutdown_funcs']) &&
+        is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
+        !empty($GLOBALS['_PEAR_shutdown_funcs'])
+    ) {
+        foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+            call_user_func_array($value[0], $value[1]);
+        }
+    }
+}
+
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is supserseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Gregory Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see        PEAR::raiseError(), PEAR::throwError()
+ * @since      Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+    var $error_message_prefix = '';
+    var $mode                 = PEAR_ERROR_RETURN;
+    var $level                = E_USER_NOTICE;
+    var $code                 = -1;
+    var $message              = '';
+    var $userinfo             = '';
+    var $backtrace            = null;
+
+    /**
+     * PEAR_Error constructor
+     *
+     * @param string $message  message
+     *
+     * @param int $code     (optional) error code
+     *
+     * @param int $mode     (optional) error mode, one of: PEAR_ERROR_RETURN,
+     * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+     * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+     *
+     * @param mixed $options   (optional) error level, _OR_ in the case of
+     * PEAR_ERROR_CALLBACK, the callback function or object/method
+     * tuple.
+     *
+     * @param string $userinfo (optional) additional user/debug info
+     *
+     * @access public
+     *
+     */
+    function PEAR_Error($message = 'unknown error', $code = null,
+                        $mode = null, $options = null, $userinfo = null)
+    {
+        if ($mode === null) {
+            $mode = PEAR_ERROR_RETURN;
+        }
+        $this->message   = $message;
+        $this->code      = $code;
+        $this->mode      = $mode;
+        $this->userinfo  = $userinfo;
+
+        if (PEAR_ZE2) {
+            $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
+        } else {
+            $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
+        }
+
+        if (!$skiptrace) {
+            $this->backtrace = debug_backtrace();
+            if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
+                unset($this->backtrace[0]['object']);
+            }
+        }
+
+        if ($mode & PEAR_ERROR_CALLBACK) {
+            $this->level = E_USER_NOTICE;
+            $this->callback = $options;
+        } else {
+            if ($options === null) {
+                $options = E_USER_NOTICE;
+            }
+
+            $this->level = $options;
+            $this->callback = null;
+        }
+
+        if ($this->mode & PEAR_ERROR_PRINT) {
+            if (is_null($options) || is_int($options)) {
+                $format = "%s";
+            } else {
+                $format = $options;
+            }
+
+            printf($format, $this->getMessage());
+        }
+
+        if ($this->mode & PEAR_ERROR_TRIGGER) {
+            trigger_error($this->getMessage(), $this->level);
+        }
+
+        if ($this->mode & PEAR_ERROR_DIE) {
+            $msg = $this->getMessage();
+            if (is_null($options) || is_int($options)) {
+                $format = "%s";
+                if (substr($msg, -1) != "\n") {
+                    $msg .= "\n";
+                }
+            } else {
+                $format = $options;
+            }
+            die(sprintf($format, $msg));
+        }
+
+        if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
+            call_user_func($this->callback, $this);
+        }
+
+        if ($this->mode & PEAR_ERROR_EXCEPTION) {
+            trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+            eval('$e = new Exception($this->message, $this->code);throw($e);');
+        }
+    }
+
+    /**
+     * Get the error mode from an error object.
+     *
+     * @return int error mode
+     * @access public
+     */
+    function getMode()
+    {
+        return $this->mode;
+    }
+
+    /**
+     * Get the callback function/method from an error object.
+     *
+     * @return mixed callback function or object/method array
+     * @access public
+     */
+    function getCallback()
+    {
+        return $this->callback;
+    }
+
+    /**
+     * Get the error message from an error object.
+     *
+     * @return  string  full error message
+     * @access public
+     */
+    function getMessage()
+    {
+        return ($this->error_message_prefix . $this->message);
+    }
+
+    /**
+     * Get error code from an error object
+     *
+     * @return int error code
+     * @access public
+     */
+     function getCode()
+     {
+        return $this->code;
+     }
+
+    /**
+     * Get the name of this error/exception.
+     *
+     * @return string error/exception name (type)
+     * @access public
+     */
+    function getType()
+    {
+        return get_class($this);
+    }
+
+    /**
+     * Get additional user-supplied information.
+     *
+     * @return string user-supplied information
+     * @access public
+     */
+    function getUserInfo()
+    {
+        return $this->userinfo;
+    }
+
+    /**
+     * Get additional debug information supplied by the application.
+     *
+     * @return string debug information
+     * @access public
+     */
+    function getDebugInfo()
+    {
+        return $this->getUserInfo();
+    }
+
+    /**
+     * Get the call backtrace from where the error was generated.
+     * Supported with PHP 4.3.0 or newer.
+     *
+     * @param int $frame (optional) what frame to fetch
+     * @return array Backtrace, or NULL if not available.
+     * @access public
+     */
+    function getBacktrace($frame = null)
+    {
+        if (defined('PEAR_IGNORE_BACKTRACE')) {
+            return null;
+        }
+        if ($frame === null) {
+            return $this->backtrace;
+        }
+        return $this->backtrace[$frame];
+    }
+
+    function addUserInfo($info)
+    {
+        if (empty($this->userinfo)) {
+            $this->userinfo = $info;
+        } else {
+            $this->userinfo .= " ** $info";
+        }
+    }
+
+    function __toString()
+    {
+        return $this->getMessage();
+    }
+
+    /**
+     * Make a string representation of this object.
+     *
+     * @return string a string with an object summary
+     * @access public
+     */
+    function toString()
+    {
+        $modes = array();
+        $levels = array(E_USER_NOTICE  => 'notice',
+                        E_USER_WARNING => 'warning',
+                        E_USER_ERROR   => 'error');
+        if ($this->mode & PEAR_ERROR_CALLBACK) {
+            if (is_array($this->callback)) {
+                $callback = (is_object($this->callback[0]) ?
+                    strtolower(get_class($this->callback[0])) :
+                    $this->callback[0]) . '::' .
+                    $this->callback[1];
+            } else {
+                $callback = $this->callback;
+            }
+            return sprintf('[%s: message="%s" code=%d mode=callback '.
+                           'callback=%s prefix="%s" info="%s"]',
+                           strtolower(get_class($this)), $this->message, $this->code,
+                           $callback, $this->error_message_prefix,
+                           $this->userinfo);
+        }
+        if ($this->mode & PEAR_ERROR_PRINT) {
+            $modes[] = 'print';
+        }
+        if ($this->mode & PEAR_ERROR_TRIGGER) {
+            $modes[] = 'trigger';
+        }
+        if ($this->mode & PEAR_ERROR_DIE) {
+            $modes[] = 'die';
+        }
+        if ($this->mode & PEAR_ERROR_RETURN) {
+            $modes[] = 'return';
+        }
+        return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+                       'prefix="%s" info="%s"]',
+                       strtolower(get_class($this)), $this->message, $this->code,
+                       implode("|", $modes), $levels[$this->level],
+                       $this->error_message_prefix,
+                       $this->userinfo);
+    }
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/WEB-INF/lib/pear/PEAR/Autoloader.php b/WEB-INF/lib/pear/PEAR/Autoloader.php
new file mode 100644 (file)
index 0000000..0ed707e
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Class auto-loader
+ *
+ * PHP versions 4
+
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Autoloader.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader
+ * @since      File available since Release 0.1
+ * @deprecated File deprecated in Release 1.4.0a1
+ */
+
+// /* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+if (!extension_loaded("overload")) {
+    // die hard without ext/overload
+    die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader");
+}
+
+/**
+ * Include for PEAR_Error and PEAR classes
+ */
+require_once "PEAR.php";
+
+/**
+ * This class is for objects where you want to separate the code for
+ * some methods into separate classes.  This is useful if you have a
+ * class with not-frequently-used methods that contain lots of code
+ * that you would like to avoid always parsing.
+ *
+ * The PEAR_Autoloader class provides autoloading and aggregation.
+ * The autoloading lets you set up in which classes the separated
+ * methods are found.  Aggregation is the technique used to import new
+ * methods, an instance of each class providing separated methods is
+ * stored and called every time the aggregated method is called.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader
+ * @since      File available since Release 0.1
+ * @deprecated File deprecated in Release 1.4.0a1
+ */
+class PEAR_Autoloader extends PEAR
+{
+    // {{{ properties
+
+    /**
+     * Map of methods and classes where they are defined
+     *
+     * @var array
+     *
+     * @access private
+     */
+    var $_autoload_map = array();
+
+    /**
+     * Map of methods and aggregate objects
+     *
+     * @var array
+     *
+     * @access private
+     */
+    var $_method_map = array();
+
+    // }}}
+    // {{{ addAutoload()
+
+    /**
+     * Add one or more autoload entries.
+     *
+     * @param string $method     which method to autoload
+     *
+     * @param string $classname  (optional) which class to find the method in.
+     *                           If the $method parameter is an array, this
+     *                           parameter may be omitted (and will be ignored
+     *                           if not), and the $method parameter will be
+     *                           treated as an associative array with method
+     *                           names as keys and class names as values.
+     *
+     * @return void
+     *
+     * @access public
+     */
+    function addAutoload($method, $classname = null)
+    {
+        if (is_array($method)) {
+            array_walk($method, create_function('$a,&$b', '$b = strtolower($b);'));
+            $this->_autoload_map = array_merge($this->_autoload_map, $method);
+        } else {
+            $this->_autoload_map[strtolower($method)] = $classname;
+        }
+    }
+
+    // }}}
+    // {{{ removeAutoload()
+
+    /**
+     * Remove an autoload entry.
+     *
+     * @param string $method  which method to remove the autoload entry for
+     *
+     * @return bool TRUE if an entry was removed, FALSE if not
+     *
+     * @access public
+     */
+    function removeAutoload($method)
+    {
+        $method = strtolower($method);
+        $ok = isset($this->_autoload_map[$method]);
+        unset($this->_autoload_map[$method]);
+        return $ok;
+    }
+
+    // }}}
+    // {{{ addAggregateObject()
+
+    /**
+     * Add an aggregate object to this object.  If the specified class
+     * is not defined, loading it will be attempted following PEAR's
+     * file naming scheme.  All the methods in the class will be
+     * aggregated, except private ones (name starting with an
+     * underscore) and constructors.
+     *
+     * @param string $classname  what class to instantiate for the object.
+     *
+     * @return void
+     *
+     * @access public
+     */
+    function addAggregateObject($classname)
+    {
+        $classname = strtolower($classname);
+        if (!class_exists($classname)) {
+            $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname);
+            include_once $include_file;
+        }
+        $obj =& new $classname;
+        $methods = get_class_methods($classname);
+        foreach ($methods as $method) {
+            // don't import priviate methods and constructors
+            if ($method{0} != '_' && $method != $classname) {
+                $this->_method_map[$method] = $obj;
+            }
+        }
+    }
+
+    // }}}
+    // {{{ removeAggregateObject()
+
+    /**
+     * Remove an aggregate object.
+     *
+     * @param string $classname  the class of the object to remove
+     *
+     * @return bool  TRUE if an object was removed, FALSE if not
+     *
+     * @access public
+     */
+    function removeAggregateObject($classname)
+    {
+        $ok = false;
+        $classname = strtolower($classname);
+        reset($this->_method_map);
+        while (list($method, $obj) = each($this->_method_map)) {
+            if (is_a($obj, $classname)) {
+                unset($this->_method_map[$method]);
+                $ok = true;
+            }
+        }
+        return $ok;
+    }
+
+    // }}}
+    // {{{ __call()
+
+    /**
+     * Overloaded object call handler, called each time an
+     * undefined/aggregated method is invoked.  This method repeats
+     * the call in the right aggregate object and passes on the return
+     * value.
+     *
+     * @param string $method  which method that was called
+     *
+     * @param string $args    An array of the parameters passed in the
+     *                        original call
+     *
+     * @return mixed  The return value from the aggregated method, or a PEAR
+     *                error if the called method was unknown.
+     */
+    function __call($method, $args, &$retval)
+    {
+        $method = strtolower($method);
+        if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) {
+            $this->addAggregateObject($this->_autoload_map[$method]);
+        }
+        if (isset($this->_method_map[$method])) {
+            $retval = call_user_func_array(array($this->_method_map[$method], $method), $args);
+            return true;
+        }
+        return false;
+    }
+
+    // }}}
+}
+
+overload("PEAR_Autoloader");
+
+?>
diff --git a/WEB-INF/lib/pear/PEAR/Builder.php b/WEB-INF/lib/pear/PEAR/Builder.php
new file mode 100644 (file)
index 0000000..90f3a14
--- /dev/null
@@ -0,0 +1,489 @@
+<?php
+/**
+ * PEAR_Builder for building PHP extensions (PECL packages)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Builder.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ *
+ * TODO: log output parameters in PECL command line
+ * TODO: msdev path in configuration
+ */
+
+/**
+ * Needed for extending PEAR_Builder
+ */
+require_once 'PEAR/Common.php';
+require_once 'PEAR/PackageFile.php';
+
+/**
+ * Class to handle building (compiling) extensions.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since PHP 4.0.2
+ * @see        http://pear.php.net/manual/en/core.ppm.pear-builder.php
+ */
+class PEAR_Builder extends PEAR_Common
+{
+    var $php_api_version = 0;
+    var $zend_module_api_no = 0;
+    var $zend_extension_api_no = 0;
+
+    var $extensions_built = array();
+
+    /**
+     * @var string Used for reporting when it is not possible to pass function
+     *             via extra parameter, e.g. log, msdevCallback
+     */
+    var $current_callback = null;
+
+    // used for msdev builds
+    var $_lastline = null;
+    var $_firstline = null;
+
+    /**
+     * PEAR_Builder constructor.
+     *
+     * @param object $ui user interface object (instance of PEAR_Frontend_*)
+     *
+     * @access public
+     */
+    function PEAR_Builder(&$ui)
+    {
+        parent::PEAR_Common();
+        $this->setFrontendObject($ui);
+    }
+
+    /**
+     * Build an extension from source on windows.
+     * requires msdev
+     */
+    function _build_win32($descfile, $callback = null)
+    {
+        if (is_object($descfile)) {
+            $pkg = $descfile;
+            $descfile = $pkg->getPackageFile();
+        } else {
+            $pf = &new PEAR_PackageFile($this->config, $this->debug);
+            $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+            if (PEAR::isError($pkg)) {
+                return $pkg;
+            }
+        }
+        $dir = dirname($descfile);
+        $old_cwd = getcwd();
+
+        if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) {
+            return $this->raiseError("could not chdir to $dir");
+        }
+
+        // packages that were in a .tar have the packagefile in this directory
+        $vdir = $pkg->getPackage() . '-' . $pkg->getVersion();
+        if (file_exists($dir) && is_dir($vdir)) {
+            if (!chdir($vdir)) {
+                return $this->raiseError("could not chdir to " . realpath($vdir));
+            }
+
+            $dir = getcwd();
+        }
+
+        $this->log(2, "building in $dir");
+
+        $dsp = $pkg->getPackage().'.dsp';
+        if (!file_exists("$dir/$dsp")) {
+            return $this->raiseError("The DSP $dsp does not exist.");
+        }
+        // XXX TODO: make release build type configurable
+        $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"';
+
+        $err = $this->_runCommand($command, array(&$this, 'msdevCallback'));
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+
+        // figure out the build platform and type
+        $platform = 'Win32';
+        $buildtype = 'Release';
+        if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) {
+            $platform = $matches[1];
+            $buildtype = $matches[2];
+        }
+
+        if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) {
+            if ($matches[2]) {
+                // there were errors in the build
+                return $this->raiseError("There were errors during compilation.");
+            }
+            $out = $matches[1];
+        } else {
+            return $this->raiseError("Did not understand the completion status returned from msdev.exe.");
+        }
+
+        // msdev doesn't tell us the output directory :/
+        // open the dsp, find /out and use that directory
+        $dsptext = join(file($dsp),'');
+
+        // this regex depends on the build platform and type having been
+        // correctly identified above.
+        $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'.
+                    $pkg->getPackage().'\s-\s'.
+                    $platform.'\s'.
+                    $buildtype.'").*?'.
+                    '\/out:"(.*?)"/is';
+
+        if ($dsptext && preg_match($regex, $dsptext, $matches)) {
+            // what we get back is a relative path to the output file itself.
+            $outfile = realpath($matches[2]);
+        } else {
+            return $this->raiseError("Could not retrieve output information from $dsp.");
+        }
+        // realpath returns false if the file doesn't exist
+        if ($outfile && copy($outfile, "$dir/$out")) {
+            $outfile = "$dir/$out";
+        }
+
+        $built_files[] = array(
+            'file' => "$outfile",
+            'php_api' => $this->php_api_version,
+            'zend_mod_api' => $this->zend_module_api_no,
+            'zend_ext_api' => $this->zend_extension_api_no,
+            );
+
+        return $built_files;
+    }
+    // }}}
+
+    // {{{ msdevCallback()
+    function msdevCallback($what, $data)
+    {
+        if (!$this->_firstline)
+            $this->_firstline = $data;
+        $this->_lastline = $data;
+        call_user_func($this->current_callback, $what, $data);
+    }
+
+    /**
+     * @param string
+     * @param string
+     * @param array
+     * @access private
+     */
+    function _harvestInstDir($dest_prefix, $dirname, &$built_files)
+    {
+        $d = opendir($dirname);
+        if (!$d)
+            return false;
+
+        $ret = true;
+        while (($ent = readdir($d)) !== false) {
+            if ($ent{0} == '.')
+                continue;
+
+            $full = $dirname . DIRECTORY_SEPARATOR . $ent;
+            if (is_dir($full)) {
+                if (!$this->_harvestInstDir(
+                        $dest_prefix . DIRECTORY_SEPARATOR . $ent,
+                        $full, $built_files)) {
+                    $ret = false;
+                    break;
+                }
+            } else {
+                $dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent;
+                $built_files[] = array(
+                        'file' => $full,
+                        'dest' => $dest,
+                        'php_api' => $this->php_api_version,
+                        'zend_mod_api' => $this->zend_module_api_no,
+                        'zend_ext_api' => $this->zend_extension_api_no,
+                        );
+            }
+        }
+        closedir($d);
+        return $ret;
+    }
+
+    /**
+     * Build an extension from source.  Runs "phpize" in the source
+     * directory, but compiles in a temporary directory
+     * (TMPDIR/pear-build-USER/PACKAGE-VERSION).
+     *
+     * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or
+     *               a PEAR_PackageFile object
+     *
+     * @param mixed $callback callback function used to report output,
+     * see PEAR_Builder::_runCommand for details
+     *
+     * @return array an array of associative arrays with built files,
+     * format:
+     * array( array( 'file' => '/path/to/ext.so',
+     *               'php_api' => YYYYMMDD,
+     *               'zend_mod_api' => YYYYMMDD,
+     *               'zend_ext_api' => YYYYMMDD ),
+     *        ... )
+     *
+     * @access public
+     *
+     * @see PEAR_Builder::_runCommand
+     */
+    function build($descfile, $callback = null)
+    {
+        if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/',
+                       $this->config->get('php_bin'), $matches)) {
+            if (isset($matches[2]) && strlen($matches[2]) &&
+                trim($matches[2]) != trim($this->config->get('php_prefix'))) {
+                $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') .
+                           ' appears to have a prefix ' . $matches[2] . ', but' .
+                           ' config variable php_prefix does not match');
+            }
+
+            if (isset($matches[3]) && strlen($matches[3]) &&
+                trim($matches[3]) != trim($this->config->get('php_suffix'))) {
+                $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') .
+                           ' appears to have a suffix ' . $matches[3] . ', but' .
+                           ' config variable php_suffix does not match');
+            }
+        }
+
+        $this->current_callback = $callback;
+        if (PEAR_OS == "Windows") {
+            return $this->_build_win32($descfile, $callback);
+        }
+
+        if (PEAR_OS != 'Unix') {
+            return $this->raiseError("building extensions not supported on this platform");
+        }
+
+        if (is_object($descfile)) {
+            $pkg = $descfile;
+            $descfile = $pkg->getPackageFile();
+            if (is_a($pkg, 'PEAR_PackageFile_v1')) {
+                $dir = dirname($descfile);
+            } else {
+                $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName();
+                // automatically delete at session end
+                $this->addTempFile($dir);
+            }
+        } else {
+            $pf = &new PEAR_PackageFile($this->config);
+            $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+            if (PEAR::isError($pkg)) {
+                return $pkg;
+            }
+            $dir = dirname($descfile);
+        }
+
+        // Find config. outside of normal path - e.g. config.m4
+        foreach (array_keys($pkg->getInstallationFileList()) as $item) {
+          if (stristr(basename($item), 'config.m4') && dirname($item) != '.') {
+            $dir .= DIRECTORY_SEPARATOR . dirname($item);
+            break;
+          }
+        }
+
+        $old_cwd = getcwd();
+        if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) {
+            return $this->raiseError("could not chdir to $dir");
+        }
+
+        $vdir = $pkg->getPackage() . '-' . $pkg->getVersion();
+        if (is_dir($vdir)) {
+            chdir($vdir);
+        }
+
+        $dir = getcwd();
+        $this->log(2, "building in $dir");
+        putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH'));
+        $err = $this->_runCommand($this->config->get('php_prefix')
+                                . "phpize" .
+                                $this->config->get('php_suffix'),
+                                array(&$this, 'phpizeCallback'));
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+
+        if (!$err) {
+            return $this->raiseError("`phpize' failed");
+        }
+
+        // {{{ start of interactive part
+        $configure_command = "$dir/configure";
+        $configure_options = $pkg->getConfigureOptions();
+        if ($configure_options) {
+            foreach ($configure_options as $o) {
+                $default = array_key_exists('default', $o) ? $o['default'] : null;
+                list($r) = $this->ui->userDialog('build',
+                                                 array($o['prompt']),
+                                                 array('text'),
+                                                 array($default));
+                if (substr($o['name'], 0, 5) == 'with-' &&
+                    ($r == 'yes' || $r == 'autodetect')) {
+                    $configure_command .= " --$o[name]";
+                } else {
+                    $configure_command .= " --$o[name]=".trim($r);
+                }
+            }
+        }
+        // }}} end of interactive part
+
+        // FIXME make configurable
+        if (!$user=getenv('USER')) {
+            $user='defaultuser';
+        }
+
+        $tmpdir = $this->config->get('temp_dir');
+        $build_basedir = System::mktemp(' -t "' . $tmpdir . '" -d "pear-build-' . $user . '"');
+        $build_dir = "$build_basedir/$vdir";
+        $inst_dir = "$build_basedir/install-$vdir";
+        $this->log(1, "building in $build_dir");
+        if (is_dir($build_dir)) {
+            System::rm(array('-rf', $build_dir));
+        }
+
+        if (!System::mkDir(array('-p', $build_dir))) {
+            return $this->raiseError("could not create build dir: $build_dir");
+        }
+
+        $this->addTempFile($build_dir);
+        if (!System::mkDir(array('-p', $inst_dir))) {
+            return $this->raiseError("could not create temporary install dir: $inst_dir");
+        }
+        $this->addTempFile($inst_dir);
+
+        $make_command = getenv('MAKE') ? getenv('MAKE') : 'make';
+
+        $to_run = array(
+            $configure_command,
+            $make_command,
+            "$make_command INSTALL_ROOT=\"$inst_dir\" install",
+            "find \"$inst_dir\" | xargs ls -dils"
+            );
+        if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) {
+            return $this->raiseError("could not chdir to $build_dir");
+        }
+        putenv('PHP_PEAR_VERSION=1.9.4');
+        foreach ($to_run as $cmd) {
+            $err = $this->_runCommand($cmd, $callback);
+            if (PEAR::isError($err)) {
+                chdir($old_cwd);
+                return $err;
+            }
+            if (!$err) {
+                chdir($old_cwd);
+                return $this->raiseError("`$cmd' failed");
+            }
+        }
+        if (!($dp = opendir("modules"))) {
+            chdir($old_cwd);
+            return $this->raiseError("no `modules' directory found");
+        }
+        $built_files = array();
+        $prefix = exec($this->config->get('php_prefix')
+                        . "php-config" .
+                       $this->config->get('php_suffix') . " --prefix");
+        $this->_harvestInstDir($prefix, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files);
+        chdir($old_cwd);
+        return $built_files;
+    }
+
+    /**
+     * Message callback function used when running the "phpize"
+     * program.  Extracts the API numbers used.  Ignores other message
+     * types than "cmdoutput".
+     *
+     * @param string $what the type of message
+     * @param mixed $data the message
+     *
+     * @return void
+     *
+     * @access public
+     */
+    function phpizeCallback($what, $data)
+    {
+        if ($what != 'cmdoutput') {
+            return;
+        }
+        $this->log(1, rtrim($data));
+        if (preg_match('/You should update your .aclocal.m4/', $data)) {
+            return;
+        }
+        $matches = array();
+        if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) {
+            $member = preg_replace('/[^a-z]/', '_', strtolower($matches[1]));
+            $apino = (int)$matches[2];
+            if (isset($this->$member)) {
+                $this->$member = $apino;
+                //$msg = sprintf("%-22s : %d", $matches[1], $apino);
+                //$this->log(1, $msg);
+            }
+        }
+    }
+
+    /**
+     * Run an external command, using a message callback to report
+     * output.  The command will be run through popen and output is
+     * reported for every line with a "cmdoutput" message with the
+     * line string, including newlines, as payload.
+     *
+     * @param string $command the command to run
+     *
+     * @param mixed $callback (optional) function to use as message
+     * callback
+     *
+     * @return bool whether the command was successful (exit code 0
+     * means success, any other means failure)
+     *
+     * @access private
+     */
+    function _runCommand($command, $callback = null)
+    {
+        $this->log(1, "running: $command");
+        $pp = popen("$command 2>&1", "r");
+        if (!$pp) {
+            return $this->raiseError("failed to run `$command'");
+        }
+        if ($callback && $callback[0]->debug == 1) {
+            $olddbg = $callback[0]->debug;
+            $callback[0]->debug = 2;
+        }
+
+        while ($line = fgets($pp, 1024)) {
+            if ($callback) {
+                call_user_func($callback, 'cmdoutput', $line);
+            } else {
+                $this->log(2, rtrim($line));
+            }
+        }
+        if ($callback && isset($olddbg)) {
+            $callback[0]->debug = $olddbg;
+        }
+
+        $exitcode = is_resource($pp) ? pclose($pp) : -1;
+        return ($exitcode == 0);
+    }
+
+    function log($level, $msg)
+    {
+        if ($this->current_callback) {
+            if ($this->debug >= $level) {
+                call_user_func($this->current_callback, 'output', $msg);
+            }
+            return;
+        }
+        return PEAR_Common::log($level, $msg);
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/ChannelFile.php b/WEB-INF/lib/pear/PEAR/ChannelFile.php
new file mode 100644 (file)
index 0000000..f2c02ab
--- /dev/null
@@ -0,0 +1,1559 @@
+<?php
+/**
+ * PEAR_ChannelFile, the channel handling class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: ChannelFile.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/Common.php';
+
+/**
+ * Error code if the channel.xml <channel> tag does not contain a valid version
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1);
+/**
+ * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version,
+ * currently
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
+
+/**
+ * Error code if parsing is attempted with no xml extension
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
+
+/**
+ * Error code if creating the xml parser resource fails
+ */
+define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
+
+/**
+ * Error code used for all sax xml parsing errors
+ */
+define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
+
+/**#@+
+ * Validation errors
+ */
+/**
+ * Error code when channel name is missing
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
+/**
+ * Error code when channel name is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
+/**
+ * Error code when channel summary is missing
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
+/**
+ * Error code when channel summary is multi-line
+ */
+define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
+/**
+ * Error code when channel server is missing for protocol
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
+/**
+ * Error code when channel server is invalid for protocol
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
+/**
+ * Error code when a mirror name is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
+/**
+ * Error code when a mirror type is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22);
+/**
+ * Error code when an attempt is made to generate xml, but the parsed content is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
+/**
+ * Error code when an empty package name validate regex is passed in
+ */
+define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
+/**
+ * Error code when a <function> tag has no version
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
+/**
+ * Error code when a <function> tag has no name
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
+/**
+ * Error code when a <validatepackage> tag has no name
+ */
+define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
+/**
+ * Error code when a <validatepackage> tag has no version attribute
+ */
+define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28);
+/**
+ * Error code when a mirror does not exist but is called for in one of the set*
+ * methods.
+ */
+define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
+/**
+ * Error code when a server port is not numeric
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
+/**
+ * Error code when <static> contains no version attribute
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
+/**
+ * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
+ */
+define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
+/**
+ * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
+ */
+define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
+/**
+ * Error code when ssl attribute is present and is not "yes"
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37);
+/**#@-*/
+
+/**
+ * Mirror types allowed.  Currently only internet servers are recognized.
+ */
+$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] =  array('server');
+
+
+/**
+ * The Channel handling class
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_ChannelFile
+{
+    /**
+     * @access private
+     * @var PEAR_ErrorStack
+     * @access private
+     */
+    var $_stack;
+
+    /**
+     * Supported channel.xml versions, for parsing
+     * @var array
+     * @access private
+     */
+    var $_supportedVersions = array('1.0');
+
+    /**
+     * Parsed channel information
+     * @var array
+     * @access private
+     */
+    var $_channelInfo;
+
+    /**
+     * index into the subchannels array, used for parsing xml
+     * @var int
+     * @access private
+     */
+    var $_subchannelIndex;
+
+    /**
+     * index into the mirrors array, used for parsing xml
+     * @var int
+     * @access private
+     */
+    var $_mirrorIndex;
+
+    /**
+     * Flag used to determine the validity of parsed content
+     * @var boolean
+     * @access private
+     */
+    var $_isValid = false;
+
+    function PEAR_ChannelFile()
+    {
+        $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile');
+        $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
+        $this->_isValid = false;
+    }
+
+    /**
+     * @return array
+     * @access protected
+     */
+    function _getErrorMessage()
+    {
+        return
+            array(
+                PEAR_CHANNELFILE_ERROR_INVALID_VERSION =>
+                    'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%',
+                PEAR_CHANNELFILE_ERROR_NO_VERSION =>
+                    'No version number found in <channel> tag',
+                PEAR_CHANNELFILE_ERROR_NO_XML_EXT =>
+                    '%error%',
+                PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER =>
+                    'Unable to create XML parser',
+                PEAR_CHANNELFILE_ERROR_PARSER_ERROR =>
+                    '%error%',
+                PEAR_CHANNELFILE_ERROR_NO_NAME =>
+                    'Missing channel name',
+                PEAR_CHANNELFILE_ERROR_INVALID_NAME =>
+                    'Invalid channel %tag% "%name%"',
+                PEAR_CHANNELFILE_ERROR_NO_SUMMARY =>
+                    'Missing channel summary',
+                PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY =>
+                    'Channel summary should be on one line, but is multi-line',
+                PEAR_CHANNELFILE_ERROR_NO_HOST =>
+                    'Missing channel server for %type% server',
+                PEAR_CHANNELFILE_ERROR_INVALID_HOST =>
+                    'Server name "%server%" is invalid for %type% server',
+                PEAR_CHANNELFILE_ERROR_INVALID_MIRROR =>
+                    'Invalid mirror name "%name%", mirror type %type%',
+                PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE =>
+                    'Invalid mirror type "%type%"',
+                PEAR_CHANNELFILE_ERROR_INVALID =>
+                    'Cannot generate xml, contents are invalid',
+                PEAR_CHANNELFILE_ERROR_EMPTY_REGEX =>
+                    'packagenameregex cannot be empty',
+                PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION =>
+                    '%parent% %protocol% function has no version',
+                PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME =>
+                    '%parent% %protocol% function has no name',
+                PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE =>
+                    '%parent% rest baseurl has no type',
+                PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME =>
+                    'Validation package has no name in <validatepackage> tag',
+                PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION =>
+                    'Validation package "%package%" has no version',
+                PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND =>
+                    'Mirror "%mirror%" does not exist',
+                PEAR_CHANNELFILE_ERROR_INVALID_PORT =>
+                    'Port "%port%" must be numeric',
+                PEAR_CHANNELFILE_ERROR_NO_STATICVERSION =>
+                    '<static> tag must contain version attribute',
+                PEAR_CHANNELFILE_URI_CANT_MIRROR =>
+                    'The __uri pseudo-channel cannot have mirrors',
+                PEAR_CHANNELFILE_ERROR_INVALID_SSL =>
+                    '%server% has invalid ssl attribute "%ssl%" can only be yes or not present',
+            );
+    }
+
+    /**
+     * @param string contents of package.xml file
+     * @return bool success of parsing
+     */
+    function fromXmlString($data)
+    {
+        if (preg_match('/<channel\s+version="([0-9]+\.[0-9]+)"/', $data, $channelversion)) {
+            if (!in_array($channelversion[1], $this->_supportedVersions)) {
+                $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error',
+                    array('version' => $channelversion[1]));
+                return false;
+            }
+            $parser = new PEAR_XMLParser;
+            $result = $parser->parse($data);
+            if ($result !== true) {
+                if ($result->getCode() == 1) {
+                    $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error',
+                        array('error' => $result->getMessage()));
+                } else {
+                    $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error');
+                }
+                return false;
+            }
+            $this->_channelInfo = $parser->getData();
+            return true;
+        } else {
+            $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data));
+            return false;
+        }
+    }
+
+    /**
+     * @return array
+     */
+    function toArray()
+    {
+        if (!$this->_isValid && !$this->validate()) {
+            return false;
+        }
+        return $this->_channelInfo;
+    }
+
+    /**
+     * @param array
+     * @static
+     * @return PEAR_ChannelFile|false false if invalid
+     */
+    function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack')
+    {
+        $a = new PEAR_ChannelFile($compatibility, $stackClass);
+        $a->_fromArray($data);
+        if (!$a->validate()) {
+            $a = false;
+            return $a;
+        }
+        return $a;
+    }
+
+    /**
+     * Unlike {@link fromArray()} this does not do any validation
+     * @param array
+     * @static
+     * @return PEAR_ChannelFile
+     */
+    function &fromArrayWithErrors($data, $compatibility = false,
+                                  $stackClass = 'PEAR_ErrorStack')
+    {
+        $a = new PEAR_ChannelFile($compatibility, $stackClass);
+        $a->_fromArray($data);
+        return $a;
+    }
+
+    /**
+     * @param array
+     * @access private
+     */
+    function _fromArray($data)
+    {
+        $this->_channelInfo = $data;
+    }
+
+    /**
+     * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+     * @param boolean determines whether to purge the error stack after retrieving
+     * @return array
+     */
+    function getErrors($purge = false)
+    {
+        return $this->_stack->getErrors($purge);
+    }
+
+    /**
+     * Unindent given string (?)
+     *
+     * @param string $str The string that has to be unindented.
+     * @return string
+     * @access private
+     */
+    function _unIndent($str)
+    {
+        // remove leading newlines
+        $str = preg_replace('/^[\r\n]+/', '', $str);
+        // find whitespace at the beginning of the first line
+        $indent_len = strspn($str, " \t");
+        $indent = substr($str, 0, $indent_len);
+        $data = '';
+        // remove the same amount of whitespace from following lines
+        foreach (explode("\n", $str) as $line) {
+            if (substr($line, 0, $indent_len) == $indent) {
+                $data .= substr($line, $indent_len) . "\n";
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Parse a channel.xml file.  Expects the name of
+     * a channel xml file as input.
+     *
+     * @param string  $descfile  name of channel xml file
+     * @return bool success of parsing
+     */
+    function fromXmlFile($descfile)
+    {
+        if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) ||
+             (!$fp = fopen($descfile, 'r'))) {
+            require_once 'PEAR.php';
+            return PEAR::raiseError("Unable to open $descfile");
+        }
+
+        // read the whole thing so we only get one cdata callback
+        // for each block of cdata
+        fclose($fp);
+        $data = file_get_contents($descfile);
+        return $this->fromXmlString($data);
+    }
+
+    /**
+     * Parse channel information from different sources
+     *
+     * This method is able to extract information about a channel
+     * from an .xml file or a string
+     *
+     * @access public
+     * @param  string Filename of the source or the source itself
+     * @return bool
+     */
+    function fromAny($info)
+    {
+        if (is_string($info) && file_exists($info) && strlen($info) < 255) {
+            $tmp = substr($info, -4);
+            if ($tmp == '.xml') {
+                $info = $this->fromXmlFile($info);
+            } else {
+                $fp = fopen($info, "r");
+                $test = fread($fp, 5);
+                fclose($fp);
+                if ($test == "<?xml") {
+                    $info = $this->fromXmlFile($info);
+                }
+            }
+            if (PEAR::isError($info)) {
+                require_once 'PEAR.php';
+                return PEAR::raiseError($info);
+            }
+        }
+        if (is_string($info)) {
+            $info = $this->fromXmlString($info);
+        }
+        return $info;
+    }
+
+    /**
+     * Return an XML document based on previous parsing and modifications
+     *
+     * @return string XML data
+     *
+     * @access public
+     */
+    function toXml()
+    {
+        if (!$this->_isValid && !$this->validate()) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID);
+            return false;
+        }
+        if (!isset($this->_channelInfo['attribs']['version'])) {
+            $this->_channelInfo['attribs']['version'] = '1.0';
+        }
+        $channelInfo = $this->_channelInfo;
+        $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
+        $ret .= "<channel version=\"" .
+            $channelInfo['attribs']['version'] . "\" xmlns=\"http://pear.php.net/channel-1.0\"
+  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
+  xsi:schemaLocation=\"http://pear.php.net/dtd/channel-"
+            . $channelInfo['attribs']['version'] . " http://pear.php.net/dtd/channel-" .
+            $channelInfo['attribs']['version'] . ".xsd\">
+ <name>$channelInfo[name]</name>
+ <summary>" . htmlspecialchars($channelInfo['summary'])."</summary>
+";
+        if (isset($channelInfo['suggestedalias'])) {
+            $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n";
+        }
+        if (isset($channelInfo['validatepackage'])) {
+            $ret .= ' <validatepackage version="' .
+                $channelInfo['validatepackage']['attribs']['version']. '">' .
+                htmlspecialchars($channelInfo['validatepackage']['_content']) .
+                "</validatepackage>\n";
+        }
+        $ret .= " <servers>\n";
+        $ret .= '  <primary';
+        if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) {
+            $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"';
+        }
+        if (isset($channelInfo['servers']['primary']['attribs']['port'])) {
+            $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"';
+        }
+        $ret .= ">\n";
+        if (isset($channelInfo['servers']['primary']['rest'])) {
+            $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], '   ');
+        }
+        $ret .= "  </primary>\n";
+        if (isset($channelInfo['servers']['mirror'])) {
+            $ret .= $this->_makeMirrorsXml($channelInfo);
+        }
+        $ret .= " </servers>\n";
+        $ret .= "</channel>";
+        return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret));
+    }
+
+    /**
+     * Generate the <rest> tag
+     * @access private
+     */
+    function _makeRestXml($info, $indent)
+    {
+        $ret = $indent . "<rest>\n";
+        if (isset($info['baseurl']) && !isset($info['baseurl'][0])) {
+            $info['baseurl'] = array($info['baseurl']);
+        }
+
+        if (isset($info['baseurl'])) {
+            foreach ($info['baseurl'] as $url) {
+                $ret .= "$indent <baseurl type=\"" . $url['attribs']['type'] . "\"";
+                $ret .= ">" . $url['_content'] . "</baseurl>\n";
+            }
+        }
+        $ret .= $indent . "</rest>\n";
+        return $ret;
+    }
+
+    /**
+     * Generate the <mirrors> tag
+     * @access private
+     */
+    function _makeMirrorsXml($channelInfo)
+    {
+        $ret = "";
+        if (!isset($channelInfo['servers']['mirror'][0])) {
+            $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']);
+        }
+        foreach ($channelInfo['servers']['mirror'] as $mirror) {
+            $ret .= '  <mirror host="' . $mirror['attribs']['host'] . '"';
+            if (isset($mirror['attribs']['port'])) {
+                $ret .= ' port="' . $mirror['attribs']['port'] . '"';
+            }
+            if (isset($mirror['attribs']['ssl'])) {
+                $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"';
+            }
+            $ret .= ">\n";
+            if (isset($mirror['rest'])) {
+                if (isset($mirror['rest'])) {
+                    $ret .= $this->_makeRestXml($mirror['rest'], '   ');
+                }
+                $ret .= "  </mirror>\n";
+            } else {
+                $ret .= "/>\n";
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Generate the <functions> tag
+     * @access private
+     */
+    function _makeFunctionsXml($functions, $indent, $rest = false)
+    {
+        $ret = '';
+        if (!isset($functions[0])) {
+            $functions = array($functions);
+        }
+        foreach ($functions as $function) {
+            $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\"";
+            if ($rest) {
+                $ret .= ' uri="' . $function['attribs']['uri'] . '"';
+            }
+            $ret .= ">" . $function['_content'] . "</function>\n";
+        }
+        return $ret;
+    }
+
+    /**
+     * Validation error.  Also marks the object contents as invalid
+     * @param error code
+     * @param array error information
+     * @access private
+     */
+    function _validateError($code, $params = array())
+    {
+        $this->_stack->push($code, 'error', $params);
+        $this->_isValid = false;
+    }
+
+    /**
+     * Validation warning.  Does not mark the object contents invalid.
+     * @param error code
+     * @param array error information
+     * @access private
+     */
+    function _validateWarning($code, $params = array())
+    {
+        $this->_stack->push($code, 'warning', $params);
+    }
+
+    /**
+     * Validate parsed file.
+     *
+     * @access public
+     * @return boolean
+     */
+    function validate()
+    {
+        $this->_isValid = true;
+        $info = $this->_channelInfo;
+        if (empty($info['name'])) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME);
+        } elseif (!$this->validChannelServer($info['name'])) {
+            if ($info['name'] != '__uri') {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name',
+                    'name' => $info['name']));
+            }
+        }
+        if (empty($info['summary'])) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
+        } elseif (strpos(trim($info['summary']), "\n") !== false) {
+            $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
+                array('summary' => $info['summary']));
+        }
+        if (isset($info['suggestedalias'])) {
+            if (!$this->validChannelServer($info['suggestedalias'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+                    array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias']));
+            }
+        }
+        if (isset($info['localalias'])) {
+            if (!$this->validChannelServer($info['localalias'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+                    array('tag' => 'localalias', 'name' =>$info['localalias']));
+            }
+        }
+        if (isset($info['validatepackage'])) {
+            if (!isset($info['validatepackage']['_content'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME);
+            }
+            if (!isset($info['validatepackage']['attribs']['version'])) {
+                $content = isset($info['validatepackage']['_content']) ?
+                    $info['validatepackage']['_content'] :
+                    null;
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION,
+                    array('package' => $content));
+            }
+        }
+
+        if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) &&
+              !is_numeric($info['servers']['primary']['attribs']['port'])) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT,
+                array('port' => $info['servers']['primary']['attribs']['port']));
+        }
+
+        if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) &&
+              $info['servers']['primary']['attribs']['ssl'] != 'yes') {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
+                array('ssl' => $info['servers']['primary']['attribs']['ssl'],
+                    'server' => $info['name']));
+        }
+
+        if (isset($info['servers']['primary']['rest']) &&
+              isset($info['servers']['primary']['rest']['baseurl'])) {
+            $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']);
+        }
+        if (isset($info['servers']['mirror'])) {
+            if ($this->_channelInfo['name'] == '__uri') {
+                $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR);
+            }
+            if (!isset($info['servers']['mirror'][0])) {
+                $info['servers']['mirror'] = array($info['servers']['mirror']);
+            }
+            foreach ($info['servers']['mirror'] as $mirror) {
+                if (!isset($mirror['attribs']['host'])) {
+                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST,
+                      array('type' => 'mirror'));
+                } elseif (!$this->validChannelServer($mirror['attribs']['host'])) {
+                    $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST,
+                        array('server' => $mirror['attribs']['host'], 'type' => 'mirror'));
+                }
+                if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') {
+                    $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
+                        array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host']));
+                }
+                if (isset($mirror['rest'])) {
+                    $this->_validateFunctions('rest', $mirror['rest']['baseurl'],
+                        $mirror['attribs']['host']);
+                }
+            }
+        }
+        return $this->_isValid;
+    }
+
+    /**
+     * @param string  rest - protocol name this function applies to
+     * @param array the functions
+     * @param string the name of the parent element (mirror name, for instance)
+     */
+    function _validateFunctions($protocol, $functions, $parent = '')
+    {
+        if (!isset($functions[0])) {
+            $functions = array($functions);
+        }
+
+        foreach ($functions as $function) {
+            if (!isset($function['_content']) || empty($function['_content'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME,
+                    array('parent' => $parent, 'protocol' => $protocol));
+            }
+
+            if ($protocol == 'rest') {
+                if (!isset($function['attribs']['type']) ||
+                      empty($function['attribs']['type'])) {
+                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE,
+                        array('parent' => $parent, 'protocol' => $protocol));
+                }
+            } else {
+                if (!isset($function['attribs']['version']) ||
+                      empty($function['attribs']['version'])) {
+                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION,
+                        array('parent' => $parent, 'protocol' => $protocol));
+                }
+            }
+        }
+    }
+
+    /**
+     * Test whether a string contains a valid channel server.
+     * @param string $ver the package version to test
+     * @return bool
+     */
+    function validChannelServer($server)
+    {
+        if ($server == '__uri') {
+            return true;
+        }
+        return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server);
+    }
+
+    /**
+     * @return string|false
+     */
+    function getName()
+    {
+        if (isset($this->_channelInfo['name'])) {
+            return $this->_channelInfo['name'];
+        }
+
+        return false;
+    }
+
+    /**
+     * @return string|false
+     */
+    function getServer()
+    {
+        if (isset($this->_channelInfo['name'])) {
+            return $this->_channelInfo['name'];
+        }
+
+        return false;
+    }
+
+    /**
+     * @return int|80 port number to connect to
+     */
+    function getPort($mirror = false)
+    {
+        if ($mirror) {
+            if ($mir = $this->getMirror($mirror)) {
+                if (isset($mir['attribs']['port'])) {
+                    return $mir['attribs']['port'];
+                }
+
+                if ($this->getSSL($mirror)) {
+                    return 443;
+                }
+
+                return 80;
+            }
+
+            return false;
+        }
+
+        if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) {
+            return $this->_channelInfo['servers']['primary']['attribs']['port'];
+        }
+
+        if ($this->getSSL()) {
+            return 443;
+        }
+
+        return 80;
+    }
+
+    /**
+     * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel
+     */
+    function getSSL($mirror = false)
+    {
+        if ($mirror) {
+            if ($mir = $this->getMirror($mirror)) {
+                if (isset($mir['attribs']['ssl'])) {
+                    return true;
+                }
+
+                return false;
+            }
+
+            return false;
+        }
+
+        if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return string|false
+     */
+    function getSummary()
+    {
+        if (isset($this->_channelInfo['summary'])) {
+            return $this->_channelInfo['summary'];
+        }
+
+        return false;
+    }
+
+    /**
+     * @param string protocol type
+     * @param string Mirror name
+     * @return array|false
+     */
+    function getFunctions($protocol, $mirror = false)
+    {
+        if ($this->getName() == '__uri') {
+            return false;
+        }
+
+        $function = $protocol == 'rest' ? 'baseurl' : 'function';
+        if ($mirror) {
+            if ($mir = $this->getMirror($mirror)) {
+                if (isset($mir[$protocol][$function])) {
+                    return $mir[$protocol][$function];
+                }
+            }
+
+            return false;
+        }
+
+        if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) {
+            return $this->_channelInfo['servers']['primary'][$protocol][$function];
+        }
+
+        return false;
+    }
+
+    /**
+     * @param string Protocol type
+     * @param string Function name (null to return the
+     *               first protocol of the type requested)
+     * @param string Mirror name, if any
+     * @return array
+     */
+     function getFunction($type, $name = null, $mirror = false)
+     {
+        $protocols = $this->getFunctions($type, $mirror);
+        if (!$protocols) {
+            return false;
+        }
+
+        foreach ($protocols as $protocol) {
+            if ($name === null) {
+                return $protocol;
+            }
+
+            if ($protocol['_content'] != $name) {
+                continue;
+            }
+
+            return $protocol;
+        }
+
+        return false;
+     }
+
+    /**
+     * @param string protocol type
+     * @param string protocol name
+     * @param string version
+     * @param string mirror name
+     * @return boolean
+     */
+    function supports($type, $name = null, $mirror = false, $version = '1.0')
+    {
+        $protocols = $this->getFunctions($type, $mirror);
+        if (!$protocols) {
+            return false;
+        }
+
+        foreach ($protocols as $protocol) {
+            if ($protocol['attribs']['version'] != $version) {
+                continue;
+            }
+
+            if ($name === null) {
+                return true;
+            }
+
+            if ($protocol['_content'] != $name) {
+                continue;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines whether a channel supports Representational State Transfer (REST) protocols
+     * for retrieving channel information
+     * @param string
+     * @return bool
+     */
+    function supportsREST($mirror = false)
+    {
+        if ($mirror == $this->_channelInfo['name']) {
+            $mirror = false;
+        }
+
+        if ($mirror) {
+            if ($mir = $this->getMirror($mirror)) {
+                return isset($mir['rest']);
+            }
+
+            return false;
+        }
+
+        return isset($this->_channelInfo['servers']['primary']['rest']);
+    }
+
+    /**
+     * Get the URL to access a base resource.
+     *
+     * Hyperlinks in the returned xml will be used to retrieve the proper information
+     * needed.  This allows extreme extensibility and flexibility in implementation
+     * @param string Resource Type to retrieve
+     */
+    function getBaseURL($resourceType, $mirror = false)
+    {
+        if ($mirror == $this->_channelInfo['name']) {
+            $mirror = false;
+        }
+
+        if ($mirror) {
+            $mir = $this->getMirror($mirror);
+            if (!$mir) {
+                return false;
+            }
+
+            $rest = $mir['rest'];
+        } else {
+            $rest = $this->_channelInfo['servers']['primary']['rest'];
+        }
+
+        if (!isset($rest['baseurl'][0])) {
+            $rest['baseurl'] = array($rest['baseurl']);
+        }
+
+        foreach ($rest['baseurl'] as $baseurl) {
+            if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) {
+                return $baseurl['_content'];
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Since REST does not implement RPC, provide this as a logical wrapper around
+     * resetFunctions for REST
+     * @param string|false mirror name, if any
+     */
+    function resetREST($mirror = false)
+    {
+        return $this->resetFunctions('rest', $mirror);
+    }
+
+    /**
+     * Empty all protocol definitions
+     * @param string protocol type
+     * @param string|false mirror name, if any
+     */
+    function resetFunctions($type, $mirror = false)
+    {
+        if ($mirror) {
+            if (isset($this->_channelInfo['servers']['mirror'])) {
+                $mirrors = $this->_channelInfo['servers']['mirror'];
+                if (!isset($mirrors[0])) {
+                    $mirrors = array($mirrors);
+                }
+
+                foreach ($mirrors as $i => $mir) {
+                    if ($mir['attribs']['host'] == $mirror) {
+                        if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) {
+                            unset($this->_channelInfo['servers']['mirror'][$i][$type]);
+                        }
+
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            return false;
+        }
+
+        if (isset($this->_channelInfo['servers']['primary'][$type])) {
+            unset($this->_channelInfo['servers']['primary'][$type]);
+        }
+
+        return true;
+    }
+
+    /**
+     * Set a channel's protocols to the protocols supported by pearweb
+     */
+    function setDefaultPEARProtocols($version = '1.0', $mirror = false)
+    {
+        switch ($version) {
+            case '1.0' :
+                $this->resetREST($mirror);
+
+                if (!isset($this->_channelInfo['servers'])) {
+                    $this->_channelInfo['servers'] = array('primary' =>
+                        array('rest' => array()));
+                } elseif (!isset($this->_channelInfo['servers']['primary'])) {
+                    $this->_channelInfo['servers']['primary'] = array('rest' => array());
+                }
+
+                return true;
+            break;
+            default :
+                return false;
+            break;
+        }
+    }
+
+    /**
+     * @return array
+     */
+    function getMirrors()
+    {
+        if (isset($this->_channelInfo['servers']['mirror'])) {
+            $mirrors = $this->_channelInfo['servers']['mirror'];
+            if (!isset($mirrors[0])) {
+                $mirrors = array($mirrors);
+            }
+
+            return $mirrors;
+        }
+
+        return array();
+    }
+
+    /**
+     * Get the unserialized XML representing a mirror
+     * @return array|false
+     */
+    function getMirror($server)
+    {
+        foreach ($this->getMirrors() as $mirror) {
+            if ($mirror['attribs']['host'] == $server) {
+                return $mirror;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param string
+     * @return string|false
+     * @error PEAR_CHANNELFILE_ERROR_NO_NAME
+     * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME
+     */
+    function setName($name)
+    {
+        return $this->setServer($name);
+    }
+
+    /**
+     * Set the socket number (port) that is used to connect to this channel
+     * @param integer
+     * @param string|false name of the mirror server, or false for the primary
+     */
+    function setPort($port, $mirror = false)
+    {
+        if ($mirror) {
+            if (!isset($this->_channelInfo['servers']['mirror'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                    array('mirror' => $mirror));
+                return false;
+            }
+
+            if (isset($this->_channelInfo['servers']['mirror'][0])) {
+                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+                    if ($mirror == $mir['attribs']['host']) {
+                        $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port;
+                        return true;
+                    }
+                }
+
+                return false;
+            } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+                $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port;
+                $this->_isValid = false;
+                return true;
+            }
+        }
+
+        $this->_channelInfo['servers']['primary']['attribs']['port'] = $port;
+        $this->_isValid = false;
+        return true;
+    }
+
+    /**
+     * Set the socket number (port) that is used to connect to this channel
+     * @param bool Determines whether to turn on SSL support or turn it off
+     * @param string|false name of the mirror server, or false for the primary
+     */
+    function setSSL($ssl = true, $mirror = false)
+    {
+        if ($mirror) {
+            if (!isset($this->_channelInfo['servers']['mirror'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                    array('mirror' => $mirror));
+                return false;
+            }
+
+            if (isset($this->_channelInfo['servers']['mirror'][0])) {
+                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+                    if ($mirror == $mir['attribs']['host']) {
+                        if (!$ssl) {
+                            if (isset($this->_channelInfo['servers']['mirror'][$i]
+                                  ['attribs']['ssl'])) {
+                                unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']);
+                            }
+                        } else {
+                            $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes';
+                        }
+
+                        return true;
+                    }
+                }
+
+                return false;
+            } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+                if (!$ssl) {
+                    if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) {
+                        unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']);
+                    }
+                } else {
+                    $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes';
+                }
+
+                $this->_isValid = false;
+                return true;
+            }
+        }
+
+        if ($ssl) {
+            $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes';
+        } else {
+            if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
+                unset($this->_channelInfo['servers']['primary']['attribs']['ssl']);
+            }
+        }
+
+        $this->_isValid = false;
+        return true;
+    }
+
+    /**
+     * @param string
+     * @return string|false
+     * @error PEAR_CHANNELFILE_ERROR_NO_SERVER
+     * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER
+     */
+    function setServer($server, $mirror = false)
+    {
+        if (empty($server)) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER);
+            return false;
+        } elseif (!$this->validChannelServer($server)) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+                array('tag' => 'name', 'name' => $server));
+            return false;
+        }
+
+        if ($mirror) {
+            $found = false;
+            foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+                if ($mirror == $mir['attribs']['host']) {
+                    $found = true;
+                    break;
+                }
+            }
+
+            if (!$found) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                    array('mirror' => $mirror));
+                return false;
+            }
+
+            $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server;
+            return true;
+        }
+
+        $this->_channelInfo['name'] = $server;
+        return true;
+    }
+
+    /**
+     * @param string
+     * @return boolean success
+     * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY
+     * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY
+     */
+    function setSummary($summary)
+    {
+        if (empty($summary)) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
+            return false;
+        } elseif (strpos(trim($summary), "\n") !== false) {
+            $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
+                array('summary' => $summary));
+        }
+
+        $this->_channelInfo['summary'] = $summary;
+        return true;
+    }
+
+    /**
+     * @param string
+     * @param boolean determines whether the alias is in channel.xml or local
+     * @return boolean success
+     */
+    function setAlias($alias, $local = false)
+    {
+        if (!$this->validChannelServer($alias)) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+                array('tag' => 'suggestedalias', 'name' => $alias));
+            return false;
+        }
+
+        if ($local) {
+            $this->_channelInfo['localalias'] = $alias;
+        } else {
+            $this->_channelInfo['suggestedalias'] = $alias;
+        }
+
+        return true;
+    }
+
+    /**
+     * @return string
+     */
+    function getAlias()
+    {
+        if (isset($this->_channelInfo['localalias'])) {
+            return $this->_channelInfo['localalias'];
+        }
+        if (isset($this->_channelInfo['suggestedalias'])) {
+            return $this->_channelInfo['suggestedalias'];
+        }
+        if (isset($this->_channelInfo['name'])) {
+            return $this->_channelInfo['name'];
+        }
+        return '';
+    }
+
+    /**
+     * Set the package validation object if it differs from PEAR's default
+     * The class must be includeable via changing _ in the classname to path separator,
+     * but no checking of this is made.
+     * @param string|false pass in false to reset to the default packagename regex
+     * @return boolean success
+     */
+    function setValidationPackage($validateclass, $version)
+    {
+        if (empty($validateclass)) {
+            unset($this->_channelInfo['validatepackage']);
+        }
+        $this->_channelInfo['validatepackage'] = array('_content' => $validateclass);
+        $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version);
+    }
+
+    /**
+     * Add a protocol to the provides section
+     * @param string protocol type
+     * @param string protocol version
+     * @param string protocol name, if any
+     * @param string mirror name, if this is a mirror's protocol
+     * @return bool
+     */
+    function addFunction($type, $version, $name = '', $mirror = false)
+    {
+        if ($mirror) {
+            return $this->addMirrorFunction($mirror, $type, $version, $name);
+        }
+
+        $set = array('attribs' => array('version' => $version), '_content' => $name);
+        if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) {
+            if (!isset($this->_channelInfo['servers'])) {
+                $this->_channelInfo['servers'] = array('primary' =>
+                    array($type => array()));
+            } elseif (!isset($this->_channelInfo['servers']['primary'])) {
+                $this->_channelInfo['servers']['primary'] = array($type => array());
+            }
+
+            $this->_channelInfo['servers']['primary'][$type]['function'] = $set;
+            $this->_isValid = false;
+            return true;
+        } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) {
+            $this->_channelInfo['servers']['primary'][$type]['function'] = array(
+                $this->_channelInfo['servers']['primary'][$type]['function']);
+        }
+
+        $this->_channelInfo['servers']['primary'][$type]['function'][] = $set;
+        return true;
+    }
+    /**
+     * Add a protocol to a mirror's provides section
+     * @param string mirror name (server)
+     * @param string protocol type
+     * @param string protocol version
+     * @param string protocol name, if any
+     */
+    function addMirrorFunction($mirror, $type, $version, $name = '')
+    {
+        if (!isset($this->_channelInfo['servers']['mirror'])) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                array('mirror' => $mirror));
+            return false;
+        }
+
+        $setmirror = false;
+        if (isset($this->_channelInfo['servers']['mirror'][0])) {
+            foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+                if ($mirror == $mir['attribs']['host']) {
+                    $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
+                    break;
+                }
+            }
+        } else {
+            if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+                $setmirror = &$this->_channelInfo['servers']['mirror'];
+            }
+        }
+
+        if (!$setmirror) {
+            $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                array('mirror' => $mirror));
+            return false;
+        }
+
+        $set = array('attribs' => array('version' => $version), '_content' => $name);
+        if (!isset($setmirror[$type]['function'])) {
+            $setmirror[$type]['function'] = $set;
+            $this->_isValid = false;
+            return true;
+        } elseif (!isset($setmirror[$type]['function'][0])) {
+            $setmirror[$type]['function'] = array($setmirror[$type]['function']);
+        }
+
+        $setmirror[$type]['function'][] = $set;
+        $this->_isValid = false;
+        return true;
+    }
+
+    /**
+     * @param string Resource Type this url links to
+     * @param string URL
+     * @param string|false mirror name, if this is not a primary server REST base URL
+     */
+    function setBaseURL($resourceType, $url, $mirror = false)
+    {
+        if ($mirror) {
+            if (!isset($this->_channelInfo['servers']['mirror'])) {
+                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+                    array('mirror' => $mirror));
+                return false;
+            }
+
+            $setmirror = false;
+            if (isset($this->_channelInfo['servers']['mirror'][0])) {
+                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+                    if ($mirror == $mir['attribs']['host']) {
+                        $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
+                        break;
+                    }
+                }
+            } else {
+                if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+                    $setmirror = &$this->_channelInfo['servers']['mirror'];
+                }
+            }
+        } else {
+            $setmirror = &$this->_channelInfo['servers']['primary'];
+        }
+
+        $set = array('attribs' => array('type' => $resourceType), '_content' => $url);
+        if (!isset($setmirror['rest'])) {
+            $setmirror['rest'] = array();
+        }
+
+        if (!isset($setmirror['rest']['baseurl'])) {
+            $setmirror['rest']['baseurl'] = $set;
+            $this->_isValid = false;
+            return true;
+        } elseif (!isset($setmirror['rest']['baseurl'][0])) {
+            $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']);
+        }
+
+        foreach ($setmirror['rest']['baseurl'] as $i => $url) {
+            if ($url['attribs']['type'] == $resourceType) {
+                $this->_isValid = false;
+                $setmirror['rest']['baseurl'][$i] = $set;
+                return true;
+            }
+        }
+
+        $setmirror['rest']['baseurl'][] = $set;
+        $this->_isValid = false;
+        return true;
+    }
+
+    /**
+     * @param string mirror server
+     * @param int mirror http port
+     * @return boolean
+     */
+    function addMirror($server, $port = null)
+    {
+        if ($this->_channelInfo['name'] == '__uri') {
+            return false; // the __uri channel cannot have mirrors by definition
+        }
+
+        $set = array('attribs' => array('host' => $server));
+        if (is_numeric($port)) {
+            $set['attribs']['port'] = $port;
+        }
+
+        if (!isset($this->_channelInfo['servers']['mirror'])) {
+            $this->_channelInfo['servers']['mirror'] = $set;
+            return true;
+        }
+
+        if (!isset($this->_channelInfo['servers']['mirror'][0])) {
+            $this->_channelInfo['servers']['mirror'] =
+                array($this->_channelInfo['servers']['mirror']);
+        }
+
+        $this->_channelInfo['servers']['mirror'][] = $set;
+        return true;
+    }
+
+    /**
+     * Retrieve the name of the validation package for this channel
+     * @return string|false
+     */
+    function getValidationPackage()
+    {
+        if (!$this->_isValid && !$this->validate()) {
+            return false;
+        }
+
+        if (!isset($this->_channelInfo['validatepackage'])) {
+            return array('attribs' => array('version' => 'default'),
+                '_content' => 'PEAR_Validate');
+        }
+
+        return $this->_channelInfo['validatepackage'];
+    }
+
+    /**
+     * Retrieve the object that can be used for custom validation
+     * @param string|false the name of the package to validate.  If the package is
+     *                     the channel validation package, PEAR_Validate is returned
+     * @return PEAR_Validate|false false is returned if the validation package
+     *         cannot be located
+     */
+    function &getValidationObject($package = false)
+    {
+        if (!class_exists('PEAR_Validate')) {
+            require_once 'PEAR/Validate.php';
+        }
+
+        if (!$this->_isValid) {
+            if (!$this->validate()) {
+                $a = false;
+                return $a;
+            }
+        }
+
+        if (isset($this->_channelInfo['validatepackage'])) {
+            if ($package == $this->_channelInfo['validatepackage']) {
+                // channel validation packages are always validated by PEAR_Validate
+                $val = &new PEAR_Validate;
+                return $val;
+            }
+
+            if (!class_exists(str_replace('.', '_',
+                  $this->_channelInfo['validatepackage']['_content']))) {
+                if ($this->isIncludeable(str_replace('_', '/',
+                      $this->_channelInfo['validatepackage']['_content']) . '.php')) {
+                    include_once str_replace('_', '/',
+                        $this->_channelInfo['validatepackage']['_content']) . '.php';
+                    $vclass = str_replace('.', '_',
+                        $this->_channelInfo['validatepackage']['_content']);
+                    $val = &new $vclass;
+                } else {
+                    $a = false;
+                    return $a;
+                }
+            } else {
+                $vclass = str_replace('.', '_',
+                    $this->_channelInfo['validatepackage']['_content']);
+                $val = &new $vclass;
+            }
+        } else {
+            $val = &new PEAR_Validate;
+        }
+
+        return $val;
+    }
+
+    function isIncludeable($path)
+    {
+        $possibilities = explode(PATH_SEPARATOR, ini_get('include_path'));
+        foreach ($possibilities as $dir) {
+            if (file_exists($dir . DIRECTORY_SEPARATOR . $path)
+                  && is_readable($dir . DIRECTORY_SEPARATOR . $path)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This function is used by the channel updater and retrieves a value set by
+     * the registry, or the current time if it has not been set
+     * @return string
+     */
+    function lastModified()
+    {
+        if (isset($this->_channelInfo['_lastmodified'])) {
+            return $this->_channelInfo['_lastmodified'];
+        }
+
+        return time();
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php b/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php
new file mode 100644 (file)
index 0000000..e630ace
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * PEAR_ChannelFile_Parser for parsing channel.xml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Parser.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * base xml parser class
+ */
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/ChannelFile.php';
+/**
+ * Parser for channel.xml
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_ChannelFile_Parser extends PEAR_XMLParser
+{
+    var $_config;
+    var $_logger;
+    var $_registry;
+
+    function setConfig(&$c)
+    {
+        $this->_config = &$c;
+        $this->_registry = &$c->getRegistry();
+    }
+
+    function setLogger(&$l)
+    {
+        $this->_logger = &$l;
+    }
+
+    function parse($data, $file)
+    {
+        if (PEAR::isError($err = parent::parse($data, $file))) {
+            return $err;
+        }
+
+        $ret = new PEAR_ChannelFile;
+        $ret->setConfig($this->_config);
+        if (isset($this->_logger)) {
+            $ret->setLogger($this->_logger);
+        }
+
+        $ret->fromArray($this->_unserializedData);
+        // make sure the filelist is in the easy to read format needed
+        $ret->flattenFilelist();
+        $ret->setPackagefile($file, $archive);
+        return $ret;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command.php b/WEB-INF/lib/pear/PEAR/Command.php
new file mode 100644 (file)
index 0000000..db39b8f
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+/**
+ * PEAR_Command, command pattern class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Command.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Frontend.php';
+require_once 'PEAR/XMLParser.php';
+
+/**
+ * List of commands and what classes they are implemented in.
+ * @var array command => implementing class
+ */
+$GLOBALS['_PEAR_Command_commandlist'] = array();
+
+/**
+ * List of commands and their descriptions
+ * @var array command => description
+ */
+$GLOBALS['_PEAR_Command_commanddesc'] = array();
+
+/**
+ * List of shortcuts to common commands.
+ * @var array shortcut => command
+ */
+$GLOBALS['_PEAR_Command_shortcuts'] = array();
+
+/**
+ * Array of command objects
+ * @var array class => object
+ */
+$GLOBALS['_PEAR_Command_objects'] = array();
+
+/**
+ * PEAR command class, a simple factory class for administrative
+ * commands.
+ *
+ * How to implement command classes:
+ *
+ * - The class must be called PEAR_Command_Nnn, installed in the
+ *   "PEAR/Common" subdir, with a method called getCommands() that
+ *   returns an array of the commands implemented by the class (see
+ *   PEAR/Command/Install.php for an example).
+ *
+ * - The class must implement a run() function that is called with three
+ *   params:
+ *
+ *    (string) command name
+ *    (array)  assoc array with options, freely defined by each
+ *             command, for example:
+ *             array('force' => true)
+ *    (array)  list of the other parameters
+ *
+ *   The run() function returns a PEAR_CommandResponse object.  Use
+ *   these methods to get information:
+ *
+ *    int getStatus()   Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL)
+ *                      *_PARTIAL means that you need to issue at least
+ *                      one more command to complete the operation
+ *                      (used for example for validation steps).
+ *
+ *    string getMessage()  Returns a message for the user.  Remember,
+ *                         no HTML or other interface-specific markup.
+ *
+ *   If something unexpected happens, run() returns a PEAR error.
+ *
+ * - DON'T OUTPUT ANYTHING! Return text for output instead.
+ *
+ * - DON'T USE HTML! The text you return will be used from both Gtk,
+ *   web and command-line interfaces, so for now, keep everything to
+ *   plain text.
+ *
+ * - DON'T USE EXIT OR DIE! Always use pear errors.  From static
+ *   classes do PEAR::raiseError(), from other classes do
+ *   $this->raiseError().
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command
+{
+    // {{{ factory()
+
+    /**
+     * Get the right object for executing a command.
+     *
+     * @param string $command The name of the command
+     * @param object $config  Instance of PEAR_Config object
+     *
+     * @return object the command object or a PEAR error
+     *
+     * @access public
+     * @static
+     */
+    function &factory($command, &$config)
+    {
+        if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+            PEAR_Command::registerCommands();
+        }
+        if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+            $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+        }
+        if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+            $a = PEAR::raiseError("unknown command `$command'");
+            return $a;
+        }
+        $class = $GLOBALS['_PEAR_Command_commandlist'][$command];
+        if (!class_exists($class)) {
+            require_once $GLOBALS['_PEAR_Command_objects'][$class];
+        }
+        if (!class_exists($class)) {
+            $a = PEAR::raiseError("unknown command `$command'");
+            return $a;
+        }
+        $ui =& PEAR_Command::getFrontendObject();
+        $obj = &new $class($ui, $config);
+        return $obj;
+    }
+
+    // }}}
+    // {{{ & getObject()
+    function &getObject($command)
+    {
+        $class = $GLOBALS['_PEAR_Command_commandlist'][$command];
+        if (!class_exists($class)) {
+            require_once $GLOBALS['_PEAR_Command_objects'][$class];
+        }
+        if (!class_exists($class)) {
+            return PEAR::raiseError("unknown command `$command'");
+        }
+        $ui =& PEAR_Command::getFrontendObject();
+        $config = &PEAR_Config::singleton();
+        $obj = &new $class($ui, $config);
+        return $obj;
+    }
+
+    // }}}
+    // {{{ & getFrontendObject()
+
+    /**
+     * Get instance of frontend object.
+     *
+     * @return object|PEAR_Error
+     * @static
+     */
+    function &getFrontendObject()
+    {
+        $a = &PEAR_Frontend::singleton();
+        return $a;
+    }
+
+    // }}}
+    // {{{ & setFrontendClass()
+
+    /**
+     * Load current frontend class.
+     *
+     * @param string $uiclass Name of class implementing the frontend
+     *
+     * @return object the frontend object, or a PEAR error
+     * @static
+     */
+    function &setFrontendClass($uiclass)
+    {
+        $a = &PEAR_Frontend::setFrontendClass($uiclass);
+        return $a;
+    }
+
+    // }}}
+    // {{{ setFrontendType()
+
+    /**
+     * Set current frontend.
+     *
+     * @param string $uitype Name of the frontend type (for example "CLI")
+     *
+     * @return object the frontend object, or a PEAR error
+     * @static
+     */
+    function setFrontendType($uitype)
+    {
+        $uiclass = 'PEAR_Frontend_' . $uitype;
+        return PEAR_Command::setFrontendClass($uiclass);
+    }
+
+    // }}}
+    // {{{ registerCommands()
+
+    /**
+     * Scan through the Command directory looking for classes
+     * and see what commands they implement.
+     *
+     * @param bool   (optional) if FALSE (default), the new list of
+     *               commands should replace the current one.  If TRUE,
+     *               new entries will be merged with old.
+     *
+     * @param string (optional) where (what directory) to look for
+     *               classes, defaults to the Command subdirectory of
+     *               the directory from where this file (__FILE__) is
+     *               included.
+     *
+     * @return bool TRUE on success, a PEAR error on failure
+     *
+     * @access public
+     * @static
+     */
+    function registerCommands($merge = false, $dir = null)
+    {
+        $parser = new PEAR_XMLParser;
+        if ($dir === null) {
+            $dir = dirname(__FILE__) . '/Command';
+        }
+        if (!is_dir($dir)) {
+            return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory");
+        }
+        $dp = @opendir($dir);
+        if (empty($dp)) {
+            return PEAR::raiseError("registerCommands: opendir($dir) failed");
+        }
+        if (!$merge) {
+            $GLOBALS['_PEAR_Command_commandlist'] = array();
+        }
+
+        while ($file = readdir($dp)) {
+            if ($file{0} == '.' || substr($file, -4) != '.xml') {
+                continue;
+            }
+
+            $f = substr($file, 0, -4);
+            $class = "PEAR_Command_" . $f;
+            // List of commands
+            if (empty($GLOBALS['_PEAR_Command_objects'][$class])) {
+                $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php';
+            }
+
+            $parser->parse(file_get_contents("$dir/$file"));
+            $implements = $parser->getData();
+            foreach ($implements as $command => $desc) {
+                if ($command == 'attribs') {
+                    continue;
+                }
+
+                if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+                    return PEAR::raiseError('Command "' . $command . '" already registered in ' .
+                        'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"');
+                }
+
+                $GLOBALS['_PEAR_Command_commandlist'][$command] = $class;
+                $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary'];
+                if (isset($desc['shortcut'])) {
+                    $shortcut = $desc['shortcut'];
+                    if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) {
+                        return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' .
+                            'registered to command "' . $command . '" in class "' .
+                            $GLOBALS['_PEAR_Command_commandlist'][$command] . '"');
+                    }
+                    $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command;
+                }
+
+                if (isset($desc['options']) && $desc['options']) {
+                    foreach ($desc['options'] as $oname => $option) {
+                        if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) {
+                            return PEAR::raiseError('Option "' . $oname . '" short option "' .
+                                $option['shortopt'] . '" must be ' .
+                                'only 1 character in Command "' . $command . '" in class "' .
+                                $class . '"');
+                        }
+                    }
+                }
+            }
+        }
+
+        ksort($GLOBALS['_PEAR_Command_shortcuts']);
+        ksort($GLOBALS['_PEAR_Command_commandlist']);
+        @closedir($dp);
+        return true;
+    }
+
+    // }}}
+    // {{{ getCommands()
+
+    /**
+     * Get the list of currently supported commands, and what
+     * classes implement them.
+     *
+     * @return array command => implementing class
+     *
+     * @access public
+     * @static
+     */
+    function getCommands()
+    {
+        if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+            PEAR_Command::registerCommands();
+        }
+        return $GLOBALS['_PEAR_Command_commandlist'];
+    }
+
+    // }}}
+    // {{{ getShortcuts()
+
+    /**
+     * Get the list of command shortcuts.
+     *
+     * @return array shortcut => command
+     *
+     * @access public
+     * @static
+     */
+    function getShortcuts()
+    {
+        if (empty($GLOBALS['_PEAR_Command_shortcuts'])) {
+            PEAR_Command::registerCommands();
+        }
+        return $GLOBALS['_PEAR_Command_shortcuts'];
+    }
+
+    // }}}
+    // {{{ getGetoptArgs()
+
+    /**
+     * Compiles arguments for getopt.
+     *
+     * @param string $command     command to get optstring for
+     * @param string $short_args  (reference) short getopt format
+     * @param array  $long_args   (reference) long getopt format
+     *
+     * @return void
+     *
+     * @access public
+     * @static
+     */
+    function getGetoptArgs($command, &$short_args, &$long_args)
+    {
+        if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+            PEAR_Command::registerCommands();
+        }
+        if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+            $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+        }
+        if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+            return null;
+        }
+        $obj = &PEAR_Command::getObject($command);
+        return $obj->getGetoptArgs($command, $short_args, $long_args);
+    }
+
+    // }}}
+    // {{{ getDescription()
+
+    /**
+     * Get description for a command.
+     *
+     * @param  string $command Name of the command
+     *
+     * @return string command description
+     *
+     * @access public
+     * @static
+     */
+    function getDescription($command)
+    {
+        if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) {
+            return null;
+        }
+        return $GLOBALS['_PEAR_Command_commanddesc'][$command];
+    }
+
+    // }}}
+    // {{{ getHelp()
+
+    /**
+     * Get help for command.
+     *
+     * @param string $command Name of the command to return help for
+     *
+     * @access public
+     * @static
+     */
+    function getHelp($command)
+    {
+        $cmds = PEAR_Command::getCommands();
+        if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+            $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+        }
+        if (isset($cmds[$command])) {
+            $obj = &PEAR_Command::getObject($command);
+            return $obj->getHelp($command);
+        }
+        return false;
+    }
+    // }}}
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Auth.php b/WEB-INF/lib/pear/PEAR/Command/Auth.php
new file mode 100644 (file)
index 0000000..63cd152
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * PEAR_Command_Auth (login, logout commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Auth.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ * @deprecated since 1.8.0alpha1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Channels.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ * @deprecated since 1.8.0alpha1
+ */
+class PEAR_Command_Auth extends PEAR_Command_Channels
+{
+    var $commands = array(
+        'login' => array(
+            'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]',
+            'shortcut' => 'li',
+            'function' => 'doLogin',
+            'options' => array(),
+            'doc' => '<channel name>
+WARNING: This function is deprecated in favor of using channel-login
+
+Log in to a remote channel server.  If <channel name> is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first.  The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems).  After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.',
+            ),
+        'logout' => array(
+            'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]',
+            'shortcut' => 'lo',
+            'function' => 'doLogout',
+            'options' => array(),
+            'doc' => '
+WARNING: This function is deprecated in favor of using channel-logout
+
+Logs out from the remote server.  This command does not actually
+connect to the remote server, it only deletes the stored username and
+password from your user configuration.',
+            )
+
+        );
+
+    /**
+     * PEAR_Command_Auth constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Auth(&$ui, &$config)
+    {
+        parent::PEAR_Command_Channels($ui, $config);
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Auth.xml b/WEB-INF/lib/pear/PEAR/Command/Auth.xml
new file mode 100644 (file)
index 0000000..590193d
--- /dev/null
@@ -0,0 +1,30 @@
+<commands version="1.0">
+ <login>
+  <summary>Connects and authenticates to remote server [Deprecated in favor of channel-login]</summary>
+  <function>doLogin</function>
+  <shortcut>li</shortcut>
+  <options />
+  <doc>&lt;channel name&gt;
+WARNING: This function is deprecated in favor of using channel-login
+
+Log in to a remote channel server.  If &lt;channel name&gt; is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first.  The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems).  After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.</doc>
+ </login>
+ <logout>
+  <summary>Logs out from the remote server [Deprecated in favor of channel-logout]</summary>
+  <function>doLogout</function>
+  <shortcut>lo</shortcut>
+  <options />
+  <doc>
+WARNING: This function is deprecated in favor of using channel-logout
+
+Logs out from the remote server.  This command does not actually
+connect to the remote server, it only deletes the stored username and
+password from your user configuration.</doc>
+ </logout>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Build.php b/WEB-INF/lib/pear/PEAR/Command/Build.php
new file mode 100644 (file)
index 0000000..1de7320
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * PEAR_Command_Auth (build command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Build.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for building extensions.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Build extends PEAR_Command_Common
+{
+    var $commands = array(
+        'build' => array(
+            'summary' => 'Build an Extension From C Source',
+            'function' => 'doBuild',
+            'shortcut' => 'b',
+            'options' => array(),
+            'doc' => '[package.xml]
+Builds one or more extensions contained in a package.'
+            ),
+        );
+
+    /**
+     * PEAR_Command_Build constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Build(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function doBuild($command, $options, $params)
+    {
+        require_once 'PEAR/Builder.php';
+        if (sizeof($params) < 1) {
+            $params[0] = 'package.xml';
+        }
+
+        $builder = &new PEAR_Builder($this->ui);
+        $this->debug = $this->config->get('verbose');
+        $err = $builder->build($params[0], array(&$this, 'buildCallback'));
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+
+        return true;
+    }
+
+    function buildCallback($what, $data)
+    {
+        if (($what == 'cmdoutput' && $this->debug > 1) ||
+            ($what == 'output' && $this->debug > 0)) {
+            $this->ui->outputData(rtrim($data), 'build');
+        }
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Build.xml b/WEB-INF/lib/pear/PEAR/Command/Build.xml
new file mode 100644 (file)
index 0000000..ec4e6f5
--- /dev/null
@@ -0,0 +1,10 @@
+<commands version="1.0">
+ <build>
+  <summary>Build an Extension From C Source</summary>
+  <function>doBuild</function>
+  <shortcut>b</shortcut>
+  <options />
+  <doc>[package.xml]
+Builds one or more extensions contained in a package.</doc>
+ </build>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Channels.php b/WEB-INF/lib/pear/PEAR/Command/Channels.php
new file mode 100644 (file)
index 0000000..fcf01b5
--- /dev/null
@@ -0,0 +1,883 @@
+<?php
+// /* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * PEAR_Command_Channels (list-channels, update-channels, channel-delete, channel-add,
+ * channel-update, channel-info, channel-alias, channel-discover commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Channels.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500);
+
+/**
+ * PEAR commands for managing channels.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Command_Channels extends PEAR_Command_Common
+{
+    var $commands = array(
+        'list-channels' => array(
+            'summary' => 'List Available Channels',
+            'function' => 'doList',
+            'shortcut' => 'lc',
+            'options' => array(),
+            'doc' => '
+List all available channels for installation.
+',
+            ),
+        'update-channels' => array(
+            'summary' => 'Update the Channel List',
+            'function' => 'doUpdateAll',
+            'shortcut' => 'uc',
+            'options' => array(),
+            'doc' => '
+List all installed packages in all channels.
+'
+            ),
+        'channel-delete' => array(
+            'summary' => 'Remove a Channel From the List',
+            'function' => 'doDelete',
+            'shortcut' => 'cde',
+            'options' => array(),
+            'doc' => '<channel name>
+Delete a channel from the registry.  You may not
+remove any channel that has installed packages.
+'
+            ),
+        'channel-add' => array(
+            'summary' => 'Add a Channel',
+            'function' => 'doAdd',
+            'shortcut' => 'ca',
+            'options' => array(),
+            'doc' => '<channel.xml>
+Add a private channel to the channel list.  Note that all
+public channels should be synced using "update-channels".
+Parameter may be either a local file or remote URL to a
+channel.xml.
+'
+            ),
+        'channel-update' => array(
+            'summary' => 'Update an Existing Channel',
+            'function' => 'doUpdate',
+            'shortcut' => 'cu',
+            'options' => array(
+                'force' => array(
+                    'shortopt' => 'f',
+                    'doc' => 'will force download of new channel.xml if an existing channel name is used',
+                    ),
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'arg' => 'CHANNEL',
+                    'doc' => 'will force download of new channel.xml if an existing channel name is used',
+                    ),
+),
+            'doc' => '[<channel.xml>|<channel name>]
+Update a channel in the channel list directly.  Note that all
+public channels can be synced using "update-channels".
+Parameter may be a local or remote channel.xml, or the name of
+an existing channel.
+'
+            ),
+        'channel-info' => array(
+            'summary' => 'Retrieve Information on a Channel',
+            'function' => 'doInfo',
+            'shortcut' => 'ci',
+            'options' => array(),
+            'doc' => '<package>
+List the files in an installed package.
+'
+            ),
+        'channel-alias' => array(
+            'summary' => 'Specify an alias to a channel name',
+            'function' => 'doAlias',
+            'shortcut' => 'cha',
+            'options' => array(),
+            'doc' => '<channel> <alias>
+Specify a specific alias to use for a channel name.
+The alias may not be an existing channel name or
+alias.
+'
+            ),
+        'channel-discover' => array(
+            'summary' => 'Initialize a Channel from its server',
+            'function' => 'doDiscover',
+            'shortcut' => 'di',
+            'options' => array(),
+            'doc' => '[<channel.xml>|<channel name>]
+Initialize a channel from its server and create a local channel.xml.
+If <channel name> is in the format "<username>:<password>@<channel>" then
+<username> and <password> will be set as the login username/password for
+<channel>. Use caution when passing the username/password in this way, as
+it may allow other users on your computer to briefly view your username/
+password via the system\'s process list.
+'
+            ),
+        'channel-login' => array(
+            'summary' => 'Connects and authenticates to remote channel server',
+            'shortcut' => 'cli',
+            'function' => 'doLogin',
+            'options' => array(),
+            'doc' => '<channel name>
+Log in to a remote channel server.  If <channel name> is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first.  The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems).  After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.',
+            ),
+        'channel-logout' => array(
+            'summary' => 'Logs out from the remote channel server',
+            'shortcut' => 'clo',
+            'function' => 'doLogout',
+            'options' => array(),
+            'doc' => '<channel name>
+Logs out from a remote channel server.  If <channel name> is not supplied,
+the default channel is used. This command does not actually connect to the
+remote server, it only deletes the stored username and password from your user
+configuration.',
+            ),
+        );
+
+    /**
+     * PEAR_Command_Registry constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Channels(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function _sortChannels($a, $b)
+    {
+        return strnatcasecmp($a->getName(), $b->getName());
+    }
+
+    function doList($command, $options, $params)
+    {
+        $reg = &$this->config->getRegistry();
+        $registered = $reg->getChannels();
+        usort($registered, array(&$this, '_sortchannels'));
+        $i = $j = 0;
+        $data = array(
+            'caption' => 'Registered Channels:',
+            'border' => true,
+            'headline' => array('Channel', 'Alias', 'Summary')
+            );
+        foreach ($registered as $channel) {
+            $data['data'][] = array($channel->getName(),
+                                    $channel->getAlias(),
+                                    $channel->getSummary());
+        }
+
+        if (count($registered) === 0) {
+            $data = '(no registered channels)';
+        }
+        $this->ui->outputData($data, $command);
+        return true;
+    }
+
+    function doUpdateAll($command, $options, $params)
+    {
+        $reg = &$this->config->getRegistry();
+        $channels = $reg->getChannels();
+
+        $success = true;
+        foreach ($channels as $channel) {
+            if ($channel->getName() != '__uri') {
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $err = $this->doUpdate('channel-update',
+                                          $options,
+                                          array($channel->getName()));
+                if (PEAR::isError($err)) {
+                    $this->ui->outputData($err->getMessage(), $command);
+                    $success = false;
+                } else {
+                    $success &= $err;
+                }
+            }
+        }
+        return $success;
+    }
+
+    function doInfo($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError("No channel specified");
+        }
+
+        $reg     = &$this->config->getRegistry();
+        $channel = strtolower($params[0]);
+        if ($reg->channelExists($channel)) {
+            $chan = $reg->getChannel($channel);
+            if (PEAR::isError($chan)) {
+                return $this->raiseError($chan);
+            }
+        } else {
+            if (strpos($channel, '://')) {
+                $downloader = &$this->getDownloader();
+                $tmpdir = $this->config->get('temp_dir');
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir);
+                PEAR::staticPopErrorHandling();
+                if (PEAR::isError($loc)) {
+                    return $this->raiseError('Cannot open "' . $channel .
+                        '" (' . $loc->getMessage() . ')');
+                } else {
+                    $contents = implode('', file($loc));
+                }
+            } else {
+                if (!file_exists($params[0])) {
+                    return $this->raiseError('Unknown channel "' . $channel . '"');
+                }
+
+                $fp = fopen($params[0], 'r');
+                if (!$fp) {
+                    return $this->raiseError('Cannot open "' . $params[0] . '"');
+                }
+
+                $contents = '';
+                while (!feof($fp)) {
+                    $contents .= fread($fp, 1024);
+                }
+                fclose($fp);
+            }
+
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $chan = new PEAR_ChannelFile;
+            $chan->fromXmlString($contents);
+            $chan->validate();
+            if ($errs = $chan->getErrors(true)) {
+                foreach ($errs as $err) {
+                    $this->ui->outputData($err['level'] . ': ' . $err['message']);
+                }
+                return $this->raiseError('Channel file "' . $params[0] . '" is not valid');
+            }
+        }
+
+        if (!$chan) {
+            return $this->raiseError('Serious error: Channel "' . $params[0] .
+                '" has a corrupted registry entry');
+        }
+
+        $channel = $chan->getName();
+        $caption = 'Channel ' . $channel . ' Information:';
+        $data1 = array(
+            'caption' => $caption,
+            'border' => true);
+        $data1['data']['server'] = array('Name and Server', $chan->getName());
+        if ($chan->getAlias() != $chan->getName()) {
+            $data1['data']['alias'] = array('Alias', $chan->getAlias());
+        }
+
+        $data1['data']['summary'] = array('Summary', $chan->getSummary());
+        $validate = $chan->getValidationPackage();
+        $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']);
+        $data1['data']['vpackageversion'] =
+            array('Validation Package Version', $validate['attribs']['version']);
+        $d = array();
+        $d['main'] = $data1;
+
+        $data['data'] = array();
+        $data['caption'] = 'Server Capabilities';
+        $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
+        if ($chan->supportsREST()) {
+            if ($chan->supportsREST()) {
+                $funcs = $chan->getFunctions('rest');
+                if (!isset($funcs[0])) {
+                    $funcs = array($funcs);
+                }
+                foreach ($funcs as $protocol) {
+                    $data['data'][] = array('rest', $protocol['attribs']['type'],
+                        $protocol['_content']);
+                }
+            }
+        } else {
+            $data['data'][] = array('No supported protocols');
+        }
+
+        $d['protocols'] = $data;
+        $data['data'] = array();
+        $mirrors = $chan->getMirrors();
+        if ($mirrors) {
+            $data['caption'] = 'Channel ' . $channel . ' Mirrors:';
+            unset($data['headline']);
+            foreach ($mirrors as $mirror) {
+                $data['data'][] = array($mirror['attribs']['host']);
+                $d['mirrors'] = $data;
+            }
+
+            foreach ($mirrors as $i => $mirror) {
+                $data['data'] = array();
+                $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities';
+                $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
+                if ($chan->supportsREST($mirror['attribs']['host'])) {
+                    if ($chan->supportsREST($mirror['attribs']['host'])) {
+                        $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']);
+                        if (!isset($funcs[0])) {
+                            $funcs = array($funcs);
+                        }
+
+                        foreach ($funcs as $protocol) {
+                            $data['data'][] = array('rest', $protocol['attribs']['type'],
+                                $protocol['_content']);
+                        }
+                    }
+                } else {
+                    $data['data'][] = array('No supported protocols');
+                }
+                $d['mirrorprotocols' . $i] = $data;
+            }
+        }
+        $this->ui->outputData($d, 'channel-info');
+    }
+
+    // }}}
+
+    function doDelete($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError('channel-delete: no channel specified');
+        }
+
+        $reg = &$this->config->getRegistry();
+        if (!$reg->channelExists($params[0])) {
+            return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist');
+        }
+
+        $channel = $reg->channelName($params[0]);
+        if ($channel == 'pear.php.net') {
+            return $this->raiseError('Cannot delete the pear.php.net channel');
+        }
+
+        if ($channel == 'pecl.php.net') {
+            return $this->raiseError('Cannot delete the pecl.php.net channel');
+        }
+
+        if ($channel == 'doc.php.net') {
+            return $this->raiseError('Cannot delete the doc.php.net channel');
+        }
+
+        if ($channel == '__uri') {
+            return $this->raiseError('Cannot delete the __uri pseudo-channel');
+        }
+
+        if (PEAR::isError($err = $reg->listPackages($channel))) {
+            return $err;
+        }
+
+        if (count($err)) {
+            return $this->raiseError('Channel "' . $channel .
+                '" has installed packages, cannot delete');
+        }
+
+        if (!$reg->deleteChannel($channel)) {
+            return $this->raiseError('Channel "' . $channel . '" deletion failed');
+        } else {
+            $this->config->deleteChannel($channel);
+            $this->ui->outputData('Channel "' . $channel . '" deleted', $command);
+        }
+    }
+
+    function doAdd($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError('channel-add: no channel file specified');
+        }
+
+        if (strpos($params[0], '://')) {
+            $downloader = &$this->getDownloader();
+            $tmpdir = $this->config->get('temp_dir');
+            if (!file_exists($tmpdir)) {
+                require_once 'System.php';
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $err = System::mkdir(array('-p', $tmpdir));
+                PEAR::staticPopErrorHandling();
+                if (PEAR::isError($err)) {
+                    return $this->raiseError('channel-add: temp_dir does not exist: "' .
+                        $tmpdir .
+                        '" - You can change this location with "pear config-set temp_dir"');
+                }
+            }
+
+            if (!is_writable($tmpdir)) {
+                return $this->raiseError('channel-add: temp_dir is not writable: "' .
+                    $tmpdir .
+                    '" - You can change this location with "pear config-set temp_dir"');
+            }
+
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false);
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($loc)) {
+                return $this->raiseError('channel-add: Cannot open "' . $params[0] .
+                    '" (' . $loc->getMessage() . ')');
+            }
+
+            list($loc, $lastmodified) = $loc;
+            $contents = implode('', file($loc));
+        } else {
+            $lastmodified = $fp = false;
+            if (file_exists($params[0])) {
+                $fp = fopen($params[0], 'r');
+            }
+
+            if (!$fp) {
+                return $this->raiseError('channel-add: cannot open "' . $params[0] . '"');
+            }
+
+            $contents = '';
+            while (!feof($fp)) {
+                $contents .= fread($fp, 1024);
+            }
+            fclose($fp);
+        }
+
+        if (!class_exists('PEAR_ChannelFile')) {
+            require_once 'PEAR/ChannelFile.php';
+        }
+
+        $channel = new PEAR_ChannelFile;
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $result = $channel->fromXmlString($contents);
+        PEAR::staticPopErrorHandling();
+        if (!$result) {
+            $exit = false;
+            if (count($errors = $channel->getErrors(true))) {
+                foreach ($errors as $error) {
+                    $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
+                    if (!$exit) {
+                        $exit = $error['level'] == 'error' ? true : false;
+                    }
+                }
+                if ($exit) {
+                    return $this->raiseError('channel-add: invalid channel.xml file');
+                }
+            }
+        }
+
+        $reg = &$this->config->getRegistry();
+        if ($reg->channelExists($channel->getName())) {
+            return $this->raiseError('channel-add: Channel "' . $channel->getName() .
+                '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
+        }
+
+        $ret = $reg->addChannel($channel, $lastmodified);
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        if (!$ret) {
+            return $this->raiseError('channel-add: adding Channel "' . $channel->getName() .
+                '" to registry failed');
+        }
+
+        $this->config->setChannels($reg->listChannels());
+        $this->config->writeConfigFile();
+        $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command);
+    }
+
+    function doUpdate($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError("No channel file specified");
+        }
+
+        $tmpdir = $this->config->get('temp_dir');
+        if (!file_exists($tmpdir)) {
+            require_once 'System.php';
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $err = System::mkdir(array('-p', $tmpdir));
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($err)) {
+                return $this->raiseError('channel-add: temp_dir does not exist: "' .
+                    $tmpdir .
+                    '" - You can change this location with "pear config-set temp_dir"');
+            }
+        }
+
+        if (!is_writable($tmpdir)) {
+            return $this->raiseError('channel-add: temp_dir is not writable: "' .
+                $tmpdir .
+                '" - You can change this location with "pear config-set temp_dir"');
+        }
+
+        $reg = &$this->config->getRegistry();
+        $lastmodified = false;
+        if ((!file_exists($params[0]) || is_dir($params[0]))
+              && $reg->channelExists(strtolower($params[0]))) {
+            $c = $reg->getChannel(strtolower($params[0]));
+            if (PEAR::isError($c)) {
+                return $this->raiseError($c);
+            }
+
+            $this->ui->outputData("Updating channel \"$params[0]\"", $command);
+            $dl = &$this->getDownloader(array());
+            // if force is specified, use a timestamp of "1" to force retrieval
+            $lastmodified = isset($options['force']) ? false : $c->lastModified();
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml',
+                $this->ui, $tmpdir, null, $lastmodified);
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($contents)) {
+                // Attempt to fall back to https
+                $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage());
+                $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead");
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml',
+                    $this->ui, $tmpdir, null, $lastmodified);
+                PEAR::staticPopErrorHandling();
+                if (PEAR::isError($contents)) {
+                    return $this->raiseError('Cannot retrieve channel.xml for channel "' .
+                        $c->getName() . '" (' . $contents->getMessage() . ')');
+                }
+            }
+
+            list($contents, $lastmodified) = $contents;
+            if (!$contents) {
+                $this->ui->outputData("Channel \"$params[0]\" is up to date");
+                return;
+            }
+
+            $contents = implode('', file($contents));
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $channel = new PEAR_ChannelFile;
+            $channel->fromXmlString($contents);
+            if (!$channel->getErrors()) {
+                // security check: is the downloaded file for the channel we got it from?
+                if (strtolower($channel->getName()) != strtolower($c->getName())) {
+                    if (!isset($options['force'])) {
+                        return $this->raiseError('ERROR: downloaded channel definition file' .
+                            ' for channel "' . $channel->getName() . '" from channel "' .
+                            strtolower($c->getName()) . '"');
+                    }
+
+                    $this->ui->log(0, 'WARNING: downloaded channel definition file' .
+                        ' for channel "' . $channel->getName() . '" from channel "' .
+                        strtolower($c->getName()) . '"');
+                }
+            }
+        } else {
+            if (strpos($params[0], '://')) {
+                $dl = &$this->getDownloader();
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $loc = $dl->downloadHttp($params[0],
+                    $this->ui, $tmpdir, null, $lastmodified);
+                PEAR::staticPopErrorHandling();
+                if (PEAR::isError($loc)) {
+                    return $this->raiseError("Cannot open " . $params[0] .
+                         ' (' . $loc->getMessage() . ')');
+                }
+
+                list($loc, $lastmodified) = $loc;
+                $contents = implode('', file($loc));
+            } else {
+                $fp = false;
+                if (file_exists($params[0])) {
+                    $fp = fopen($params[0], 'r');
+                }
+
+                if (!$fp) {
+                    return $this->raiseError("Cannot open " . $params[0]);
+                }
+
+                $contents = '';
+                while (!feof($fp)) {
+                    $contents .= fread($fp, 1024);
+                }
+                fclose($fp);
+            }
+
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $channel = new PEAR_ChannelFile;
+            $channel->fromXmlString($contents);
+        }
+
+        $exit = false;
+        if (count($errors = $channel->getErrors(true))) {
+            foreach ($errors as $error) {
+                $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
+                if (!$exit) {
+                    $exit = $error['level'] == 'error' ? true : false;
+                }
+            }
+            if ($exit) {
+                return $this->raiseError('Invalid channel.xml file');
+            }
+        }
+
+        if (!$reg->channelExists($channel->getName())) {
+            return $this->raiseError('Error: Channel "' . $channel->getName() .
+                '" does not exist, use channel-add to add an entry');
+        }
+
+        $ret = $reg->updateChannel($channel, $lastmodified);
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        if (!$ret) {
+            return $this->raiseError('Updating Channel "' . $channel->getName() .
+                '" in registry failed');
+        }
+
+        $this->config->setChannels($reg->listChannels());
+        $this->config->writeConfigFile();
+        $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded');
+    }
+
+    function &getDownloader()
+    {
+        if (!class_exists('PEAR_Downloader')) {
+            require_once 'PEAR/Downloader.php';
+        }
+        $a = new PEAR_Downloader($this->ui, array(), $this->config);
+        return $a;
+    }
+
+    function doAlias($command, $options, $params)
+    {
+        if (count($params) === 1) {
+            return $this->raiseError('No channel alias specified');
+        }
+
+        if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) {
+            return $this->raiseError(
+                'Invalid format, correct is: channel-alias channel alias');
+        }
+
+        $reg = &$this->config->getRegistry();
+        if (!$reg->channelExists($params[0], true)) {
+            $extra = '';
+            if ($reg->isAlias($params[0])) {
+                $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' .
+                    strtolower($params[1]) . '")';
+            }
+
+            return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra);
+        }
+
+        if ($reg->isAlias($params[1])) {
+            return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' .
+                'already aliased to "' . strtolower($params[1]) . '", cannot re-alias');
+        }
+
+        $chan = &$reg->getChannel($params[0]);
+        if (PEAR::isError($chan)) {
+            return $this->raiseError('Corrupt registry?  Error retrieving channel "' . $params[0] .
+                '" information (' . $chan->getMessage() . ')');
+        }
+
+        // make it a local alias
+        if (!$chan->setAlias(strtolower($params[1]), true)) {
+            return $this->raiseError('Alias "' . strtolower($params[1]) .
+                '" is not a valid channel alias');
+        }
+
+        $reg->updateChannel($chan);
+        $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' .
+            strtolower($params[1]) . '"');
+    }
+
+    /**
+     * The channel-discover command
+     *
+     * @param string $command command name
+     * @param array  $options option_name => value
+     * @param array  $params  list of additional parameters.
+     *               $params[0] should contain a string with either:
+     *               - <channel name> or
+     *               - <username>:<password>@<channel name>
+     * @return null|PEAR_Error
+     */
+    function doDiscover($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError("No channel server specified");
+        }
+
+        // Look for the possible input format "<username>:<password>@<channel>"
+        if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) {
+            $username = $matches[1];
+            $password = $matches[2];
+            $channel  = $matches[3];
+        } else {
+            $channel = $params[0];
+        }
+
+        $reg = &$this->config->getRegistry();
+        if ($reg->channelExists($channel)) {
+            if (!$reg->isAlias($channel)) {
+                return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
+            }
+
+            return $this->raiseError("A channel alias named \"$channel\" " .
+                'already exists, aliasing channel "' . $reg->channelName($channel)
+                . '"');
+        }
+
+        $this->pushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml'));
+        $this->popErrorHandling();
+        if (PEAR::isError($err)) {
+            if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) {
+                return $this->raiseError("Discovery of channel \"$channel\" failed (" .
+                    $err->getMessage() . ')');
+            }
+            // Attempt fetch via https
+            $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage());
+            $this->ui->outputData("Trying to discover channel $channel over https:// instead");
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml'));
+            $this->popErrorHandling();
+            if (PEAR::isError($err)) {
+                return $this->raiseError("Discovery of channel \"$channel\" failed (" .
+                    $err->getMessage() . ')');
+            }
+        }
+
+        // Store username/password if they were given
+        // Arguably we should do a logintest on the channel here, but since
+        // that's awkward on a REST-based channel (even "pear login" doesn't
+        // do it for those), and XML-RPC is deprecated, it's fairly pointless.
+        if (isset($username)) {
+            $this->config->set('username', $username, 'user', $channel);
+            $this->config->set('password', $password, 'user', $channel);
+            $this->config->store();
+            $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command);
+        }
+
+        $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command);
+    }
+
+    /**
+     * Execute the 'login' command.
+     *
+     * @param string $command command name
+     * @param array $options option_name => value
+     * @param array $params list of additional parameters
+     *
+     * @return bool TRUE on success or
+     * a PEAR error on failure
+     *
+     * @access public
+     */
+    function doLogin($command, $options, $params)
+    {
+        $reg = &$this->config->getRegistry();
+
+        // If a parameter is supplied, use that as the channel to log in to
+        $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
+
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($chan)) {
+            return $this->raiseError($chan);
+        }
+
+        $server   = $this->config->get('preferred_mirror', null, $channel);
+        $username = $this->config->get('username',         null, $channel);
+        if (empty($username)) {
+            $username = isset($_ENV['USER']) ? $_ENV['USER'] : null;
+        }
+        $this->ui->outputData("Logging in to $server.", $command);
+
+        list($username, $password) = $this->ui->userDialog(
+            $command,
+            array('Username', 'Password'),
+            array('text',     'password'),
+            array($username,  '')
+            );
+        $username = trim($username);
+        $password = trim($password);
+
+        $ourfile = $this->config->getConfFile('user');
+        if (!$ourfile) {
+            $ourfile = $this->config->getConfFile('system');
+        }
+
+        $this->config->set('username', $username, 'user', $channel);
+        $this->config->set('password', $password, 'user', $channel);
+
+        if ($chan->supportsREST()) {
+            $ok = true;
+        }
+
+        if ($ok !== true) {
+            return $this->raiseError('Login failed!');
+        }
+
+        $this->ui->outputData("Logged in.", $command);
+        // avoid changing any temporary settings changed with -d
+        $ourconfig = new PEAR_Config($ourfile, $ourfile);
+        $ourconfig->set('username', $username, 'user', $channel);
+        $ourconfig->set('password', $password, 'user', $channel);
+        $ourconfig->store();
+
+        return true;
+    }
+
+    /**
+     * Execute the 'logout' command.
+     *
+     * @param string $command command name
+     * @param array $options option_name => value
+     * @param array $params list of additional parameters
+     *
+     * @return bool TRUE on success or
+     * a PEAR error on failure
+     *
+     * @access public
+     */
+    function doLogout($command, $options, $params)
+    {
+        $reg     = &$this->config->getRegistry();
+
+        // If a parameter is supplied, use that as the channel to log in to
+        $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
+
+        $chan    = $reg->getChannel($channel);
+        if (PEAR::isError($chan)) {
+            return $this->raiseError($chan);
+        }
+
+        $server = $this->config->get('preferred_mirror', null, $channel);
+        $this->ui->outputData("Logging out from $server.", $command);
+        $this->config->remove('username', 'user', $channel);
+        $this->config->remove('password', 'user', $channel);
+        $this->config->store();
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Channels.xml b/WEB-INF/lib/pear/PEAR/Command/Channels.xml
new file mode 100644 (file)
index 0000000..47b7206
--- /dev/null
@@ -0,0 +1,123 @@
+<commands version="1.0">
+ <list-channels>
+  <summary>List Available Channels</summary>
+  <function>doList</function>
+  <shortcut>lc</shortcut>
+  <options />
+  <doc>
+List all available channels for installation.
+</doc>
+ </list-channels>
+ <update-channels>
+  <summary>Update the Channel List</summary>
+  <function>doUpdateAll</function>
+  <shortcut>uc</shortcut>
+  <options />
+  <doc>
+List all installed packages in all channels.
+</doc>
+ </update-channels>
+ <channel-delete>
+  <summary>Remove a Channel From the List</summary>
+  <function>doDelete</function>
+  <shortcut>cde</shortcut>
+  <options />
+  <doc>&lt;channel name&gt;
+Delete a channel from the registry.  You may not
+remove any channel that has installed packages.
+</doc>
+ </channel-delete>
+ <channel-add>
+  <summary>Add a Channel</summary>
+  <function>doAdd</function>
+  <shortcut>ca</shortcut>
+  <options />
+  <doc>&lt;channel.xml&gt;
+Add a private channel to the channel list.  Note that all
+public channels should be synced using &quot;update-channels&quot;.
+Parameter may be either a local file or remote URL to a
+channel.xml.
+</doc>
+ </channel-add>
+ <channel-update>
+  <summary>Update an Existing Channel</summary>
+  <function>doUpdate</function>
+  <shortcut>cu</shortcut>
+  <options>
+   <force>
+    <shortopt>f</shortopt>
+    <doc>will force download of new channel.xml if an existing channel name is used</doc>
+   </force>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>will force download of new channel.xml if an existing channel name is used</doc>
+    <arg>CHANNEL</arg>
+   </channel>
+  </options>
+  <doc>[&lt;channel.xml&gt;|&lt;channel name&gt;]
+Update a channel in the channel list directly.  Note that all
+public channels can be synced using &quot;update-channels&quot;.
+Parameter may be a local or remote channel.xml, or the name of
+an existing channel.
+</doc>
+ </channel-update>
+ <channel-info>
+  <summary>Retrieve Information on a Channel</summary>
+  <function>doInfo</function>
+  <shortcut>ci</shortcut>
+  <options />
+  <doc>&lt;package&gt;
+List the files in an installed package.
+</doc>
+ </channel-info>
+ <channel-alias>
+  <summary>Specify an alias to a channel name</summary>
+  <function>doAlias</function>
+  <shortcut>cha</shortcut>
+  <options />
+  <doc>&lt;channel&gt; &lt;alias&gt;
+Specify a specific alias to use for a channel name.
+The alias may not be an existing channel name or
+alias.
+</doc>
+ </channel-alias>
+ <channel-discover>
+  <summary>Initialize a Channel from its server</summary>
+  <function>doDiscover</function>
+  <shortcut>di</shortcut>
+  <options />
+  <doc>[&lt;channel.xml&gt;|&lt;channel name&gt;]
+Initialize a channel from its server and create a local channel.xml.
+If &lt;channel name&gt; is in the format &quot;&lt;username&gt;:&lt;password&gt;@&lt;channel&gt;&quot; then
+&lt;username&gt; and &lt;password&gt; will be set as the login username/password for
+&lt;channel&gt;. Use caution when passing the username/password in this way, as
+it may allow other users on your computer to briefly view your username/
+password via the system&#039;s process list.
+</doc>
+ </channel-discover>
+ <channel-login>
+  <summary>Connects and authenticates to remote channel server</summary>
+  <function>doLogin</function>
+  <shortcut>cli</shortcut>
+  <options />
+  <doc>&lt;channel name&gt;
+Log in to a remote channel server.  If &lt;channel name&gt; is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first.  The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems).  After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.</doc>
+ </channel-login>
+ <channel-logout>
+  <summary>Logs out from the remote channel server</summary>
+  <function>doLogout</function>
+  <shortcut>clo</shortcut>
+  <options />
+  <doc>&lt;channel name&gt;
+Logs out from a remote channel server.  If &lt;channel name&gt; is not supplied,
+the default channel is used. This command does not actually connect to the
+remote server, it only deletes the stored username and password from your user
+configuration.</doc>
+ </channel-logout>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Common.php b/WEB-INF/lib/pear/PEAR/Command/Common.php
new file mode 100644 (file)
index 0000000..279a716
--- /dev/null
@@ -0,0 +1,273 @@
+<?php
+/**
+ * PEAR_Command_Common base class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR.php';
+
+/**
+ * PEAR commands base class
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Common extends PEAR
+{
+    /**
+     * PEAR_Config object used to pass user system and configuration
+     * on when executing commands
+     *
+     * @var PEAR_Config
+     */
+    var $config;
+    /**
+     * @var PEAR_Registry
+     * @access protected
+     */
+    var $_registry;
+
+    /**
+     * User Interface object, for all interaction with the user.
+     * @var object
+     */
+    var $ui;
+
+    var $_deps_rel_trans = array(
+                                 'lt' => '<',
+                                 'le' => '<=',
+                                 'eq' => '=',
+                                 'ne' => '!=',
+                                 'gt' => '>',
+                                 'ge' => '>=',
+                                 'has' => '=='
+                                 );
+
+    var $_deps_type_trans = array(
+                                  'pkg' => 'package',
+                                  'ext' => 'extension',
+                                  'php' => 'PHP',
+                                  'prog' => 'external program',
+                                  'ldlib' => 'external library for linking',
+                                  'rtlib' => 'external runtime library',
+                                  'os' => 'operating system',
+                                  'websrv' => 'web server',
+                                  'sapi' => 'SAPI backend'
+                                  );
+
+    /**
+     * PEAR_Command_Common constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Common(&$ui, &$config)
+    {
+        parent::PEAR();
+        $this->config = &$config;
+        $this->ui = &$ui;
+    }
+
+    /**
+     * Return a list of all the commands defined by this class.
+     * @return array list of commands
+     * @access public
+     */
+    function getCommands()
+    {
+        $ret = array();
+        foreach (array_keys($this->commands) as $command) {
+            $ret[$command] = $this->commands[$command]['summary'];
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Return a list of all the command shortcuts defined by this class.
+     * @return array shortcut => command
+     * @access public
+     */
+    function getShortcuts()
+    {
+        $ret = array();
+        foreach (array_keys($this->commands) as $command) {
+            if (isset($this->commands[$command]['shortcut'])) {
+                $ret[$this->commands[$command]['shortcut']] = $command;
+            }
+        }
+
+        return $ret;
+    }
+
+    function getOptions($command)
+    {
+        $shortcuts = $this->getShortcuts();
+        if (isset($shortcuts[$command])) {
+            $command = $shortcuts[$command];
+        }
+
+        if (isset($this->commands[$command]) &&
+              isset($this->commands[$command]['options'])) {
+            return $this->commands[$command]['options'];
+        }
+
+        return null;
+    }
+
+    function getGetoptArgs($command, &$short_args, &$long_args)
+    {
+        $short_args = '';
+        $long_args = array();
+        if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) {
+            return;
+        }
+
+        reset($this->commands[$command]['options']);
+        while (list($option, $info) = each($this->commands[$command]['options'])) {
+            $larg = $sarg = '';
+            if (isset($info['arg'])) {
+                if ($info['arg']{0} == '(') {
+                    $larg = '==';
+                    $sarg = '::';
+                    $arg = substr($info['arg'], 1, -1);
+                } else {
+                    $larg = '=';
+                    $sarg = ':';
+                    $arg = $info['arg'];
+                }
+            }
+
+            if (isset($info['shortopt'])) {
+                $short_args .= $info['shortopt'] . $sarg;
+            }
+
+            $long_args[] = $option . $larg;
+        }
+    }
+
+    /**
+    * Returns the help message for the given command
+    *
+    * @param string $command The command
+    * @return mixed A fail string if the command does not have help or
+    *               a two elements array containing [0]=>help string,
+    *               [1]=> help string for the accepted cmd args
+    */
+    function getHelp($command)
+    {
+        $config = &PEAR_Config::singleton();
+        if (!isset($this->commands[$command])) {
+            return "No such command \"$command\"";
+        }
+
+        $help = null;
+        if (isset($this->commands[$command]['doc'])) {
+            $help = $this->commands[$command]['doc'];
+        }
+
+        if (empty($help)) {
+            // XXX (cox) Fallback to summary if there is no doc (show both?)
+            if (!isset($this->commands[$command]['summary'])) {
+                return "No help for command \"$command\"";
+            }
+            $help = $this->commands[$command]['summary'];
+        }
+
+        if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) {
+            foreach($matches[0] as $k => $v) {
+                $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help);
+            }
+        }
+
+        return array($help, $this->getHelpArgs($command));
+    }
+
+    /**
+     * Returns the help for the accepted arguments of a command
+     *
+     * @param  string $command
+     * @return string The help string
+     */
+    function getHelpArgs($command)
+    {
+        if (isset($this->commands[$command]['options']) &&
+            count($this->commands[$command]['options']))
+        {
+            $help = "Options:\n";
+            foreach ($this->commands[$command]['options'] as $k => $v) {
+                if (isset($v['arg'])) {
+                    if ($v['arg'][0] == '(') {
+                        $arg = substr($v['arg'], 1, -1);
+                        $sapp = " [$arg]";
+                        $lapp = "[=$arg]";
+                    } else {
+                        $sapp = " $v[arg]";
+                        $lapp = "=$v[arg]";
+                    }
+                } else {
+                    $sapp = $lapp = "";
+                }
+
+                if (isset($v['shortopt'])) {
+                    $s = $v['shortopt'];
+                    $help .= "  -$s$sapp, --$k$lapp\n";
+                } else {
+                    $help .= "  --$k$lapp\n";
+                }
+
+                $p = "        ";
+                $doc = rtrim(str_replace("\n", "\n$p", $v['doc']));
+                $help .= "        $doc\n";
+            }
+
+            return $help;
+        }
+
+        return null;
+    }
+
+    function run($command, $options, $params)
+    {
+        if (empty($this->commands[$command]['function'])) {
+            // look for shortcuts
+            foreach (array_keys($this->commands) as $cmd) {
+                if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) {
+                    if (empty($this->commands[$cmd]['function'])) {
+                        return $this->raiseError("unknown command `$command'");
+                    } else {
+                        $func = $this->commands[$cmd]['function'];
+                    }
+                    $command = $cmd;
+
+                    //$command = $this->commands[$cmd]['function'];
+                    break;
+                }
+            }
+        } else {
+            $func = $this->commands[$command]['function'];
+        }
+
+        return $this->$func($command, $options, $params);
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Config.php b/WEB-INF/lib/pear/PEAR/Command/Config.php
new file mode 100644 (file)
index 0000000..a761b27
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+/**
+ * PEAR_Command_Config (config-show, config-get, config-set, config-help, config-create commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Config.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for managing configuration data.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Config extends PEAR_Command_Common
+{
+    var $commands = array(
+        'config-show' => array(
+            'summary' => 'Show All Settings',
+            'function' => 'doConfigShow',
+            'shortcut' => 'csh',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'show configuration variables for another channel',
+                    'arg' => 'CHAN',
+                    ),
+),
+            'doc' => '[layer]
+Displays all configuration values.  An optional argument
+may be used to tell which configuration layer to display.  Valid
+configuration layers are "user", "system" and "default". To display
+configurations for different channels, set the default_channel
+configuration variable and run config-show again.
+',
+            ),
+        'config-get' => array(
+            'summary' => 'Show One Setting',
+            'function' => 'doConfigGet',
+            'shortcut' => 'cg',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'show configuration variables for another channel',
+                    'arg' => 'CHAN',
+                    ),
+),
+            'doc' => '<parameter> [layer]
+Displays the value of one configuration parameter.  The
+first argument is the name of the parameter, an optional second argument
+may be used to tell which configuration layer to look in.  Valid configuration
+layers are "user", "system" and "default".  If no layer is specified, a value
+will be picked from the first layer that defines the parameter, in the order
+just specified.  The configuration value will be retrieved for the channel
+specified by the default_channel configuration variable.
+',
+            ),
+        'config-set' => array(
+            'summary' => 'Change Setting',
+            'function' => 'doConfigSet',
+            'shortcut' => 'cs',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'show configuration variables for another channel',
+                    'arg' => 'CHAN',
+                    ),
+),
+            'doc' => '<parameter> <value> [layer]
+Sets the value of one configuration parameter.  The first argument is
+the name of the parameter, the second argument is the new value.  Some
+parameters are subject to validation, and the command will fail with
+an error message if the new value does not make sense.  An optional
+third argument may be used to specify in which layer to set the
+configuration parameter.  The default layer is "user".  The
+configuration value will be set for the current channel, which
+is controlled by the default_channel configuration variable.
+',
+            ),
+        'config-help' => array(
+            'summary' => 'Show Information About Setting',
+            'function' => 'doConfigHelp',
+            'shortcut' => 'ch',
+            'options' => array(),
+            'doc' => '[parameter]
+Displays help for a configuration parameter.  Without arguments it
+displays help for all configuration parameters.
+',
+           ),
+        'config-create' => array(
+            'summary' => 'Create a Default configuration file',
+            'function' => 'doConfigCreate',
+            'shortcut' => 'coc',
+            'options' => array(
+                'windows' => array(
+                    'shortopt' => 'w',
+                    'doc' => 'create a config file for a windows install',
+                    ),
+            ),
+            'doc' => '<root path> <filename>
+Create a default configuration file with all directory configuration
+variables set to subdirectories of <root path>, and save it as <filename>.
+This is useful especially for creating a configuration file for a remote
+PEAR installation (using the --remoteconfig option of install, upgrade,
+and uninstall).
+',
+            ),
+        );
+
+    /**
+     * PEAR_Command_Config constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Config(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function doConfigShow($command, $options, $params)
+    {
+        $layer = null;
+        if (is_array($params)) {
+            $layer = isset($params[0]) ? $params[0] : null;
+        }
+
+        // $params[0] -> the layer
+        if ($error = $this->_checkLayer($layer)) {
+            return $this->raiseError("config-show:$error");
+        }
+
+        $keys = $this->config->getKeys();
+        sort($keys);
+        $channel = isset($options['channel']) ? $options['channel'] :
+            $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        if (!$reg->channelExists($channel)) {
+            return $this->raiseError('Channel "' . $channel . '" does not exist');
+        }
+
+        $channel = $reg->channelName($channel);
+        $data = array('caption' => 'Configuration (channel ' . $channel . '):');
+        foreach ($keys as $key) {
+            $type = $this->config->getType($key);
+            $value = $this->config->get($key, $layer, $channel);
+            if ($type == 'password' && $value) {
+                $value = '********';
+            }
+
+            if ($value === false) {
+                $value = 'false';
+            } elseif ($value === true) {
+                $value = 'true';
+            }
+
+            $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value);
+        }
+
+        foreach ($this->config->getLayers() as $layer) {
+            $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer));
+        }
+
+        $this->ui->outputData($data, $command);
+        return true;
+    }
+
+    function doConfigGet($command, $options, $params)
+    {
+        $args_cnt = is_array($params) ? count($params) : 0;
+        switch ($args_cnt) {
+            case 1:
+                $config_key = $params[0];
+                $layer = null;
+                break;
+            case 2:
+                $config_key = $params[0];
+                $layer = $params[1];
+                if ($error = $this->_checkLayer($layer)) {
+                    return $this->raiseError("config-get:$error");
+                }
+                break;
+            case 0:
+            default:
+                return $this->raiseError("config-get expects 1 or 2 parameters");
+        }
+
+        $reg = &$this->config->getRegistry();
+        $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+        if (!$reg->channelExists($channel)) {
+            return $this->raiseError('Channel "' . $channel . '" does not exist');
+        }
+
+        $channel = $reg->channelName($channel);
+        $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command);
+        return true;
+    }
+
+    function doConfigSet($command, $options, $params)
+    {
+        // $param[0] -> a parameter to set
+        // $param[1] -> the value for the parameter
+        // $param[2] -> the layer
+        $failmsg = '';
+        if (count($params) < 2 || count($params) > 3) {
+            $failmsg .= "config-set expects 2 or 3 parameters";
+            return PEAR::raiseError($failmsg);
+        }
+
+        if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) {
+            $failmsg .= $error;
+            return PEAR::raiseError("config-set:$failmsg");
+        }
+
+        $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        if (!$reg->channelExists($channel)) {
+            return $this->raiseError('Channel "' . $channel . '" does not exist');
+        }
+
+        $channel = $reg->channelName($channel);
+        if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) {
+            return $this->raiseError('Channel "' . $params[1] . '" does not exist');
+        }
+
+        if ($params[0] == 'preferred_mirror'
+            && (
+                !$reg->mirrorExists($channel, $params[1]) &&
+                (!$reg->channelExists($params[1]) || $channel != $params[1])
+            )
+        ) {
+            $msg  = 'Channel Mirror "' . $params[1] . '" does not exist';
+            $msg .= ' in your registry for channel "' . $channel . '".';
+            $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"';
+            $msg .= ' if you believe this mirror should exist as you may';
+            $msg .= ' have outdated channel information.';
+            return $this->raiseError($msg);
+        }
+
+        if (count($params) == 2) {
+            array_push($params, 'user');
+            $layer = 'user';
+        } else {
+            $layer = $params[2];
+        }
+
+        array_push($params, $channel);
+        if (!call_user_func_array(array(&$this->config, 'set'), $params)) {
+            array_pop($params);
+            $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel";
+        } else {
+            $this->config->store($layer);
+        }
+
+        if ($failmsg) {
+            return $this->raiseError($failmsg);
+        }
+
+        $this->ui->outputData('config-set succeeded', $command);
+        return true;
+    }
+
+    function doConfigHelp($command, $options, $params)
+    {
+        if (empty($params)) {
+            $params = $this->config->getKeys();
+        }
+
+        $data['caption']  = "Config help" . ((count($params) == 1) ? " for $params[0]" : '');
+        $data['headline'] = array('Name', 'Type', 'Description');
+        $data['border']   = true;
+        foreach ($params as $name) {
+            $type = $this->config->getType($name);
+            $docs = $this->config->getDocs($name);
+            if ($type == 'set') {
+                $docs = rtrim($docs) . "\nValid set: " .
+                    implode(' ', $this->config->getSetValues($name));
+            }
+
+            $data['data'][] = array($name, $type, $docs);
+        }
+
+        $this->ui->outputData($data, $command);
+    }
+
+    function doConfigCreate($command, $options, $params)
+    {
+        if (count($params) != 2) {
+            return PEAR::raiseError('config-create: must have 2 parameters, root path and ' .
+                'filename to save as');
+        }
+
+        $root = $params[0];
+        // Clean up the DIRECTORY_SEPARATOR mess
+        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+        $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"),
+                             array('/', '/', '/'),
+                            $root);
+        if ($root{0} != '/') {
+            if (!isset($options['windows'])) {
+                return PEAR::raiseError('Root directory must be an absolute path beginning ' .
+                    'with "/", was: "' . $root . '"');
+            }
+
+            if (!preg_match('/^[A-Za-z]:/', $root)) {
+                return PEAR::raiseError('Root directory must be an absolute path beginning ' .
+                    'with "\\" or "C:\\", was: "' . $root . '"');
+            }
+        }
+
+        $windows = isset($options['windows']);
+        if ($windows) {
+            $root = str_replace('/', '\\', $root);
+        }
+
+        if (!file_exists($params[1]) && !@touch($params[1])) {
+            return PEAR::raiseError('Could not create "' . $params[1] . '"');
+        }
+
+        $params[1] = realpath($params[1]);
+        $config = &new PEAR_Config($params[1], '#no#system#config#', false, false);
+        if ($root{strlen($root) - 1} == '/') {
+            $root = substr($root, 0, strlen($root) - 1);
+        }
+
+        $config->noRegistry();
+        $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user');
+        $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data");
+        $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www");
+        $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg");
+        $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext");
+        $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs");
+        $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests");
+        $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache");
+        $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download");
+        $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp");
+        $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear");
+        $config->writeConfigFile();
+        $this->_showConfig($config);
+        $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"',
+            $command);
+    }
+
+    function _showConfig(&$config)
+    {
+        $params = array('user');
+        $keys = $config->getKeys();
+        sort($keys);
+        $channel = 'pear.php.net';
+        $data = array('caption' => 'Configuration (channel ' . $channel . '):');
+        foreach ($keys as $key) {
+            $type = $config->getType($key);
+            $value = $config->get($key, 'user', $channel);
+            if ($type == 'password' && $value) {
+                $value = '********';
+            }
+
+            if ($value === false) {
+                $value = 'false';
+            } elseif ($value === true) {
+                $value = 'true';
+            }
+            $data['data'][$config->getGroup($key)][] =
+                array($config->getPrompt($key) , $key, $value);
+        }
+
+        foreach ($config->getLayers() as $layer) {
+            $data['data']['Config Files'][] =
+                array(ucfirst($layer) . ' Configuration File', 'Filename' ,
+                    $config->getConfFile($layer));
+        }
+
+        $this->ui->outputData($data, 'config-show');
+        return true;
+    }
+
+    /**
+     * Checks if a layer is defined or not
+     *
+     * @param string $layer The layer to search for
+     * @return mixed False on no error or the error message
+     */
+    function _checkLayer($layer = null)
+    {
+        if (!empty($layer) && $layer != 'default') {
+            $layers = $this->config->getLayers();
+            if (!in_array($layer, $layers)) {
+                return " only the layers: \"" . implode('" or "', $layers) . "\" are supported";
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Config.xml b/WEB-INF/lib/pear/PEAR/Command/Config.xml
new file mode 100644 (file)
index 0000000..f64a925
--- /dev/null
@@ -0,0 +1,92 @@
+<commands version="1.0">
+ <config-show>
+  <summary>Show All Settings</summary>
+  <function>doConfigShow</function>
+  <shortcut>csh</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>show configuration variables for another channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+  </options>
+  <doc>[layer]
+Displays all configuration values.  An optional argument
+may be used to tell which configuration layer to display.  Valid
+configuration layers are &quot;user&quot;, &quot;system&quot; and &quot;default&quot;. To display
+configurations for different channels, set the default_channel
+configuration variable and run config-show again.
+</doc>
+ </config-show>
+ <config-get>
+  <summary>Show One Setting</summary>
+  <function>doConfigGet</function>
+  <shortcut>cg</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>show configuration variables for another channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+  </options>
+  <doc>&lt;parameter&gt; [layer]
+Displays the value of one configuration parameter.  The
+first argument is the name of the parameter, an optional second argument
+may be used to tell which configuration layer to look in.  Valid configuration
+layers are &quot;user&quot;, &quot;system&quot; and &quot;default&quot;.  If no layer is specified, a value
+will be picked from the first layer that defines the parameter, in the order
+just specified.  The configuration value will be retrieved for the channel
+specified by the default_channel configuration variable.
+</doc>
+ </config-get>
+ <config-set>
+  <summary>Change Setting</summary>
+  <function>doConfigSet</function>
+  <shortcut>cs</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>show configuration variables for another channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+  </options>
+  <doc>&lt;parameter&gt; &lt;value&gt; [layer]
+Sets the value of one configuration parameter.  The first argument is
+the name of the parameter, the second argument is the new value.  Some
+parameters are subject to validation, and the command will fail with
+an error message if the new value does not make sense.  An optional
+third argument may be used to specify in which layer to set the
+configuration parameter.  The default layer is &quot;user&quot;.  The
+configuration value will be set for the current channel, which
+is controlled by the default_channel configuration variable.
+</doc>
+ </config-set>
+ <config-help>
+  <summary>Show Information About Setting</summary>
+  <function>doConfigHelp</function>
+  <shortcut>ch</shortcut>
+  <options />
+  <doc>[parameter]
+Displays help for a configuration parameter.  Without arguments it
+displays help for all configuration parameters.
+</doc>
+ </config-help>
+ <config-create>
+  <summary>Create a Default configuration file</summary>
+  <function>doConfigCreate</function>
+  <shortcut>coc</shortcut>
+  <options>
+   <windows>
+    <shortopt>w</shortopt>
+    <doc>create a config file for a windows install</doc>
+   </windows>
+  </options>
+  <doc>&lt;root path&gt; &lt;filename&gt;
+Create a default configuration file with all directory configuration
+variables set to subdirectories of &lt;root path&gt;, and save it as &lt;filename&gt;.
+This is useful especially for creating a configuration file for a remote
+PEAR installation (using the --remoteconfig option of install, upgrade,
+and uninstall).
+</doc>
+ </config-create>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Install.php b/WEB-INF/lib/pear/PEAR/Command/Install.php
new file mode 100644 (file)
index 0000000..c035f6d
--- /dev/null
@@ -0,0 +1,1268 @@
+<?php
+/**
+ * PEAR_Command_Install (install, upgrade, upgrade-all, uninstall, bundle, run-scripts commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Install.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for installation or deinstallation/upgrading of
+ * packages.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Install extends PEAR_Command_Common
+{
+    // {{{ properties
+
+    var $commands = array(
+        'install' => array(
+            'summary' => 'Install Package',
+            'function' => 'doInstall',
+            'shortcut' => 'i',
+            'options' => array(
+                'force' => array(
+                    'shortopt' => 'f',
+                    'doc' => 'will overwrite newer installed packages',
+                    ),
+                'loose' => array(
+                    'shortopt' => 'l',
+                    'doc' => 'do not check for recommended dependency version',
+                    ),
+                'nodeps' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'ignore dependencies, install anyway',
+                    ),
+                'register-only' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'do not install files, only register the package as installed',
+                    ),
+                'soft' => array(
+                    'shortopt' => 's',
+                    'doc' => 'soft install, fail silently, or upgrade if already installed',
+                    ),
+                'nobuild' => array(
+                    'shortopt' => 'B',
+                    'doc' => 'don\'t build C extensions',
+                    ),
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'request uncompressed files when downloading',
+                    ),
+                'installroot' => array(
+                    'shortopt' => 'R',
+                    'arg' => 'DIR',
+                    'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM',
+                    ),
+                'packagingroot' => array(
+                    'shortopt' => 'P',
+                    'arg' => 'DIR',
+                    'doc' => 'root directory used when packaging files, like RPM packaging',
+                    ),
+                'ignore-errors' => array(
+                    'doc' => 'force install even if there were errors',
+                    ),
+                'alldeps' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'install all required and optional dependencies',
+                    ),
+                'onlyreqdeps' => array(
+                    'shortopt' => 'o',
+                    'doc' => 'install all required dependencies',
+                    ),
+                'offline' => array(
+                    'shortopt' => 'O',
+                    'doc' => 'do not attempt to download any urls or contact channels',
+                    ),
+                'pretend' => array(
+                    'shortopt' => 'p',
+                    'doc' => 'Only list the packages that would be downloaded',
+                    ),
+                ),
+            'doc' => '[channel/]<package> ...
+Installs one or more PEAR packages.  You can specify a package to
+install in four ways:
+
+"Package-1.0.tgz" : installs from a local file
+
+"http://example.com/Package-1.0.tgz" : installs from
+anywhere on the net.
+
+"package.xml" : installs the package described in
+package.xml.  Useful for testing, or for wrapping a PEAR package in
+another package manager such as RPM.
+
+"Package[-version/state][.tar]" : queries your default channel\'s server
+({config master_server}) and downloads the newest package with
+the preferred quality/state ({config preferred_state}).
+
+To retrieve Package version 1.1, use "Package-1.1," to retrieve
+Package state beta, use "Package-beta."  To retrieve an uncompressed
+file, append .tar (make sure there is no file by the same name first)
+
+To download a package from another channel, prefix with the channel name like
+"channel/Package"
+
+More than one package may be specified at once.  It is ok to mix these
+four ways of specifying packages.
+'),
+        'upgrade' => array(
+            'summary' => 'Upgrade Package',
+            'function' => 'doInstall',
+            'shortcut' => 'up',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'upgrade packages from a specific channel',
+                    'arg' => 'CHAN',
+                    ),
+                'force' => array(
+                    'shortopt' => 'f',
+                    'doc' => 'overwrite newer installed packages',
+                    ),
+                'loose' => array(
+                    'shortopt' => 'l',
+                    'doc' => 'do not check for recommended dependency version',
+                    ),
+                'nodeps' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'ignore dependencies, upgrade anyway',
+                    ),
+                'register-only' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'do not install files, only register the package as upgraded',
+                    ),
+                'nobuild' => array(
+                    'shortopt' => 'B',
+                    'doc' => 'don\'t build C extensions',
+                    ),
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'request uncompressed files when downloading',
+                    ),
+                'installroot' => array(
+                    'shortopt' => 'R',
+                    'arg' => 'DIR',
+                    'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
+                    ),
+                'ignore-errors' => array(
+                    'doc' => 'force install even if there were errors',
+                    ),
+                'alldeps' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'install all required and optional dependencies',
+                    ),
+                'onlyreqdeps' => array(
+                    'shortopt' => 'o',
+                    'doc' => 'install all required dependencies',
+                    ),
+                'offline' => array(
+                    'shortopt' => 'O',
+                    'doc' => 'do not attempt to download any urls or contact channels',
+                    ),
+                'pretend' => array(
+                    'shortopt' => 'p',
+                    'doc' => 'Only list the packages that would be downloaded',
+                    ),
+                ),
+            'doc' => '<package> ...
+Upgrades one or more PEAR packages.  See documentation for the
+"install" command for ways to specify a package.
+
+When upgrading, your package will be updated if the provided new
+package has a higher version number (use the -f option if you need to
+upgrade anyway).
+
+More than one package may be specified at once.
+'),
+        'upgrade-all' => array(
+            'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]',
+            'function' => 'doUpgradeAll',
+            'shortcut' => 'ua',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'upgrade packages from a specific channel',
+                    'arg' => 'CHAN',
+                    ),
+                'nodeps' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'ignore dependencies, upgrade anyway',
+                    ),
+                'register-only' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'do not install files, only register the package as upgraded',
+                    ),
+                'nobuild' => array(
+                    'shortopt' => 'B',
+                    'doc' => 'don\'t build C extensions',
+                    ),
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'request uncompressed files when downloading',
+                    ),
+                'installroot' => array(
+                    'shortopt' => 'R',
+                    'arg' => 'DIR',
+                    'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM',
+                    ),
+                'ignore-errors' => array(
+                    'doc' => 'force install even if there were errors',
+                    ),
+                'loose' => array(
+                    'doc' => 'do not check for recommended dependency version',
+                    ),
+                ),
+            'doc' => '
+WARNING: This function is deprecated in favor of using the upgrade command with no params
+
+Upgrades all packages that have a newer release available.  Upgrades are
+done only if there is a release available of the state specified in
+"preferred_state" (currently {config preferred_state}), or a state considered
+more stable.
+'),
+        'uninstall' => array(
+            'summary' => 'Un-install Package',
+            'function' => 'doUninstall',
+            'shortcut' => 'un',
+            'options' => array(
+                'nodeps' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'ignore dependencies, uninstall anyway',
+                    ),
+                'register-only' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'do not remove files, only register the packages as not installed',
+                    ),
+                'installroot' => array(
+                    'shortopt' => 'R',
+                    'arg' => 'DIR',
+                    'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
+                    ),
+                'ignore-errors' => array(
+                    'doc' => 'force install even if there were errors',
+                    ),
+                'offline' => array(
+                    'shortopt' => 'O',
+                    'doc' => 'do not attempt to uninstall remotely',
+                    ),
+                ),
+            'doc' => '[channel/]<package> ...
+Uninstalls one or more PEAR packages.  More than one package may be
+specified at once.  Prefix with channel name to uninstall from a
+channel not in your default channel ({config default_channel})
+'),
+        'bundle' => array(
+            'summary' => 'Unpacks a Pecl Package',
+            'function' => 'doBundle',
+            'shortcut' => 'bun',
+            'options' => array(
+                'destination' => array(
+                   'shortopt' => 'd',
+                    'arg' => 'DIR',
+                    'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)',
+                    ),
+                'force' => array(
+                    'shortopt' => 'f',
+                    'doc' => 'Force the unpacking even if there were errors in the package',
+                ),
+            ),
+            'doc' => '<package>
+Unpacks a Pecl Package into the selected location. It will download the
+package if needed.
+'),
+        'run-scripts' => array(
+            'summary' => 'Run Post-Install Scripts bundled with a package',
+            'function' => 'doRunScripts',
+            'shortcut' => 'rs',
+            'options' => array(
+            ),
+            'doc' => '<package>
+Run post-installation scripts in package <package>, if any exist.
+'),
+    );
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * PEAR_Command_Install constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Install(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    // }}}
+
+    /**
+     * For unit testing purposes
+     */
+    function &getDownloader(&$ui, $options, &$config)
+    {
+        if (!class_exists('PEAR_Downloader')) {
+            require_once 'PEAR/Downloader.php';
+        }
+        $a = &new PEAR_Downloader($ui, $options, $config);
+        return $a;
+    }
+
+    /**
+     * For unit testing purposes
+     */
+    function &getInstaller(&$ui)
+    {
+        if (!class_exists('PEAR_Installer')) {
+            require_once 'PEAR/Installer.php';
+        }
+        $a = &new PEAR_Installer($ui);
+        return $a;
+    }
+
+    function enableExtension($binaries, $type)
+    {
+        if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) {
+            return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location');
+        }
+        $ini = $this->_parseIni($phpini);
+        if (PEAR::isError($ini)) {
+            return $ini;
+        }
+        $line = 0;
+        if ($type == 'extsrc' || $type == 'extbin') {
+            $search = 'extensions';
+            $enable = 'extension';
+        } else {
+            $search = 'zend_extensions';
+            ob_start();
+            phpinfo(INFO_GENERAL);
+            $info = ob_get_contents();
+            ob_end_clean();
+            $debug = function_exists('leak') ? '_debug' : '';
+            $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+            $enable = 'zend_extension' . $debug . $ts;
+        }
+        foreach ($ini[$search] as $line => $extension) {
+            if (in_array($extension, $binaries, true) || in_array(
+                  $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) {
+                // already enabled - assume if one is, all are
+                return true;
+            }
+        }
+        if ($line) {
+            $newini = array_slice($ini['all'], 0, $line);
+        } else {
+            $newini = array();
+        }
+        foreach ($binaries as $binary) {
+            if ($ini['extension_dir']) {
+                $binary = basename($binary);
+            }
+            $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n");
+        }
+        $newini = array_merge($newini, array_slice($ini['all'], $line));
+        $fp = @fopen($phpini, 'wb');
+        if (!$fp) {
+            return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing');
+        }
+        foreach ($newini as $line) {
+            fwrite($fp, $line);
+        }
+        fclose($fp);
+        return true;
+    }
+
+    function disableExtension($binaries, $type)
+    {
+        if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) {
+            return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location');
+        }
+        $ini = $this->_parseIni($phpini);
+        if (PEAR::isError($ini)) {
+            return $ini;
+        }
+        $line = 0;
+        if ($type == 'extsrc' || $type == 'extbin') {
+            $search = 'extensions';
+            $enable = 'extension';
+        } else {
+            $search = 'zend_extensions';
+            ob_start();
+            phpinfo(INFO_GENERAL);
+            $info = ob_get_contents();
+            ob_end_clean();
+            $debug = function_exists('leak') ? '_debug' : '';
+            $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+            $enable = 'zend_extension' . $debug . $ts;
+        }
+        $found = false;
+        foreach ($ini[$search] as $line => $extension) {
+            if (in_array($extension, $binaries, true) || in_array(
+                  $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) {
+                $found = true;
+                break;
+            }
+        }
+        if (!$found) {
+            // not enabled
+            return true;
+        }
+        $fp = @fopen($phpini, 'wb');
+        if (!$fp) {
+            return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing');
+        }
+        if ($line) {
+            $newini = array_slice($ini['all'], 0, $line);
+            // delete the enable line
+            $newini = array_merge($newini, array_slice($ini['all'], $line + 1));
+        } else {
+            $newini = array_slice($ini['all'], 1);
+        }
+        foreach ($newini as $line) {
+            fwrite($fp, $line);
+        }
+        fclose($fp);
+        return true;
+    }
+
+    function _parseIni($filename)
+    {
+        if (!file_exists($filename)) {
+            return PEAR::raiseError('php.ini "' . $filename . '" does not exist');
+        }
+
+        if (filesize($filename) > 300000) {
+            return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting');
+        }
+
+        ob_start();
+        phpinfo(INFO_GENERAL);
+        $info = ob_get_contents();
+        ob_end_clean();
+        $debug = function_exists('leak') ? '_debug' : '';
+        $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+        $zend_extension_line = 'zend_extension' . $debug . $ts;
+        $all = @file($filename);
+        if (!$all) {
+            return PEAR::raiseError('php.ini "' . $filename .'" could not be read');
+        }
+        $zend_extensions = $extensions = array();
+        // assume this is right, but pull from the php.ini if it is found
+        $extension_dir = ini_get('extension_dir');
+        foreach ($all as $linenum => $line) {
+            $line = trim($line);
+            if (!$line) {
+                continue;
+            }
+            if ($line[0] == ';') {
+                continue;
+            }
+            if (strtolower(substr($line, 0, 13)) == 'extension_dir') {
+                $line = trim(substr($line, 13));
+                if ($line[0] == '=') {
+                    $x = trim(substr($line, 1));
+                    $x = explode(';', $x);
+                    $extension_dir = str_replace('"', '', array_shift($x));
+                    continue;
+                }
+            }
+            if (strtolower(substr($line, 0, 9)) == 'extension') {
+                $line = trim(substr($line, 9));
+                if ($line[0] == '=') {
+                    $x = trim(substr($line, 1));
+                    $x = explode(';', $x);
+                    $extensions[$linenum] = str_replace('"', '', array_shift($x));
+                    continue;
+                }
+            }
+            if (strtolower(substr($line, 0, strlen($zend_extension_line))) ==
+                  $zend_extension_line) {
+                $line = trim(substr($line, strlen($zend_extension_line)));
+                if ($line[0] == '=') {
+                    $x = trim(substr($line, 1));
+                    $x = explode(';', $x);
+                    $zend_extensions[$linenum] = str_replace('"', '', array_shift($x));
+                    continue;
+                }
+            }
+        }
+        return array(
+            'extensions' => $extensions,
+            'zend_extensions' => $zend_extensions,
+            'extension_dir' => $extension_dir,
+            'all' => $all,
+        );
+    }
+
+    // {{{ doInstall()
+
+    function doInstall($command, $options, $params)
+    {
+        if (!class_exists('PEAR_PackageFile')) {
+            require_once 'PEAR/PackageFile.php';
+        }
+
+        if (isset($options['installroot']) && isset($options['packagingroot'])) {
+            return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot');
+        }
+
+        $reg = &$this->config->getRegistry();
+        $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+        if (!$reg->channelExists($channel)) {
+            return $this->raiseError('Channel "' . $channel . '" does not exist');
+        }
+
+        if (empty($this->installer)) {
+            $this->installer = &$this->getInstaller($this->ui);
+        }
+
+        if ($command == 'upgrade' || $command == 'upgrade-all') {
+            // If people run the upgrade command but pass nothing, emulate a upgrade-all
+            if ($command == 'upgrade' && empty($params)) {
+                return $this->doUpgradeAll($command, $options, $params);
+            }
+            $options['upgrade'] = true;
+        } else {
+            $packages = $params;
+        }
+
+        $instreg = &$reg; // instreg used to check if package is installed
+        if (isset($options['packagingroot']) && !isset($options['upgrade'])) {
+            $packrootphp_dir = $this->installer->_prependPath(
+                $this->config->get('php_dir', null, 'pear.php.net'),
+                $options['packagingroot']);
+            $instreg = new PEAR_Registry($packrootphp_dir); // other instreg!
+
+            if ($this->config->get('verbose') > 2) {
+                $this->ui->outputData('using package root: ' . $options['packagingroot']);
+            }
+        }
+
+        $abstractpackages = $otherpackages = array();
+        // parse params
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+
+        foreach ($params as $param) {
+            if (strpos($param, 'http://') === 0) {
+                $otherpackages[] = $param;
+                continue;
+            }
+
+            if (strpos($param, 'channel://') === false && @file_exists($param)) {
+                if (isset($options['force'])) {
+                    $otherpackages[] = $param;
+                    continue;
+                }
+
+                $pkg = new PEAR_PackageFile($this->config);
+                $pf  = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING);
+                if (PEAR::isError($pf)) {
+                    $otherpackages[] = $param;
+                    continue;
+                }
+
+                $exists   = $reg->packageExists($pf->getPackage(), $pf->getChannel());
+                $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel());
+                $version_compare = version_compare($pf->getVersion(), $pversion, '<=');
+                if ($exists && $version_compare) {
+                    if ($this->config->get('verbose')) {
+                        $this->ui->outputData('Ignoring installed package ' .
+                            $reg->parsedPackageNameToString(
+                            array('package' => $pf->getPackage(),
+                                  'channel' => $pf->getChannel()), true));
+                    }
+                    continue;
+                }
+                $otherpackages[] = $param;
+                continue;
+            }
+
+            $e = $reg->parsePackageName($param, $channel);
+            if (PEAR::isError($e)) {
+                $otherpackages[] = $param;
+            } else {
+                $abstractpackages[] = $e;
+            }
+        }
+        PEAR::staticPopErrorHandling();
+
+        // if there are any local package .tgz or remote static url, we can't
+        // filter.  The filter only works for abstract packages
+        if (count($abstractpackages) && !isset($options['force'])) {
+            // when not being forced, only do necessary upgrades/installs
+            if (isset($options['upgrade'])) {
+                $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command);
+            } else {
+                $count = count($abstractpackages);
+                foreach ($abstractpackages as $i => $package) {
+                    if (isset($package['group'])) {
+                        // do not filter out install groups
+                        continue;
+                    }
+
+                    if ($instreg->packageExists($package['package'], $package['channel'])) {
+                        if ($count > 1) {
+                            if ($this->config->get('verbose')) {
+                                $this->ui->outputData('Ignoring installed package ' .
+                                    $reg->parsedPackageNameToString($package, true));
+                            }
+                            unset($abstractpackages[$i]);
+                        } elseif ($count === 1) {
+                            // Lets try to upgrade it since it's already installed
+                            $options['upgrade'] = true;
+                        }
+                    }
+                }
+            }
+            $abstractpackages =
+                array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages);
+        } elseif (count($abstractpackages)) {
+            $abstractpackages =
+                array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages);
+        }
+
+        $packages = array_merge($abstractpackages, $otherpackages);
+        if (!count($packages)) {
+            $c = '';
+            if (isset($options['channel'])){
+                $c .= ' in channel "' . $options['channel'] . '"';
+            }
+            $this->ui->outputData('Nothing to ' . $command . $c);
+            return true;
+        }
+
+        $this->downloader = &$this->getDownloader($this->ui, $options, $this->config);
+        $errors = $downloaded = $binaries = array();
+        $downloaded = &$this->downloader->download($packages);
+        if (PEAR::isError($downloaded)) {
+            return $this->raiseError($downloaded);
+        }
+
+        $errors = $this->downloader->getErrorMsgs();
+        if (count($errors)) {
+            $err = array();
+            $err['data'] = array();
+            foreach ($errors as $error) {
+                if ($error !== null) {
+                    $err['data'][] = array($error);
+                }
+            }
+
+            if (!empty($err['data'])) {
+                $err['headline'] = 'Install Errors';
+                $this->ui->outputData($err);
+            }
+
+            if (!count($downloaded)) {
+                return $this->raiseError("$command failed");
+            }
+        }
+
+        $data = array(
+            'headline' => 'Packages that would be Installed'
+        );
+
+        if (isset($options['pretend'])) {
+            foreach ($downloaded as $package) {
+                $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage()));
+            }
+            $this->ui->outputData($data, 'pretend');
+            return true;
+        }
+
+        $this->installer->setOptions($options);
+        $this->installer->sortPackagesForInstall($downloaded);
+        if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) {
+            $this->raiseError($err->getMessage());
+            return true;
+        }
+
+        $binaries = $extrainfo = array();
+        foreach ($downloaded as $param) {
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $info = $this->installer->install($param, $options);
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($info)) {
+                $oldinfo = $info;
+                $pkg = &$param->getPackageFile();
+                if ($info->getCode() != PEAR_INSTALLER_NOBINARY) {
+                    if (!($info = $pkg->installBinary($this->installer))) {
+                        $this->ui->outputData('ERROR: ' .$oldinfo->getMessage());
+                        continue;
+                    }
+
+                    // we just installed a different package than requested,
+                    // let's change the param and info so that the rest of this works
+                    $param = $info[0];
+                    $info  = $info[1];
+                }
+            }
+
+            if (!is_array($info)) {
+                return $this->raiseError("$command failed");
+            }
+
+            if ($param->getPackageType() == 'extsrc' ||
+                  $param->getPackageType() == 'extbin' ||
+                  $param->getPackageType() == 'zendextsrc' ||
+                  $param->getPackageType() == 'zendextbin'
+            ) {
+                $pkg = &$param->getPackageFile();
+                if ($instbin = $pkg->getInstalledBinary()) {
+                    $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel());
+                } else {
+                    $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel());
+                }
+
+                foreach ($instpkg->getFilelist() as $name => $atts) {
+                    $pinfo = pathinfo($atts['installed_as']);
+                    if (!isset($pinfo['extension']) ||
+                          in_array($pinfo['extension'], array('c', 'h'))
+                    ) {
+                        continue; // make sure we don't match php_blah.h
+                    }
+
+                    if ((strpos($pinfo['basename'], 'php_') === 0 &&
+                          $pinfo['extension'] == 'dll') ||
+                          // most unices
+                          $pinfo['extension'] == 'so' ||
+                          // hp-ux
+                          $pinfo['extension'] == 'sl') {
+                        $binaries[] = array($atts['installed_as'], $pinfo);
+                        break;
+                    }
+                }
+
+                if (count($binaries)) {
+                    foreach ($binaries as $pinfo) {
+                        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                        $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType());
+                        PEAR::staticPopErrorHandling();
+                        if (PEAR::isError($ret)) {
+                            $extrainfo[] = $ret->getMessage();
+                            if ($param->getPackageType() == 'extsrc' ||
+                                  $param->getPackageType() == 'extbin') {
+                                $exttype = 'extension';
+                            } else {
+                                ob_start();
+                                phpinfo(INFO_GENERAL);
+                                $info = ob_get_contents();
+                                ob_end_clean();
+                                $debug = function_exists('leak') ? '_debug' : '';
+                                $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+                                $exttype = 'zend_extension' . $debug . $ts;
+                            }
+                            $extrainfo[] = 'You should add "' . $exttype . '=' .
+                                $pinfo[1]['basename'] . '" to php.ini';
+                        } else {
+                            $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() .
+                                ' enabled in php.ini';
+                        }
+                    }
+                }
+            }
+
+            if ($this->config->get('verbose') > 0) {
+                $chan = $param->getChannel();
+                $label = $reg->parsedPackageNameToString(
+                    array(
+                        'channel' => $chan,
+                        'package' => $param->getPackage(),
+                        'version' => $param->getVersion(),
+                    ));
+                $out = array('data' => "$command ok: $label");
+                if (isset($info['release_warnings'])) {
+                    $out['release_warnings'] = $info['release_warnings'];
+                }
+                $this->ui->outputData($out, $command);
+
+                if (!isset($options['register-only']) && !isset($options['offline'])) {
+                    if ($this->config->isDefinedLayer('ftp')) {
+                        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                        $info = $this->installer->ftpInstall($param);
+                        PEAR::staticPopErrorHandling();
+                        if (PEAR::isError($info)) {
+                            $this->ui->outputData($info->getMessage());
+                            $this->ui->outputData("remote install failed: $label");
+                        } else {
+                            $this->ui->outputData("remote install ok: $label");
+                        }
+                    }
+                }
+            }
+
+            $deps = $param->getDeps();
+            if ($deps) {
+                if (isset($deps['group'])) {
+                    $groups = $deps['group'];
+                    if (!isset($groups[0])) {
+                        $groups = array($groups);
+                    }
+
+                    foreach ($groups as $group) {
+                        if ($group['attribs']['name'] == 'default') {
+                            // default group is always installed, unless the user
+                            // explicitly chooses to install another group
+                            continue;
+                        }
+                        $extrainfo[] = $param->getPackage() . ': Optional feature ' .
+                            $group['attribs']['name'] . ' available (' .
+                            $group['attribs']['hint'] . ')';
+                    }
+
+                    $extrainfo[] = $param->getPackage() .
+                        ': To install optional features use "pear install ' .
+                        $reg->parsedPackageNameToString(
+                            array('package' => $param->getPackage(),
+                                  'channel' => $param->getChannel()), true) .
+                              '#featurename"';
+                }
+            }
+
+            $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel());
+            // $pkg may be NULL if install is a 'fake' install via --packagingroot
+            if (is_object($pkg)) {
+                $pkg->setConfig($this->config);
+                if ($list = $pkg->listPostinstallScripts()) {
+                    $pn = $reg->parsedPackageNameToString(array('channel' =>
+                       $param->getChannel(), 'package' => $param->getPackage()), true);
+                    $extrainfo[] = $pn . ' has post-install scripts:';
+                    foreach ($list as $file) {
+                        $extrainfo[] = $file;
+                    }
+                    $extrainfo[] = $param->getPackage() .
+                        ': Use "pear run-scripts ' . $pn . '" to finish setup.';
+                    $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES';
+                }
+            }
+        }
+
+        if (count($extrainfo)) {
+            foreach ($extrainfo as $info) {
+                $this->ui->outputData($info);
+            }
+        }
+
+        return true;
+    }
+
+    // }}}
+    // {{{ doUpgradeAll()
+
+    function doUpgradeAll($command, $options, $params)
+    {
+        $reg = &$this->config->getRegistry();
+        $upgrade = array();
+
+        if (isset($options['channel'])) {
+            $channels = array($options['channel']);
+        } else {
+            $channels = $reg->listChannels();
+        }
+
+        foreach ($channels as $channel) {
+            if ($channel == '__uri') {
+                continue;
+            }
+
+            // parse name with channel
+            foreach ($reg->listPackages($channel) as $name) {
+                $upgrade[] = $reg->parsedPackageNameToString(array(
+                        'channel' => $channel,
+                        'package' => $name
+                    ));
+            }
+        }
+
+        $err = $this->doInstall($command, $options, $upgrade);
+        if (PEAR::isError($err)) {
+            $this->ui->outputData($err->getMessage(), $command);
+        }
+   }
+
+    // }}}
+    // {{{ doUninstall()
+
+    function doUninstall($command, $options, $params)
+    {
+        if (count($params) < 1) {
+            return $this->raiseError("Please supply the package(s) you want to uninstall");
+        }
+
+        if (empty($this->installer)) {
+            $this->installer = &$this->getInstaller($this->ui);
+        }
+
+        if (isset($options['remoteconfig'])) {
+            $e = $this->config->readFTPConfigFile($options['remoteconfig']);
+            if (!PEAR::isError($e)) {
+                $this->installer->setConfig($this->config);
+            }
+        }
+
+        $reg = &$this->config->getRegistry();
+        $newparams = array();
+        $binaries = array();
+        $badparams = array();
+        foreach ($params as $pkg) {
+            $channel = $this->config->get('default_channel');
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $parsed = $reg->parsePackageName($pkg, $channel);
+            PEAR::staticPopErrorHandling();
+            if (!$parsed || PEAR::isError($parsed)) {
+                $badparams[] = $pkg;
+                continue;
+            }
+            $package = $parsed['package'];
+            $channel = $parsed['channel'];
+            $info = &$reg->getPackage($package, $channel);
+            if ($info === null &&
+                 ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) {
+                // make sure this isn't a package that has flipped from pear to pecl but
+                // used a package.xml 1.0
+                $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net';
+                $info = &$reg->getPackage($package, $testc);
+                if ($info !== null) {
+                    $channel = $testc;
+                }
+            }
+            if ($info === null) {
+                $badparams[] = $pkg;
+            } else {
+                $newparams[] = &$info;
+                // check for binary packages (this is an alias for those packages if so)
+                if ($installedbinary = $info->getInstalledBinary()) {
+                    $this->ui->log('adding binary package ' .
+                        $reg->parsedPackageNameToString(array('channel' => $channel,
+                            'package' => $installedbinary), true));
+                    $newparams[] = &$reg->getPackage($installedbinary, $channel);
+                }
+                // add the contents of a dependency group to the list of installed packages
+                if (isset($parsed['group'])) {
+                    $group = $info->getDependencyGroup($parsed['group']);
+                    if ($group) {
+                        $installed = $reg->getInstalledGroup($group);
+                        if ($installed) {
+                            foreach ($installed as $i => $p) {
+                                $newparams[] = &$installed[$i];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        $err = $this->installer->sortPackagesForUninstall($newparams);
+        if (PEAR::isError($err)) {
+            $this->ui->outputData($err->getMessage(), $command);
+            return true;
+        }
+        $params = $newparams;
+        // twist this to use it to check on whether dependent packages are also being uninstalled
+        // for circular dependencies like subpackages
+        $this->installer->setUninstallPackages($newparams);
+        $params = array_merge($params, $badparams);
+        $binaries = array();
+        foreach ($params as $pkg) {
+            $this->installer->pushErrorHandling(PEAR_ERROR_RETURN);
+            if ($err = $this->installer->uninstall($pkg, $options)) {
+                $this->installer->popErrorHandling();
+                if (PEAR::isError($err)) {
+                    $this->ui->outputData($err->getMessage(), $command);
+                    continue;
+                }
+                if ($pkg->getPackageType() == 'extsrc' ||
+                      $pkg->getPackageType() == 'extbin' ||
+                      $pkg->getPackageType() == 'zendextsrc' ||
+                      $pkg->getPackageType() == 'zendextbin') {
+                    if ($instbin = $pkg->getInstalledBinary()) {
+                        continue; // this will be uninstalled later
+                    }
+
+                    foreach ($pkg->getFilelist() as $name => $atts) {
+                        $pinfo = pathinfo($atts['installed_as']);
+                        if (!isset($pinfo['extension']) ||
+                              in_array($pinfo['extension'], array('c', 'h'))) {
+                            continue; // make sure we don't match php_blah.h
+                        }
+                        if ((strpos($pinfo['basename'], 'php_') === 0 &&
+                              $pinfo['extension'] == 'dll') ||
+                              // most unices
+                              $pinfo['extension'] == 'so' ||
+                              // hp-ux
+                              $pinfo['extension'] == 'sl') {
+                            $binaries[] = array($atts['installed_as'], $pinfo);
+                            break;
+                        }
+                    }
+                    if (count($binaries)) {
+                        foreach ($binaries as $pinfo) {
+                            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                            $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType());
+                            PEAR::staticPopErrorHandling();
+                            if (PEAR::isError($ret)) {
+                                $extrainfo[] = $ret->getMessage();
+                                if ($pkg->getPackageType() == 'extsrc' ||
+                                      $pkg->getPackageType() == 'extbin') {
+                                    $exttype = 'extension';
+                                } else {
+                                    ob_start();
+                                    phpinfo(INFO_GENERAL);
+                                    $info = ob_get_contents();
+                                    ob_end_clean();
+                                    $debug = function_exists('leak') ? '_debug' : '';
+                                    $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+                                    $exttype = 'zend_extension' . $debug . $ts;
+                                }
+                                $this->ui->outputData('Unable to remove "' . $exttype . '=' .
+                                    $pinfo[1]['basename'] . '" from php.ini', $command);
+                            } else {
+                                $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() .
+                                    ' disabled in php.ini', $command);
+                            }
+                        }
+                    }
+                }
+                $savepkg = $pkg;
+                if ($this->config->get('verbose') > 0) {
+                    if (is_object($pkg)) {
+                        $pkg = $reg->parsedPackageNameToString($pkg);
+                    }
+                    $this->ui->outputData("uninstall ok: $pkg", $command);
+                }
+                if (!isset($options['offline']) && is_object($savepkg) &&
+                      defined('PEAR_REMOTEINSTALL_OK')) {
+                    if ($this->config->isDefinedLayer('ftp')) {
+                        $this->installer->pushErrorHandling(PEAR_ERROR_RETURN);
+                        $info = $this->installer->ftpUninstall($savepkg);
+                        $this->installer->popErrorHandling();
+                        if (PEAR::isError($info)) {
+                            $this->ui->outputData($info->getMessage());
+                            $this->ui->outputData("remote uninstall failed: $pkg");
+                        } else {
+                            $this->ui->outputData("remote uninstall ok: $pkg");
+                        }
+                    }
+                }
+            } else {
+                $this->installer->popErrorHandling();
+                if (!is_object($pkg)) {
+                    return $this->raiseError("uninstall failed: $pkg");
+                }
+                $pkg = $reg->parsedPackageNameToString($pkg);
+            }
+        }
+
+        return true;
+    }
+
+    // }}}
+
+
+    // }}}
+    // {{{ doBundle()
+    /*
+    (cox) It just downloads and untars the package, does not do
+            any check that the PEAR_Installer::_installFile() does.
+    */
+
+    function doBundle($command, $options, $params)
+    {
+        $opts = array(
+            'force'        => true,
+            'nodeps'       => true,
+            'soft'         => true,
+            'downloadonly' => true
+        );
+        $downloader = &$this->getDownloader($this->ui, $opts, $this->config);
+        $reg = &$this->config->getRegistry();
+        if (count($params) < 1) {
+            return $this->raiseError("Please supply the package you want to bundle");
+        }
+
+        if (isset($options['destination'])) {
+            if (!is_dir($options['destination'])) {
+                System::mkdir('-p ' . $options['destination']);
+            }
+            $dest = realpath($options['destination']);
+        } else {
+            $pwd  = getcwd();
+            $dir  = $pwd . DIRECTORY_SEPARATOR . 'ext';
+            $dest = is_dir($dir) ? $dir : $pwd;
+        }
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $downloader->setDownloadDir($dest);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($err)) {
+            return PEAR::raiseError('download directory "' . $dest .
+                '" is not writeable.');
+        }
+        $result = &$downloader->download(array($params[0]));
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+        if (!isset($result[0])) {
+            return $this->raiseError('unable to unpack ' . $params[0]);
+        }
+        $pkgfile = &$result[0]->getPackageFile();
+        $pkgname = $pkgfile->getName();
+        $pkgversion = $pkgfile->getVersion();
+
+        // Unpacking -------------------------------------------------
+        $dest .= DIRECTORY_SEPARATOR . $pkgname;
+        $orig = $pkgname . '-' . $pkgversion;
+
+        $tar = &new Archive_Tar($pkgfile->getArchiveFile());
+        if (!$tar->extractModify($dest, $orig)) {
+            return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile());
+        }
+        $this->ui->outputData("Package ready at '$dest'");
+    // }}}
+    }
+
+    // }}}
+
+    function doRunScripts($command, $options, $params)
+    {
+        if (!isset($params[0])) {
+            return $this->raiseError('run-scripts expects 1 parameter: a package name');
+        }
+
+        $reg = &$this->config->getRegistry();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($parsed)) {
+            return $this->raiseError($parsed);
+        }
+
+        $package = &$reg->getPackage($parsed['package'], $parsed['channel']);
+        if (!is_object($package)) {
+            return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry');
+        }
+
+        $package->setConfig($this->config);
+        $package->runPostinstallScripts();
+        $this->ui->outputData('Install scripts complete', $command);
+        return true;
+    }
+
+    /**
+     * Given a list of packages, filter out those ones that are already up to date
+     *
+     * @param $packages: packages, in parsed array format !
+     * @return list of packages that can be upgraded
+     */
+    function _filterUptodatePackages($packages, $command)
+    {
+        $reg = &$this->config->getRegistry();
+        $latestReleases = array();
+
+        $ret = array();
+        foreach ($packages as $package) {
+            if (isset($package['group'])) {
+                $ret[] = $package;
+                continue;
+            }
+
+            $channel = $package['channel'];
+            $name    = $package['package'];
+            if (!$reg->packageExists($name, $channel)) {
+                $ret[] = $package;
+                continue;
+            }
+
+            if (!isset($latestReleases[$channel])) {
+                // fill in cache for this channel
+                $chan = &$reg->getChannel($channel);
+                if (PEAR::isError($chan)) {
+                    return $this->raiseError($chan);
+                }
+
+                $base2 = false;
+                $preferred_mirror = $this->config->get('preferred_mirror', null, $channel);
+                if ($chan->supportsREST($preferred_mirror) &&
+                    (
+                       //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
+                       ($base  = $chan->getBaseURL('REST1.0', $preferred_mirror))
+                    )
+                ) {
+                    $dorest = true;
+                }
+
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                if (!isset($package['state'])) {
+                    $state = $this->config->get('preferred_state', null, $channel);
+                } else {
+                    $state = $package['state'];
+                }
+
+                if ($dorest) {
+                    if ($base2) {
+                        $rest = &$this->config->getREST('1.4', array());
+                        $base = $base2;
+                    } else {
+                        $rest = &$this->config->getREST('1.0', array());
+                    }
+
+                    $installed = array_flip($reg->listPackages($channel));
+                    $latest    = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg);
+                }
+
+                PEAR::staticPopErrorHandling();
+                if (PEAR::isError($latest)) {
+                    $this->ui->outputData('Error getting channel info from ' . $channel .
+                        ': ' . $latest->getMessage());
+                    continue;
+                }
+
+                $latestReleases[$channel] = array_change_key_case($latest);
+            }
+
+            // check package for latest release
+            $name_lower = strtolower($name);
+            if (isset($latestReleases[$channel][$name_lower])) {
+                // if not set, up to date
+                $inst_version    = $reg->packageInfo($name, 'version', $channel);
+                $channel_version = $latestReleases[$channel][$name_lower]['version'];
+                if (version_compare($channel_version, $inst_version, 'le')) {
+                    // installed version is up-to-date
+                    continue;
+                }
+
+                // maintain BC
+                if ($command == 'upgrade-all') {
+                    $this->ui->outputData(array('data' => 'Will upgrade ' .
+                        $reg->parsedPackageNameToString($package)), $command);
+                }
+                $ret[] = $package;
+            }
+        }
+
+        return $ret;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Install.xml b/WEB-INF/lib/pear/PEAR/Command/Install.xml
new file mode 100644 (file)
index 0000000..1b1e933
--- /dev/null
@@ -0,0 +1,276 @@
+<commands version="1.0">
+ <install>
+  <summary>Install Package</summary>
+  <function>doInstall</function>
+  <shortcut>i</shortcut>
+  <options>
+   <force>
+    <shortopt>f</shortopt>
+    <doc>will overwrite newer installed packages</doc>
+   </force>
+   <loose>
+    <shortopt>l</shortopt>
+    <doc>do not check for recommended dependency version</doc>
+   </loose>
+   <nodeps>
+    <shortopt>n</shortopt>
+    <doc>ignore dependencies, install anyway</doc>
+   </nodeps>
+   <register-only>
+    <shortopt>r</shortopt>
+    <doc>do not install files, only register the package as installed</doc>
+   </register-only>
+   <soft>
+    <shortopt>s</shortopt>
+    <doc>soft install, fail silently, or upgrade if already installed</doc>
+   </soft>
+   <nobuild>
+    <shortopt>B</shortopt>
+    <doc>don&#039;t build C extensions</doc>
+   </nobuild>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>request uncompressed files when downloading</doc>
+   </nocompress>
+   <installroot>
+    <shortopt>R</shortopt>
+    <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT), use packagingroot for RPM</doc>
+    <arg>DIR</arg>
+   </installroot>
+   <packagingroot>
+    <shortopt>P</shortopt>
+    <doc>root directory used when packaging files, like RPM packaging</doc>
+    <arg>DIR</arg>
+   </packagingroot>
+   <ignore-errors>
+    <shortopt></shortopt>
+    <doc>force install even if there were errors</doc>
+   </ignore-errors>
+   <alldeps>
+    <shortopt>a</shortopt>
+    <doc>install all required and optional dependencies</doc>
+   </alldeps>
+   <onlyreqdeps>
+    <shortopt>o</shortopt>
+    <doc>install all required dependencies</doc>
+   </onlyreqdeps>
+   <offline>
+    <shortopt>O</shortopt>
+    <doc>do not attempt to download any urls or contact channels</doc>
+   </offline>
+   <pretend>
+    <shortopt>p</shortopt>
+    <doc>Only list the packages that would be downloaded</doc>
+   </pretend>
+  </options>
+  <doc>[channel/]&lt;package&gt; ...
+Installs one or more PEAR packages.  You can specify a package to
+install in four ways:
+
+&quot;Package-1.0.tgz&quot; : installs from a local file
+
+&quot;http://example.com/Package-1.0.tgz&quot; : installs from
+anywhere on the net.
+
+&quot;package.xml&quot; : installs the package described in
+package.xml.  Useful for testing, or for wrapping a PEAR package in
+another package manager such as RPM.
+
+&quot;Package[-version/state][.tar]&quot; : queries your default channel&#039;s server
+({config master_server}) and downloads the newest package with
+the preferred quality/state ({config preferred_state}).
+
+To retrieve Package version 1.1, use &quot;Package-1.1,&quot; to retrieve
+Package state beta, use &quot;Package-beta.&quot;  To retrieve an uncompressed
+file, append .tar (make sure there is no file by the same name first)
+
+To download a package from another channel, prefix with the channel name like
+&quot;channel/Package&quot;
+
+More than one package may be specified at once.  It is ok to mix these
+four ways of specifying packages.
+</doc>
+ </install>
+ <upgrade>
+  <summary>Upgrade Package</summary>
+  <function>doInstall</function>
+  <shortcut>up</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>upgrade packages from a specific channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+   <force>
+    <shortopt>f</shortopt>
+    <doc>overwrite newer installed packages</doc>
+   </force>
+   <loose>
+    <shortopt>l</shortopt>
+    <doc>do not check for recommended dependency version</doc>
+   </loose>
+   <nodeps>
+    <shortopt>n</shortopt>
+    <doc>ignore dependencies, upgrade anyway</doc>
+   </nodeps>
+   <register-only>
+    <shortopt>r</shortopt>
+    <doc>do not install files, only register the package as upgraded</doc>
+   </register-only>
+   <nobuild>
+    <shortopt>B</shortopt>
+    <doc>don&#039;t build C extensions</doc>
+   </nobuild>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>request uncompressed files when downloading</doc>
+   </nocompress>
+   <installroot>
+    <shortopt>R</shortopt>
+    <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT)</doc>
+    <arg>DIR</arg>
+   </installroot>
+   <ignore-errors>
+    <shortopt></shortopt>
+    <doc>force install even if there were errors</doc>
+   </ignore-errors>
+   <alldeps>
+    <shortopt>a</shortopt>
+    <doc>install all required and optional dependencies</doc>
+   </alldeps>
+   <onlyreqdeps>
+    <shortopt>o</shortopt>
+    <doc>install all required dependencies</doc>
+   </onlyreqdeps>
+   <offline>
+    <shortopt>O</shortopt>
+    <doc>do not attempt to download any urls or contact channels</doc>
+   </offline>
+   <pretend>
+    <shortopt>p</shortopt>
+    <doc>Only list the packages that would be downloaded</doc>
+   </pretend>
+  </options>
+  <doc>&lt;package&gt; ...
+Upgrades one or more PEAR packages.  See documentation for the
+&quot;install&quot; command for ways to specify a package.
+
+When upgrading, your package will be updated if the provided new
+package has a higher version number (use the -f option if you need to
+upgrade anyway).
+
+More than one package may be specified at once.
+</doc>
+ </upgrade>
+ <upgrade-all>
+  <summary>Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]</summary>
+  <function>doUpgradeAll</function>
+  <shortcut>ua</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>upgrade packages from a specific channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+   <nodeps>
+    <shortopt>n</shortopt>
+    <doc>ignore dependencies, upgrade anyway</doc>
+   </nodeps>
+   <register-only>
+    <shortopt>r</shortopt>
+    <doc>do not install files, only register the package as upgraded</doc>
+   </register-only>
+   <nobuild>
+    <shortopt>B</shortopt>
+    <doc>don&#039;t build C extensions</doc>
+   </nobuild>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>request uncompressed files when downloading</doc>
+   </nocompress>
+   <installroot>
+    <shortopt>R</shortopt>
+    <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT), use packagingroot for RPM</doc>
+    <arg>DIR</arg>
+   </installroot>
+   <ignore-errors>
+    <shortopt></shortopt>
+    <doc>force install even if there were errors</doc>
+   </ignore-errors>
+   <loose>
+    <shortopt></shortopt>
+    <doc>do not check for recommended dependency version</doc>
+   </loose>
+  </options>
+  <doc>
+WARNING: This function is deprecated in favor of using the upgrade command with no params
+
+Upgrades all packages that have a newer release available.  Upgrades are
+done only if there is a release available of the state specified in
+&quot;preferred_state&quot; (currently {config preferred_state}), or a state considered
+more stable.
+</doc>
+ </upgrade-all>
+ <uninstall>
+  <summary>Un-install Package</summary>
+  <function>doUninstall</function>
+  <shortcut>un</shortcut>
+  <options>
+   <nodeps>
+    <shortopt>n</shortopt>
+    <doc>ignore dependencies, uninstall anyway</doc>
+   </nodeps>
+   <register-only>
+    <shortopt>r</shortopt>
+    <doc>do not remove files, only register the packages as not installed</doc>
+   </register-only>
+   <installroot>
+    <shortopt>R</shortopt>
+    <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT)</doc>
+    <arg>DIR</arg>
+   </installroot>
+   <ignore-errors>
+    <shortopt></shortopt>
+    <doc>force install even if there were errors</doc>
+   </ignore-errors>
+   <offline>
+    <shortopt>O</shortopt>
+    <doc>do not attempt to uninstall remotely</doc>
+   </offline>
+  </options>
+  <doc>[channel/]&lt;package&gt; ...
+Uninstalls one or more PEAR packages.  More than one package may be
+specified at once.  Prefix with channel name to uninstall from a
+channel not in your default channel ({config default_channel})
+</doc>
+ </uninstall>
+ <bundle>
+  <summary>Unpacks a Pecl Package</summary>
+  <function>doBundle</function>
+  <shortcut>bun</shortcut>
+  <options>
+   <destination>
+    <shortopt>d</shortopt>
+    <doc>Optional destination directory for unpacking (defaults to current path or &quot;ext&quot; if exists)</doc>
+    <arg>DIR</arg>
+   </destination>
+   <force>
+    <shortopt>f</shortopt>
+    <doc>Force the unpacking even if there were errors in the package</doc>
+   </force>
+  </options>
+  <doc>&lt;package&gt;
+Unpacks a Pecl Package into the selected location. It will download the
+package if needed.
+</doc>
+ </bundle>
+ <run-scripts>
+  <summary>Run Post-Install Scripts bundled with a package</summary>
+  <function>doRunScripts</function>
+  <shortcut>rs</shortcut>
+  <options />
+  <doc>&lt;package&gt;
+Run post-installation scripts in package &lt;package&gt;, if any exist.
+</doc>
+ </run-scripts>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Mirror.php b/WEB-INF/lib/pear/PEAR/Command/Mirror.php
new file mode 100644 (file)
index 0000000..4d157c6
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * PEAR_Command_Mirror (download-all command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Alexander Merz <alexmerz@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Mirror.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.2.0
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for providing file mirrors
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Alexander Merz <alexmerz@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.2.0
+ */
+class PEAR_Command_Mirror extends PEAR_Command_Common
+{
+    var $commands = array(
+        'download-all' => array(
+            'summary' => 'Downloads each available package from the default channel',
+            'function' => 'doDownloadAll',
+            'shortcut' => 'da',
+            'options' => array(
+                'channel' =>
+                    array(
+                    'shortopt' => 'c',
+                    'doc' => 'specify a channel other than the default channel',
+                    'arg' => 'CHAN',
+                    ),
+                ),
+            'doc' => '
+Requests a list of available packages from the default channel ({config default_channel})
+and downloads them to current working directory.  Note: only
+packages within preferred_state ({config preferred_state}) will be downloaded'
+            ),
+        );
+
+    /**
+     * PEAR_Command_Mirror constructor.
+     *
+     * @access public
+     * @param object PEAR_Frontend a reference to an frontend
+     * @param object PEAR_Config a reference to the configuration data
+     */
+    function PEAR_Command_Mirror(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    /**
+     * For unit-testing
+     */
+    function &factory($a)
+    {
+        $a = &PEAR_Command::factory($a, $this->config);
+        return $a;
+    }
+
+    /**
+    * retrieves a list of avaible Packages from master server
+    * and downloads them
+    *
+    * @access public
+    * @param string $command the command
+    * @param array $options the command options before the command
+    * @param array $params the stuff after the command name
+    * @return bool true if succesful
+    * @throw PEAR_Error
+    */
+    function doDownloadAll($command, $options, $params)
+    {
+        $savechannel = $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        $channel = isset($options['channel']) ? $options['channel'] :
+            $this->config->get('default_channel');
+        if (!$reg->channelExists($channel)) {
+            $this->config->set('default_channel', $savechannel);
+            return $this->raiseError('Channel "' . $channel . '" does not exist');
+        }
+        $this->config->set('default_channel', $channel);
+
+        $this->ui->outputData('Using Channel ' . $this->config->get('default_channel'));
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($chan)) {
+            return $this->raiseError($chan);
+        }
+
+        if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+            $rest = &$this->config->getREST('1.0', array());
+            $remoteInfo = array_flip($rest->listPackages($base, $channel));
+        }
+
+        if (PEAR::isError($remoteInfo)) {
+            return $remoteInfo;
+        }
+
+        $cmd = &$this->factory("download");
+        if (PEAR::isError($cmd)) {
+            return $cmd;
+        }
+
+        $this->ui->outputData('Using Preferred State of ' .
+            $this->config->get('preferred_state'));
+        $this->ui->outputData('Gathering release information, please wait...');
+
+        /**
+         * Error handling not necessary, because already done by
+         * the download command
+         */
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo));
+        PEAR::staticPopErrorHandling();
+        $this->config->set('default_channel', $savechannel);
+        if (PEAR::isError($err)) {
+            $this->ui->outputData($err->getMessage());
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Mirror.xml b/WEB-INF/lib/pear/PEAR/Command/Mirror.xml
new file mode 100644 (file)
index 0000000..fe8be9d
--- /dev/null
@@ -0,0 +1,18 @@
+<commands version="1.0">
+ <download-all>
+  <summary>Downloads each available package from the default channel</summary>
+  <function>doDownloadAll</function>
+  <shortcut>da</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>specify a channel other than the default channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+  </options>
+  <doc>
+Requests a list of available packages from the default channel ({config default_channel})
+and downloads them to current working directory.  Note: only
+packages within preferred_state ({config preferred_state}) will be downloaded</doc>
+ </download-all>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Package.php b/WEB-INF/lib/pear/PEAR/Command/Package.php
new file mode 100644 (file)
index 0000000..81df7bf
--- /dev/null
@@ -0,0 +1,1124 @@
+<?php
+/**
+ * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies,
+ * sign, makerpm, convert commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Package.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+
+class PEAR_Command_Package extends PEAR_Command_Common
+{
+    var $commands = array(
+        'package' => array(
+            'summary' => 'Build Package',
+            'function' => 'doPackage',
+            'shortcut' => 'p',
+            'options' => array(
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'Do not gzip the package file'
+                    ),
+                'showname' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'Print the name of the packaged file.',
+                    ),
+                ),
+            'doc' => '[descfile] [descfile2]
+Creates a PEAR package from its description file (usually called
+package.xml).  If a second packagefile is passed in, then
+the packager will check to make sure that one is a package.xml
+version 1.0, and the other is a package.xml version 2.0.  The
+package.xml version 1.0 will be saved as "package.xml" in the archive,
+and the other as "package2.xml" in the archive"
+'
+            ),
+        'package-validate' => array(
+            'summary' => 'Validate Package Consistency',
+            'function' => 'doPackageValidate',
+            'shortcut' => 'pv',
+            'options' => array(),
+            'doc' => '
+',
+            ),
+        'cvsdiff' => array(
+            'summary' => 'Run a "cvs diff" for all files in a package',
+            'function' => 'doCvsDiff',
+            'shortcut' => 'cd',
+            'options' => array(
+                'quiet' => array(
+                    'shortopt' => 'q',
+                    'doc' => 'Be quiet',
+                    ),
+                'reallyquiet' => array(
+                    'shortopt' => 'Q',
+                    'doc' => 'Be really quiet',
+                    ),
+                'date' => array(
+                    'shortopt' => 'D',
+                    'doc' => 'Diff against revision of DATE',
+                    'arg' => 'DATE',
+                    ),
+                'release' => array(
+                    'shortopt' => 'R',
+                    'doc' => 'Diff against tag for package release REL',
+                    'arg' => 'REL',
+                    ),
+                'revision' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'Diff against revision REV',
+                    'arg' => 'REV',
+                    ),
+                'context' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'Generate context diff',
+                    ),
+                'unified' => array(
+                    'shortopt' => 'u',
+                    'doc' => 'Generate unified diff',
+                    ),
+                'ignore-case' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'Ignore case, consider upper- and lower-case letters equivalent',
+                    ),
+                'ignore-whitespace' => array(
+                    'shortopt' => 'b',
+                    'doc' => 'Ignore changes in amount of white space',
+                    ),
+                'ignore-blank-lines' => array(
+                    'shortopt' => 'B',
+                    'doc' => 'Ignore changes that insert or delete blank lines',
+                    ),
+                'brief' => array(
+                    'doc' => 'Report only whether the files differ, no details',
+                    ),
+                'dry-run' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'Don\'t do anything, just pretend',
+                    ),
+                ),
+            'doc' => '<package.xml>
+Compares all the files in a package.  Without any options, this
+command will compare the current code with the last checked-in code.
+Using the -r or -R option you may compare the current code with that
+of a specific release.
+',
+            ),
+         'svntag' => array(
+             'summary' => 'Set SVN Release Tag',
+             'function' => 'doSvnTag',
+             'shortcut' => 'sv',
+             'options' => array(
+                 'quiet' => array(
+                     'shortopt' => 'q',
+                     'doc' => 'Be quiet',
+                     ),
+                 'slide' => array(
+                     'shortopt' => 'F',
+                     'doc' => 'Move (slide) tag if it exists',
+                     ),
+                 'delete' => array(
+                     'shortopt' => 'd',
+                     'doc' => 'Remove tag',
+                     ),
+                 'dry-run' => array(
+                     'shortopt' => 'n',
+                     'doc' => 'Don\'t do anything, just pretend',
+                     ),
+                 ),
+             'doc' => '<package.xml> [files...]
+ Sets a SVN tag on all files in a package.  Use this command after you have
+ packaged a distribution tarball with the "package" command to tag what
+ revisions of what files were in that release.  If need to fix something
+ after running svntag once, but before the tarball is released to the public,
+ use the "slide" option to move the release tag.
+
+ to include files (such as a second package.xml, or tests not included in the
+ release), pass them as additional parameters.
+ ',
+             ),
+        'cvstag' => array(
+            'summary' => 'Set CVS Release Tag',
+            'function' => 'doCvsTag',
+            'shortcut' => 'ct',
+            'options' => array(
+                'quiet' => array(
+                    'shortopt' => 'q',
+                    'doc' => 'Be quiet',
+                    ),
+                'reallyquiet' => array(
+                    'shortopt' => 'Q',
+                    'doc' => 'Be really quiet',
+                    ),
+                'slide' => array(
+                    'shortopt' => 'F',
+                    'doc' => 'Move (slide) tag if it exists',
+                    ),
+                'delete' => array(
+                    'shortopt' => 'd',
+                    'doc' => 'Remove tag',
+                    ),
+                'dry-run' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'Don\'t do anything, just pretend',
+                    ),
+                ),
+            'doc' => '<package.xml> [files...]
+Sets a CVS tag on all files in a package.  Use this command after you have
+packaged a distribution tarball with the "package" command to tag what
+revisions of what files were in that release.  If need to fix something
+after running cvstag once, but before the tarball is released to the public,
+use the "slide" option to move the release tag.
+
+to include files (such as a second package.xml, or tests not included in the
+release), pass them as additional parameters.
+',
+            ),
+        'package-dependencies' => array(
+            'summary' => 'Show package dependencies',
+            'function' => 'doPackageDependencies',
+            'shortcut' => 'pd',
+            'options' => array(),
+            'doc' => '<package-file> or <package.xml> or <install-package-name>
+List all dependencies the package has.
+Can take a tgz / tar file, package.xml or a package name of an installed package.'
+            ),
+        'sign' => array(
+            'summary' => 'Sign a package distribution file',
+            'function' => 'doSign',
+            'shortcut' => 'si',
+            'options' => array(
+                'verbose' => array(
+                    'shortopt' => 'v',
+                    'doc' => 'Display GnuPG output',
+                    ),
+            ),
+            'doc' => '<package-file>
+Signs a package distribution (.tar or .tgz) file with GnuPG.',
+            ),
+        'makerpm' => array(
+            'summary' => 'Builds an RPM spec file from a PEAR package',
+            'function' => 'doMakeRPM',
+            'shortcut' => 'rpm',
+            'options' => array(
+                'spec-template' => array(
+                    'shortopt' => 't',
+                    'arg' => 'FILE',
+                    'doc' => 'Use FILE as RPM spec file template'
+                    ),
+                'rpm-pkgname' => array(
+                    'shortopt' => 'p',
+                    'arg' => 'FORMAT',
+                    'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced
+by the PEAR package name, defaults to "PEAR::%s".',
+                    ),
+                ),
+            'doc' => '<package-file>
+
+Creates an RPM .spec file for wrapping a PEAR package inside an RPM
+package.  Intended to be used from the SPECS directory, with the PEAR
+package tarball in the SOURCES directory:
+
+$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
+Wrote RPM spec file PEAR::Net_Geo-1.0.spec
+$ rpm -bb PEAR::Net_Socket-1.0.spec
+...
+Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
+',
+            ),
+        'convert' => array(
+            'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format',
+            'function' => 'doConvert',
+            'shortcut' => 'c2',
+            'options' => array(
+                'flat' => array(
+                    'shortopt' => 'f',
+                    'doc' => 'do not beautify the filelist.',
+                    ),
+                ),
+            'doc' => '[descfile] [descfile2]
+Converts a package.xml in 1.0 format into a package.xml
+in 2.0 format.  The new file will be named package2.xml by default,
+and package.xml will be used as the old file by default.
+This is not the most intelligent conversion, and should only be
+used for automated conversion or learning the format.
+'
+            ),
+        );
+
+    var $output;
+
+    /**
+     * PEAR_Command_Package constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Package(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function _displayValidationResults($err, $warn, $strict = false)
+    {
+        foreach ($err as $e) {
+            $this->output .= "Error: $e\n";
+        }
+        foreach ($warn as $w) {
+            $this->output .= "Warning: $w\n";
+        }
+        $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n",
+                                       sizeof($err), sizeof($warn));
+        if ($strict && count($err) > 0) {
+            $this->output .= "Fix these errors and try again.";
+            return false;
+        }
+        return true;
+    }
+
+    function &getPackager()
+    {
+        if (!class_exists('PEAR_Packager')) {
+            require_once 'PEAR/Packager.php';
+        }
+        $a = &new PEAR_Packager;
+        return $a;
+    }
+
+    function &getPackageFile($config, $debug = false)
+    {
+        if (!class_exists('PEAR_Common')) {
+            require_once 'PEAR/Common.php';
+        }
+        if (!class_exists('PEAR_PackageFile')) {
+            require_once 'PEAR/PackageFile.php';
+        }
+        $a = &new PEAR_PackageFile($config, $debug);
+        $common = new PEAR_Common;
+        $common->ui = $this->ui;
+        $a->setLogger($common);
+        return $a;
+    }
+
+    function doPackage($command, $options, $params)
+    {
+        $this->output = '';
+        $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml';
+        $pkg2 = isset($params[1]) ? $params[1] : null;
+        if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) {
+            $pkg2 = 'package2.xml';
+        }
+
+        $packager = &$this->getPackager();
+        $compress = empty($options['nocompress']) ? true : false;
+        $result   = $packager->package($pkginfofile, $compress, $pkg2);
+        if (PEAR::isError($result)) {
+            return $this->raiseError($result);
+        }
+
+        // Don't want output, only the package file name just created
+        if (isset($options['showname'])) {
+            $this->output = $result;
+        }
+
+        if ($this->output) {
+            $this->ui->outputData($this->output, $command);
+        }
+
+        return true;
+    }
+
+    function doPackageValidate($command, $options, $params)
+    {
+        $this->output = '';
+        if (count($params) < 1) {
+            $params[0] = 'package.xml';
+        }
+
+        $obj = &$this->getPackageFile($this->config, $this->_debug);
+        $obj->rawReturn();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL);
+        } else {
+            $archive = $info->getArchiveFile();
+            $tar = &new Archive_Tar($archive);
+            $tar->extract(dirname($info->getPackageFile()));
+            $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR .
+                $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR .
+                basename($info->getPackageFile()));
+        }
+
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $valid = false;
+        if ($info->getPackagexmlVersion() == '2.0') {
+            if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) {
+                $info->flattenFileList();
+                $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
+            }
+        } else {
+            $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
+        }
+
+        $err = $warn = array();
+        if ($errors = $info->getValidationWarnings()) {
+            foreach ($errors as $error) {
+                if ($error['level'] == 'warning') {
+                    $warn[] = $error['message'];
+                } else {
+                    $err[] = $error['message'];
+                }
+            }
+        }
+
+        $this->_displayValidationResults($err, $warn);
+        $this->ui->outputData($this->output, $command);
+        return true;
+    }
+
+    function doSvnTag($command, $options, $params)
+    {
+        $this->output = '';
+        $_cmd = $command;
+        if (count($params) < 1) {
+            $help = $this->getHelp($command);
+            return $this->raiseError("$command: missing parameter: $help[0]");
+        }
+
+        $packageFile = realpath($params[0]);
+        $dir = dirname($packageFile);
+        $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
+        $obj  = &$this->getPackageFile($this->config, $this->_debug);
+        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $err = $warn = array();
+        if (!$info->validate()) {
+            foreach ($info->getValidationWarnings() as $error) {
+                if ($error['level'] == 'warning') {
+                    $warn[] = $error['message'];
+                } else {
+                    $err[] = $error['message'];
+                }
+            }
+        }
+
+        if (!$this->_displayValidationResults($err, $warn, true)) {
+            $this->ui->outputData($this->output, $command);
+            return $this->raiseError('SVN tag failed');
+        }
+
+        $version    = $info->getVersion();
+        $package    = $info->getName();
+        $svntag     = "$package-$version";
+
+        if (isset($options['delete'])) {
+            return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
+        }
+
+        $path = $this->_svnFindPath($packageFile);
+
+        // Check if there are any modified files
+        $fp = popen('svn st --xml ' . dirname($packageFile), "r");
+        $out = '';
+        while ($line = fgets($fp, 1024)) {
+            $out .= rtrim($line)."\n";
+        }
+        pclose($fp);
+
+        if (!isset($options['quiet']) && strpos($out, 'item="modified"')) {
+            $params = array(array(
+                'name' => 'modified',
+                'type' => 'yesno',
+                'default' => 'no',
+                'prompt' => 'You have files in your SVN checkout (' . $path['from']  . ') that have been modified but not commited, do you still want to tag ' . $version . '?',
+            ));
+            $answers = $this->ui->confirmDialog($params);
+
+            if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) {
+                return true;
+            }
+        }
+
+        if (isset($options['slide'])) {
+            $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
+        }
+
+        // Check if tag already exists
+        $releaseTag = $path['local']['base'] . 'tags' . DIRECTORY_SEPARATOR . $svntag;
+        $existsCommand = 'svn ls ' . $path['base'] . 'tags/';
+
+        $fp = popen($existsCommand, "r");
+        $out = '';
+        while ($line = fgets($fp, 1024)) {
+            $out .= rtrim($line)."\n";
+        }
+        pclose($fp);
+
+        if (in_array($svntag . DIRECTORY_SEPARATOR, explode("\n", $out))) {
+            $this->ui->outputData($this->output, $command);
+            return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.');
+        } elseif (file_exists($path['local']['base'] . 'tags') === false) {
+            return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags');
+        } elseif (is_writeable($path['local']['base'] . 'tags') === false) {
+            return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags');
+        } else {
+            $makeCommand = 'svn mkdir ' . $releaseTag;
+            $this->output .= "+ $makeCommand\n";
+            if (empty($options['dry-run'])) {
+                // We need to create the tag dir.
+                $fp = popen($makeCommand, "r");
+                $out = '';
+                while ($line = fgets($fp, 1024)) {
+                    $out .= rtrim($line)."\n";
+                }
+                pclose($fp);
+                $this->output .= "$out\n";
+            }
+        }
+
+        $command = 'svn';
+        if (isset($options['quiet'])) {
+            $command .= ' -q';
+        }
+
+        $command .= ' copy --parents ';
+
+        $dir   = dirname($packageFile);
+        $dir   = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
+        $files = array_keys($info->getFilelist());
+        if (!in_array(basename($packageFile), $files)) {
+            $files[] = basename($packageFile);
+        }
+
+        array_shift($params);
+        if (count($params)) {
+            // add in additional files to be tagged (package files and such)
+            $files = array_merge($files, $params);
+        }
+
+        $commands = array();
+        foreach ($files as $file) {
+            if (!file_exists($file)) {
+                $file = $dir . DIRECTORY_SEPARATOR . $file;
+            }
+            $commands[] = $command . ' ' . escapeshellarg($file) . ' ' .
+                          escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file);
+        }
+
+        $this->output .= implode("\n", $commands) . "\n";
+        if (empty($options['dry-run'])) {
+            foreach ($commands as $command) {
+                $fp = popen($command, "r");
+                while ($line = fgets($fp, 1024)) {
+                    $this->output .= rtrim($line)."\n";
+                }
+                pclose($fp);
+            }
+        }
+
+        $command = 'svn ci -m "Tagging the ' . $version  . ' release" ' . $releaseTag . "\n";
+        $this->output .= "+ $command\n";
+        if (empty($options['dry-run'])) {
+            $fp = popen($command, "r");
+            while ($line = fgets($fp, 1024)) {
+                $this->output .= rtrim($line)."\n";
+            }
+            pclose($fp);
+        }
+
+        $this->ui->outputData($this->output, $_cmd);
+        return true;
+    }
+
+    function _svnFindPath($file)
+    {
+        $xml = '';
+        $command = "svn info --xml $file";
+        $fp = popen($command, "r");
+        while ($line = fgets($fp, 1024)) {
+            $xml .= rtrim($line)."\n";
+        }
+        pclose($fp);
+        $url_tag = strpos($xml, '<url>');
+        $url = substr($xml, $url_tag + 5, strpos($xml, '</url>', $url_tag + 5) - ($url_tag + 5));
+
+        $path = array();
+        $path['from'] = substr($url, 0, strrpos($url, '/'));
+        $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1);
+
+        // Figure out the local paths - see http://pear.php.net/bugs/17463
+        $pos = strpos($file, DIRECTORY_SEPARATOR . 'trunk' . DIRECTORY_SEPARATOR);
+        if ($pos === false) {
+            $pos = strpos($file, DIRECTORY_SEPARATOR . 'branches' . DIRECTORY_SEPARATOR);
+        }
+        $path['local']['base'] = substr($file, 0, $pos + 1);
+
+        return $path;
+    }
+
+    function _svnRemoveTag($version, $package, $tag, $packageFile, $options)
+    {
+        $command = 'svn';
+
+        if (isset($options['quiet'])) {
+            $command .= ' -q';
+        }
+
+        $command .= ' remove';
+        $command .= ' -m "Removing tag for the ' . $version  . ' release."';
+
+        $path = $this->_svnFindPath($packageFile);
+        $command .= ' ' . $path['base'] . 'tags/' . $tag;
+
+
+        if ($this->config->get('verbose') > 1) {
+            $this->output .= "+ $command\n";
+        }
+
+        $this->output .= "+ $command\n";
+        if (empty($options['dry-run'])) {
+            $fp = popen($command, "r");
+            while ($line = fgets($fp, 1024)) {
+                $this->output .= rtrim($line)."\n";
+            }
+            pclose($fp);
+        }
+
+        $this->ui->outputData($this->output, $command);
+        return true;
+    }
+
+    function doCvsTag($command, $options, $params)
+    {
+        $this->output = '';
+        $_cmd = $command;
+        if (count($params) < 1) {
+            $help = $this->getHelp($command);
+            return $this->raiseError("$command: missing parameter: $help[0]");
+        }
+
+        $packageFile = realpath($params[0]);
+        $obj  = &$this->getPackageFile($this->config, $this->_debug);
+        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $err = $warn = array();
+        if (!$info->validate()) {
+            foreach ($info->getValidationWarnings() as $error) {
+                if ($error['level'] == 'warning') {
+                    $warn[] = $error['message'];
+                } else {
+                    $err[] = $error['message'];
+                }
+            }
+        }
+
+        if (!$this->_displayValidationResults($err, $warn, true)) {
+            $this->ui->outputData($this->output, $command);
+            return $this->raiseError('CVS tag failed');
+        }
+
+        $version    = $info->getVersion();
+        $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version);
+        $cvstag     = "RELEASE_$cvsversion";
+        $files      = array_keys($info->getFilelist());
+        $command = 'cvs';
+        if (isset($options['quiet'])) {
+            $command .= ' -q';
+        }
+
+        if (isset($options['reallyquiet'])) {
+            $command .= ' -Q';
+        }
+
+        $command .= ' tag';
+        if (isset($options['slide'])) {
+            $command .= ' -F';
+        }
+
+        if (isset($options['delete'])) {
+            $command .= ' -d';
+        }
+
+        $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]);
+        array_shift($params);
+        if (count($params)) {
+            // add in additional files to be tagged
+            $files = array_merge($files, $params);
+        }
+
+        $dir = dirname($packageFile);
+        $dir = substr($dir, strrpos($dir, '/') + 1);
+        foreach ($files as $file) {
+            if (!file_exists($file)) {
+                $file = $dir . DIRECTORY_SEPARATOR . $file;
+            }
+            $command .= ' ' . escapeshellarg($file);
+        }
+
+        if ($this->config->get('verbose') > 1) {
+            $this->output .= "+ $command\n";
+        }
+
+        $this->output .= "+ $command\n";
+        if (empty($options['dry-run'])) {
+            $fp = popen($command, "r");
+            while ($line = fgets($fp, 1024)) {
+                $this->output .= rtrim($line)."\n";
+            }
+            pclose($fp);
+        }
+
+        $this->ui->outputData($this->output, $_cmd);
+        return true;
+    }
+
+    function doCvsDiff($command, $options, $params)
+    {
+        $this->output = '';
+        if (sizeof($params) < 1) {
+            $help = $this->getHelp($command);
+            return $this->raiseError("$command: missing parameter: $help[0]");
+        }
+
+        $file = realpath($params[0]);
+        $obj  = &$this->getPackageFile($this->config, $this->_debug);
+        $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $err = $warn = array();
+        if (!$info->validate()) {
+            foreach ($info->getValidationWarnings() as $error) {
+                if ($error['level'] == 'warning') {
+                    $warn[] = $error['message'];
+                } else {
+                    $err[] = $error['message'];
+                }
+            }
+        }
+
+        if (!$this->_displayValidationResults($err, $warn, true)) {
+            $this->ui->outputData($this->output, $command);
+            return $this->raiseError('CVS diff failed');
+        }
+
+        $info1 = $info->getFilelist();
+        $files = $info1;
+        $cmd = "cvs";
+        if (isset($options['quiet'])) {
+            $cmd .= ' -q';
+            unset($options['quiet']);
+        }
+
+        if (isset($options['reallyquiet'])) {
+            $cmd .= ' -Q';
+            unset($options['reallyquiet']);
+        }
+
+        if (isset($options['release'])) {
+            $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']);
+            $cvstag = "RELEASE_$cvsversion";
+            $options['revision'] = $cvstag;
+            unset($options['release']);
+        }
+
+        $execute = true;
+        if (isset($options['dry-run'])) {
+            $execute = false;
+            unset($options['dry-run']);
+        }
+
+        $cmd .= ' diff';
+        // the rest of the options are passed right on to "cvs diff"
+        foreach ($options as $option => $optarg) {
+            $arg = $short = false;
+            if (isset($this->commands[$command]['options'][$option])) {
+                $arg = $this->commands[$command]['options'][$option]['arg'];
+                $short = $this->commands[$command]['options'][$option]['shortopt'];
+            }
+            $cmd .= $short ? " -$short" : " --$option";
+            if ($arg && $optarg) {
+                $cmd .= ($short ? '' : '=') . escapeshellarg($optarg);
+            }
+        }
+
+        foreach ($files as $file) {
+            $cmd .= ' ' . escapeshellarg($file['name']);
+        }
+
+        if ($this->config->get('verbose') > 1) {
+            $this->output .= "+ $cmd\n";
+        }
+
+        if ($execute) {
+            $fp = popen($cmd, "r");
+            while ($line = fgets($fp, 1024)) {
+                $this->output .= rtrim($line)."\n";
+            }
+            pclose($fp);
+        }
+
+        $this->ui->outputData($this->output, $command);
+        return true;
+    }
+
+    function doPackageDependencies($command, $options, $params)
+    {
+        // $params[0] -> the PEAR package to list its information
+        if (count($params) !== 1) {
+            return $this->raiseError("bad parameter(s), try \"help $command\"");
+        }
+
+        $obj = &$this->getPackageFile($this->config, $this->_debug);
+        if (is_file($params[0]) || strpos($params[0], '.xml') > 0) {
+           $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+        } else {
+            $reg  = $this->config->getRegistry();
+            $info = $obj->fromArray($reg->packageInfo($params[0]));
+        }
+
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $deps = $info->getDeps();
+        if (is_array($deps)) {
+            if ($info->getPackagexmlVersion() == '1.0') {
+                $data = array(
+                    'caption' => 'Dependencies for pear/' . $info->getPackage(),
+                    'border' => true,
+                    'headline' => array("Required?", "Type", "Name", "Relation", "Version"),
+                    );
+
+                foreach ($deps as $d) {
+                    if (isset($d['optional'])) {
+                        if ($d['optional'] == 'yes') {
+                            $req = 'No';
+                        } else {
+                            $req = 'Yes';
+                        }
+                    } else {
+                        $req = 'Yes';
+                    }
+
+                    if (isset($this->_deps_rel_trans[$d['rel']])) {
+                        $rel = $this->_deps_rel_trans[$d['rel']];
+                    } else {
+                        $rel = $d['rel'];
+                    }
+
+                    if (isset($this->_deps_type_trans[$d['type']])) {
+                        $type = ucfirst($this->_deps_type_trans[$d['type']]);
+                    } else {
+                        $type = $d['type'];
+                    }
+
+                    if (isset($d['name'])) {
+                        $name = $d['name'];
+                    } else {
+                        $name = '';
+                    }
+
+                    if (isset($d['version'])) {
+                        $version = $d['version'];
+                    } else {
+                        $version = '';
+                    }
+
+                    $data['data'][] = array($req, $type, $name, $rel, $version);
+                }
+            } else { // package.xml 2.0 dependencies display
+                require_once 'PEAR/Dependency2.php';
+                $deps = $info->getDependencies();
+                $reg = &$this->config->getRegistry();
+                if (is_array($deps)) {
+                    $d = new PEAR_Dependency2($this->config, array(), '');
+                    $data = array(
+                        'caption' => 'Dependencies for ' . $info->getPackage(),
+                        'border' => true,
+                        'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'),
+                        );
+                    foreach ($deps as $type => $subd) {
+                        $req = ($type == 'required') ? 'Yes' : 'No';
+                        if ($type == 'group') {
+                            $group = $subd['attribs']['name'];
+                        } else {
+                            $group = '';
+                        }
+
+                        if (!isset($subd[0])) {
+                            $subd = array($subd);
+                        }
+
+                        foreach ($subd as $groupa) {
+                            foreach ($groupa as $deptype => $depinfo) {
+                                if ($deptype == 'attribs') {
+                                    continue;
+                                }
+
+                                if ($deptype == 'pearinstaller') {
+                                    $deptype = 'pear Installer';
+                                }
+
+                                if (!isset($depinfo[0])) {
+                                    $depinfo = array($depinfo);
+                                }
+
+                                foreach ($depinfo as $inf) {
+                                    $name = '';
+                                    if (isset($inf['channel'])) {
+                                        $alias = $reg->channelAlias($inf['channel']);
+                                        if (!$alias) {
+                                            $alias = '(channel?) ' .$inf['channel'];
+                                        }
+                                        $name = $alias . '/';
+
+                                    }
+                                    if (isset($inf['name'])) {
+                                        $name .= $inf['name'];
+                                    } elseif (isset($inf['pattern'])) {
+                                        $name .= $inf['pattern'];
+                                    } else {
+                                        $name .= '';
+                                    }
+
+                                    if (isset($inf['uri'])) {
+                                        $name .= ' [' . $inf['uri'] .  ']';
+                                    }
+
+                                    if (isset($inf['conflicts'])) {
+                                        $ver = 'conflicts';
+                                    } else {
+                                        $ver = $d->_getExtraString($inf);
+                                    }
+
+                                    $data['data'][] = array($req, ucfirst($deptype), $name,
+                                        $ver, $group);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            $this->ui->outputData($data, $command);
+            return true;
+        }
+
+        // Fallback
+        $this->ui->outputData("This package does not have any dependencies.", $command);
+    }
+
+    function doSign($command, $options, $params)
+    {
+        // should move most of this code into PEAR_Packager
+        // so it'll be easy to implement "pear package --sign"
+        if (count($params) !== 1) {
+            return $this->raiseError("bad parameter(s), try \"help $command\"");
+        }
+
+        require_once 'System.php';
+        require_once 'Archive/Tar.php';
+
+        if (!file_exists($params[0])) {
+            return $this->raiseError("file does not exist: $params[0]");
+        }
+
+        $obj = $this->getPackageFile($this->config, $this->_debug);
+        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        $tar = new Archive_Tar($params[0]);
+
+        $tmpdir = $this->config->get('temp_dir');
+        $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign');
+        if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
+            return $this->raiseError("failed to extract tar file");
+        }
+
+        if (file_exists("$tmpdir/package.sig")) {
+            return $this->raiseError("package already signed");
+        }
+
+        $packagexml = 'package.xml';
+        if (file_exists("$tmpdir/package2.xml")) {
+            $packagexml = 'package2.xml';
+        }
+
+        if (file_exists("$tmpdir/package.sig")) {
+            unlink("$tmpdir/package.sig");
+        }
+
+        if (!file_exists("$tmpdir/$packagexml")) {
+            return $this->raiseError("Extracted file $tmpdir/$packagexml not found.");
+        }
+
+        $input = $this->ui->userDialog($command,
+                                       array('GnuPG Passphrase'),
+                                       array('password'));
+        if (!isset($input[0])) {
+            //use empty passphrase
+            $input[0] = '';
+        }
+
+        $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null';
+        $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w");
+        if (!$gpg) {
+            return $this->raiseError("gpg command failed");
+        }
+
+        fwrite($gpg, "$input[0]\n");
+        if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
+            return $this->raiseError("gpg sign failed");
+        }
+
+        if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) {
+            return $this->raiseError('failed adding signature to file');
+        }
+
+        $this->ui->outputData("Package signed.", $command);
+        return true;
+    }
+
+    /**
+     * For unit testing purposes
+     */
+    function &getInstaller(&$ui)
+    {
+        if (!class_exists('PEAR_Installer')) {
+            require_once 'PEAR/Installer.php';
+        }
+        $a = &new PEAR_Installer($ui);
+        return $a;
+    }
+
+    /**
+     * For unit testing purposes
+     */
+    function &getCommandPackaging(&$ui, &$config)
+    {
+        if (!class_exists('PEAR_Command_Packaging')) {
+            if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) {
+                fclose($fp);
+                include_once 'PEAR/Command/Packaging.php';
+            }
+        }
+
+        if (class_exists('PEAR_Command_Packaging')) {
+            $a = &new PEAR_Command_Packaging($ui, $config);
+        } else {
+            $a = null;
+        }
+
+        return $a;
+    }
+
+    function doMakeRPM($command, $options, $params)
+    {
+
+        // Check to see if PEAR_Command_Packaging is installed, and
+        // transparently switch to use the "make-rpm-spec" command from it
+        // instead, if it does. Otherwise, continue to use the old version
+        // of "makerpm" supplied with this package (PEAR).
+        $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config);
+        if ($packaging_cmd !== null) {
+            $this->ui->outputData('PEAR_Command_Packaging is installed; using '.
+                'newer "make-rpm-spec" command instead');
+            return $packaging_cmd->run('make-rpm-spec', $options, $params);
+        }
+
+        $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '.
+          'improved version is available via "pear make-rpm-spec", which '.
+          'is available by installing PEAR_Command_Packaging');
+        return true;
+    }
+
+    function doConvert($command, $options, $params)
+    {
+        $packagexml    = isset($params[0]) ? $params[0] : 'package.xml';
+        $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) .
+            DIRECTORY_SEPARATOR . 'package2.xml';
+        $pkg = &$this->getPackageFile($this->config, $this->_debug);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($pf)) {
+            if (is_array($pf->getUserInfo())) {
+                foreach ($pf->getUserInfo() as $warning) {
+                    $this->ui->outputData($warning['message']);
+                }
+            }
+            return $this->raiseError($pf);
+        }
+
+        if (is_a($pf, 'PEAR_PackageFile_v2')) {
+            $this->ui->outputData($packagexml . ' is already a package.xml version 2.0');
+            return true;
+        }
+
+        $gen   = &$pf->getDefaultGenerator();
+        $newpf = &$gen->toV2();
+        $newpf->setPackagefile($newpackagexml);
+        $gen = &$newpf->getDefaultGenerator();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL);
+        $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml));
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($saved)) {
+            if (is_array($saved->getUserInfo())) {
+                foreach ($saved->getUserInfo() as $warning) {
+                    $this->ui->outputData($warning['message']);
+                }
+            }
+
+            $this->ui->outputData($saved->getMessage());
+            return true;
+        }
+
+        $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"');
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Package.xml b/WEB-INF/lib/pear/PEAR/Command/Package.xml
new file mode 100644 (file)
index 0000000..d1aff9d
--- /dev/null
@@ -0,0 +1,237 @@
+<commands version="1.0">
+ <package>
+  <summary>Build Package</summary>
+  <function>doPackage</function>
+  <shortcut>p</shortcut>
+  <options>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>Do not gzip the package file</doc>
+   </nocompress>
+   <showname>
+    <shortopt>n</shortopt>
+    <doc>Print the name of the packaged file.</doc>
+   </showname>
+  </options>
+  <doc>[descfile] [descfile2]
+Creates a PEAR package from its description file (usually called
+package.xml).  If a second packagefile is passed in, then
+the packager will check to make sure that one is a package.xml
+version 1.0, and the other is a package.xml version 2.0.  The
+package.xml version 1.0 will be saved as &quot;package.xml&quot; in the archive,
+and the other as &quot;package2.xml&quot; in the archive&quot;
+</doc>
+ </package>
+ <package-validate>
+  <summary>Validate Package Consistency</summary>
+  <function>doPackageValidate</function>
+  <shortcut>pv</shortcut>
+  <options />
+  <doc>
+</doc>
+ </package-validate>
+ <cvsdiff>
+  <summary>Run a &quot;cvs diff&quot; for all files in a package</summary>
+  <function>doCvsDiff</function>
+  <shortcut>cd</shortcut>
+  <options>
+   <quiet>
+    <shortopt>q</shortopt>
+    <doc>Be quiet</doc>
+   </quiet>
+   <reallyquiet>
+    <shortopt>Q</shortopt>
+    <doc>Be really quiet</doc>
+   </reallyquiet>
+   <date>
+    <shortopt>D</shortopt>
+    <doc>Diff against revision of DATE</doc>
+    <arg>DATE</arg>
+   </date>
+   <release>
+    <shortopt>R</shortopt>
+    <doc>Diff against tag for package release REL</doc>
+    <arg>REL</arg>
+   </release>
+   <revision>
+    <shortopt>r</shortopt>
+    <doc>Diff against revision REV</doc>
+    <arg>REV</arg>
+   </revision>
+   <context>
+    <shortopt>c</shortopt>
+    <doc>Generate context diff</doc>
+   </context>
+   <unified>
+    <shortopt>u</shortopt>
+    <doc>Generate unified diff</doc>
+   </unified>
+   <ignore-case>
+    <shortopt>i</shortopt>
+    <doc>Ignore case, consider upper- and lower-case letters equivalent</doc>
+   </ignore-case>
+   <ignore-whitespace>
+    <shortopt>b</shortopt>
+    <doc>Ignore changes in amount of white space</doc>
+   </ignore-whitespace>
+   <ignore-blank-lines>
+    <shortopt>B</shortopt>
+    <doc>Ignore changes that insert or delete blank lines</doc>
+   </ignore-blank-lines>
+   <brief>
+    <shortopt></shortopt>
+    <doc>Report only whether the files differ, no details</doc>
+   </brief>
+   <dry-run>
+    <shortopt>n</shortopt>
+    <doc>Don&#039;t do anything, just pretend</doc>
+   </dry-run>
+  </options>
+  <doc>&lt;package.xml&gt;
+Compares all the files in a package.  Without any options, this
+command will compare the current code with the last checked-in code.
+Using the -r or -R option you may compare the current code with that
+of a specific release.
+</doc>
+ </cvsdiff>
+ <svntag>
+  <summary>Set SVN Release Tag</summary>
+  <function>doSvnTag</function>
+  <shortcut>sv</shortcut>
+  <options>
+   <quiet>
+    <shortopt>q</shortopt>
+    <doc>Be quiet</doc>
+   </quiet>
+   <slide>
+    <shortopt>F</shortopt>
+    <doc>Move (slide) tag if it exists</doc>
+   </slide>
+   <delete>
+    <shortopt>d</shortopt>
+    <doc>Remove tag</doc>
+   </delete>
+   <dry-run>
+    <shortopt>n</shortopt>
+    <doc>Don&#039;t do anything, just pretend</doc>
+   </dry-run>
+  </options>
+  <doc>&lt;package.xml&gt; [files...]
+ Sets a SVN tag on all files in a package.  Use this command after you have
+ packaged a distribution tarball with the &quot;package&quot; command to tag what
+ revisions of what files were in that release.  If need to fix something
+ after running svntag once, but before the tarball is released to the public,
+ use the &quot;slide&quot; option to move the release tag.
+
+ to include files (such as a second package.xml, or tests not included in the
+ release), pass them as additional parameters.
+ </doc>
+ </svntag>
+ <cvstag>
+  <summary>Set CVS Release Tag</summary>
+  <function>doCvsTag</function>
+  <shortcut>ct</shortcut>
+  <options>
+   <quiet>
+    <shortopt>q</shortopt>
+    <doc>Be quiet</doc>
+   </quiet>
+   <reallyquiet>
+    <shortopt>Q</shortopt>
+    <doc>Be really quiet</doc>
+   </reallyquiet>
+   <slide>
+    <shortopt>F</shortopt>
+    <doc>Move (slide) tag if it exists</doc>
+   </slide>
+   <delete>
+    <shortopt>d</shortopt>
+    <doc>Remove tag</doc>
+   </delete>
+   <dry-run>
+    <shortopt>n</shortopt>
+    <doc>Don&#039;t do anything, just pretend</doc>
+   </dry-run>
+  </options>
+  <doc>&lt;package.xml&gt; [files...]
+Sets a CVS tag on all files in a package.  Use this command after you have
+packaged a distribution tarball with the &quot;package&quot; command to tag what
+revisions of what files were in that release.  If need to fix something
+after running cvstag once, but before the tarball is released to the public,
+use the &quot;slide&quot; option to move the release tag.
+
+to include files (such as a second package.xml, or tests not included in the
+release), pass them as additional parameters.
+</doc>
+ </cvstag>
+ <package-dependencies>
+  <summary>Show package dependencies</summary>
+  <function>doPackageDependencies</function>
+  <shortcut>pd</shortcut>
+  <options />
+  <doc>&lt;package-file&gt; or &lt;package.xml&gt; or &lt;install-package-name&gt;
+List all dependencies the package has.
+Can take a tgz / tar file, package.xml or a package name of an installed package.</doc>
+ </package-dependencies>
+ <sign>
+  <summary>Sign a package distribution file</summary>
+  <function>doSign</function>
+  <shortcut>si</shortcut>
+  <options>
+   <verbose>
+    <shortopt>v</shortopt>
+    <doc>Display GnuPG output</doc>
+   </verbose>
+  </options>
+  <doc>&lt;package-file&gt;
+Signs a package distribution (.tar or .tgz) file with GnuPG.</doc>
+ </sign>
+ <makerpm>
+  <summary>Builds an RPM spec file from a PEAR package</summary>
+  <function>doMakeRPM</function>
+  <shortcut>rpm</shortcut>
+  <options>
+   <spec-template>
+    <shortopt>t</shortopt>
+    <doc>Use FILE as RPM spec file template</doc>
+    <arg>FILE</arg>
+   </spec-template>
+   <rpm-pkgname>
+    <shortopt>p</shortopt>
+    <doc>Use FORMAT as format string for RPM package name, %s is replaced
+by the PEAR package name, defaults to &quot;PEAR::%s&quot;.</doc>
+    <arg>FORMAT</arg>
+   </rpm-pkgname>
+  </options>
+  <doc>&lt;package-file&gt;
+
+Creates an RPM .spec file for wrapping a PEAR package inside an RPM
+package.  Intended to be used from the SPECS directory, with the PEAR
+package tarball in the SOURCES directory:
+
+$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
+Wrote RPM spec file PEAR::Net_Geo-1.0.spec
+$ rpm -bb PEAR::Net_Socket-1.0.spec
+...
+Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
+</doc>
+ </makerpm>
+ <convert>
+  <summary>Convert a package.xml 1.0 to package.xml 2.0 format</summary>
+  <function>doConvert</function>
+  <shortcut>c2</shortcut>
+  <options>
+   <flat>
+    <shortopt>f</shortopt>
+    <doc>do not beautify the filelist.</doc>
+   </flat>
+  </options>
+  <doc>[descfile] [descfile2]
+Converts a package.xml in 1.0 format into a package.xml
+in 2.0 format.  The new file will be named package2.xml by default,
+and package.xml will be used as the old file by default.
+This is not the most intelligent conversion, and should only be
+used for automated conversion or learning the format.
+</doc>
+ </convert>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Pickle.php b/WEB-INF/lib/pear/PEAR/Command/Pickle.php
new file mode 100644 (file)
index 0000000..87aa25e
--- /dev/null
@@ -0,0 +1,421 @@
+<?php
+/**
+ * PEAR_Command_Pickle (pickle command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2005-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Pickle.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2005-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.1
+ */
+
+class PEAR_Command_Pickle extends PEAR_Command_Common
+{
+    var $commands = array(
+        'pickle' => array(
+            'summary' => 'Build PECL Package',
+            'function' => 'doPackage',
+            'shortcut' => 'pi',
+            'options' => array(
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'Do not gzip the package file'
+                    ),
+                'showname' => array(
+                    'shortopt' => 'n',
+                    'doc' => 'Print the name of the packaged file.',
+                    ),
+                ),
+            'doc' => '[descfile]
+Creates a PECL package from its package2.xml file.
+
+An automatic conversion will be made to a package.xml 1.0 and written out to
+disk in the current directory as "package.xml".  Note that
+only simple package.xml 2.0 will be converted.  package.xml 2.0 with:
+
+ - dependency types other than required/optional PECL package/ext/php/pearinstaller
+ - more than one extsrcrelease or zendextsrcrelease
+ - zendextbinrelease, extbinrelease, phprelease, or bundle release type
+ - dependency groups
+ - ignore tags in release filelist
+ - tasks other than replace
+ - custom roles
+
+will cause pickle to fail, and output an error message.  If your package2.xml
+uses any of these features, you are best off using PEAR_PackageFileManager to
+generate both package.xml.
+'
+            ),
+        );
+
+    /**
+     * PEAR_Command_Package constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Pickle(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    /**
+     * For unit-testing ease
+     *
+     * @return PEAR_Packager
+     */
+    function &getPackager()
+    {
+        if (!class_exists('PEAR_Packager')) {
+            require_once 'PEAR/Packager.php';
+        }
+
+        $a = &new PEAR_Packager;
+        return $a;
+    }
+
+    /**
+     * For unit-testing ease
+     *
+     * @param PEAR_Config $config
+     * @param bool $debug
+     * @param string|null $tmpdir
+     * @return PEAR_PackageFile
+     */
+    function &getPackageFile($config, $debug = false)
+    {
+        if (!class_exists('PEAR_Common')) {
+            require_once 'PEAR/Common.php';
+        }
+
+        if (!class_exists('PEAR_PackageFile')) {
+            require_once 'PEAR/PackageFile.php';
+        }
+
+        $a = &new PEAR_PackageFile($config, $debug);
+        $common = new PEAR_Common;
+        $common->ui = $this->ui;
+        $a->setLogger($common);
+        return $a;
+    }
+
+    function doPackage($command, $options, $params)
+    {
+        $this->output = '';
+        $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml';
+        $packager = &$this->getPackager();
+        if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) {
+            return $err;
+        }
+
+        $compress = empty($options['nocompress']) ? true : false;
+        $result = $packager->package($pkginfofile, $compress, 'package.xml');
+        if (PEAR::isError($result)) {
+            return $this->raiseError($result);
+        }
+
+        // Don't want output, only the package file name just created
+        if (isset($options['showname'])) {
+            $this->ui->outputData($result, $command);
+        }
+
+        return true;
+    }
+
+    function _convertPackage($packagexml)
+    {
+        $pkg = &$this->getPackageFile($this->config);
+        $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
+        if (!is_a($pf2, 'PEAR_PackageFile_v2')) {
+            return $this->raiseError('Cannot process "' .
+                $packagexml . '", is not a package.xml 2.0');
+        }
+
+        require_once 'PEAR/PackageFile/v1.php';
+        $pf = new PEAR_PackageFile_v1;
+        $pf->setConfig($this->config);
+        if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", is not an extension source package.  Using a PEAR_PackageFileManager-based ' .
+            'script is an option');
+        }
+
+        if (is_array($pf2->getUsesRole())) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains custom roles.  Using a PEAR_PackageFileManager-based script or ' .
+            'the convert command is an option');
+        }
+
+        if (is_array($pf2->getUsesTask())) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains custom tasks.  Using a PEAR_PackageFileManager-based script or ' .
+            'the convert command is an option');
+        }
+
+        $deps = $pf2->getDependencies();
+        if (isset($deps['group'])) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains dependency groups.  Using a PEAR_PackageFileManager-based script ' .
+            'or the convert command is an option');
+        }
+
+        if (isset($deps['required']['subpackage']) ||
+              isset($deps['optional']['subpackage'])) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains subpackage dependencies.  Using a PEAR_PackageFileManager-based  '.
+            'script is an option');
+        }
+
+        if (isset($deps['required']['os'])) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains os dependencies.  Using a PEAR_PackageFileManager-based  '.
+            'script is an option');
+        }
+
+        if (isset($deps['required']['arch'])) {
+            return $this->raiseError('Cannot safely convert "' . $packagexml .
+            '", contains arch dependencies.  Using a PEAR_PackageFileManager-based  '.
+            'script is an option');
+        }
+
+        $pf->setPackage($pf2->getPackage());
+        $pf->setSummary($pf2->getSummary());
+        $pf->setDescription($pf2->getDescription());
+        foreach ($pf2->getMaintainers() as $maintainer) {
+            $pf->addMaintainer($maintainer['role'], $maintainer['handle'],
+                $maintainer['name'], $maintainer['email']);
+        }
+
+        $pf->setVersion($pf2->getVersion());
+        $pf->setDate($pf2->getDate());
+        $pf->setLicense($pf2->getLicense());
+        $pf->setState($pf2->getState());
+        $pf->setNotes($pf2->getNotes());
+        $pf->addPhpDep($deps['required']['php']['min'], 'ge');
+        if (isset($deps['required']['php']['max'])) {
+            $pf->addPhpDep($deps['required']['php']['max'], 'le');
+        }
+
+        if (isset($deps['required']['package'])) {
+            if (!isset($deps['required']['package'][0])) {
+                $deps['required']['package'] = array($deps['required']['package']);
+            }
+
+            foreach ($deps['required']['package'] as $dep) {
+                if (!isset($dep['channel'])) {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains uri-based dependency on a package.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if ($dep['channel'] != 'pear.php.net'
+                    && $dep['channel'] != 'pecl.php.net'
+                    && $dep['channel'] != 'doc.php.net') {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains dependency on a non-standard channel package.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if (isset($dep['conflicts'])) {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains conflicts dependency.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if (isset($dep['exclude'])) {
+                    $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+                }
+
+                if (isset($dep['min'])) {
+                    $pf->addPackageDep($dep['name'], $dep['min'], 'ge');
+                }
+
+                if (isset($dep['max'])) {
+                    $pf->addPackageDep($dep['name'], $dep['max'], 'le');
+                }
+            }
+        }
+
+        if (isset($deps['required']['extension'])) {
+            if (!isset($deps['required']['extension'][0])) {
+                $deps['required']['extension'] = array($deps['required']['extension']);
+            }
+
+            foreach ($deps['required']['extension'] as $dep) {
+                if (isset($dep['conflicts'])) {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains conflicts dependency.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if (isset($dep['exclude'])) {
+                    $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+                }
+
+                if (isset($dep['min'])) {
+                    $pf->addExtensionDep($dep['name'], $dep['min'], 'ge');
+                }
+
+                if (isset($dep['max'])) {
+                    $pf->addExtensionDep($dep['name'], $dep['max'], 'le');
+                }
+            }
+        }
+
+        if (isset($deps['optional']['package'])) {
+            if (!isset($deps['optional']['package'][0])) {
+                $deps['optional']['package'] = array($deps['optional']['package']);
+            }
+
+            foreach ($deps['optional']['package'] as $dep) {
+                if (!isset($dep['channel'])) {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains uri-based dependency on a package.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if ($dep['channel'] != 'pear.php.net'
+                    && $dep['channel'] != 'pecl.php.net'
+                    && $dep['channel'] != 'doc.php.net') {
+                    return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+                    ' contains dependency on a non-standard channel package.  Using a ' .
+                    'PEAR_PackageFileManager-based script is an option');
+                }
+
+                if (isset($dep['exclude'])) {
+                    $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+                }
+
+                if (isset($dep['min'])) {
+                    $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes');
+                }
+
+                if (isset($dep['max'])) {
+                    $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes');
+                }
+            }
+        }
+
+        if (isset($deps['optional']['extension'])) {
+            if (!isset($deps['optional']['extension'][0])) {
+                $deps['optional']['extension'] = array($deps['optional']['extension']);
+            }
+
+            foreach ($deps['optional']['extension'] as $dep) {
+                if (isset($dep['exclude'])) {
+                    $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+                }
+
+                if (isset($dep['min'])) {
+                    $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes');
+                }
+
+                if (isset($dep['max'])) {
+                    $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes');
+                }
+            }
+        }
+
+        $contents = $pf2->getContents();
+        $release  = $pf2->getReleases();
+        if (isset($releases[0])) {
+            return $this->raiseError('Cannot safely process "' . $packagexml . '" contains '
+            . 'multiple extsrcrelease/zendextsrcrelease tags.  Using a PEAR_PackageFileManager-based script ' .
+            'or the convert command is an option');
+        }
+
+        if ($configoptions = $pf2->getConfigureOptions()) {
+            foreach ($configoptions as $option) {
+                $default = isset($option['default']) ? $option['default'] : false;
+                $pf->addConfigureOption($option['name'], $option['prompt'], $default);
+            }
+        }
+
+        if (isset($release['filelist']['ignore'])) {
+            return $this->raiseError('Cannot safely process "' . $packagexml . '" contains '
+            . 'ignore tags.  Using a PEAR_PackageFileManager-based script or the convert' .
+            ' command is an option');
+        }
+
+        if (isset($release['filelist']['install']) &&
+              !isset($release['filelist']['install'][0])) {
+            $release['filelist']['install'] = array($release['filelist']['install']);
+        }
+
+        if (isset($contents['dir']['attribs']['baseinstalldir'])) {
+            $baseinstalldir = $contents['dir']['attribs']['baseinstalldir'];
+        } else {
+            $baseinstalldir = false;
+        }
+
+        if (!isset($contents['dir']['file'][0])) {
+            $contents['dir']['file'] = array($contents['dir']['file']);
+        }
+
+        foreach ($contents['dir']['file'] as $file) {
+            if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) {
+                $file['attribs']['baseinstalldir'] = $baseinstalldir;
+            }
+
+            $processFile = $file;
+            unset($processFile['attribs']);
+            if (count($processFile)) {
+                foreach ($processFile as $name => $task) {
+                    if ($name != $pf2->getTasksNs() . ':replace') {
+                        return $this->raiseError('Cannot safely process "' . $packagexml .
+                        '" contains tasks other than replace.  Using a ' .
+                        'PEAR_PackageFileManager-based script is an option.');
+                    }
+                    $file['attribs']['replace'][] = $task;
+                }
+            }
+
+            if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) {
+                return $this->raiseError('Cannot safely convert "' . $packagexml .
+                '", contains custom roles.  Using a PEAR_PackageFileManager-based script ' .
+                'or the convert command is an option');
+            }
+
+            if (isset($release['filelist']['install'])) {
+                foreach ($release['filelist']['install'] as $installas) {
+                    if ($installas['attribs']['name'] == $file['attribs']['name']) {
+                        $file['attribs']['install-as'] = $installas['attribs']['as'];
+                    }
+                }
+            }
+
+            $pf->addFile('/', $file['attribs']['name'], $file['attribs']);
+        }
+
+        if ($pf2->getChangeLog()) {
+            $this->ui->outputData('WARNING: changelog is not translated to package.xml ' .
+                '1.0, use PEAR_PackageFileManager-based script if you need changelog-' .
+                'translation for package.xml 1.0');
+        }
+
+        $gen = &$pf->getDefaultGenerator();
+        $gen->toPackageFile('.');
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Pickle.xml b/WEB-INF/lib/pear/PEAR/Command/Pickle.xml
new file mode 100644 (file)
index 0000000..721ecea
--- /dev/null
@@ -0,0 +1,36 @@
+<commands version="1.0">
+ <pickle>
+  <summary>Build PECL Package</summary>
+  <function>doPackage</function>
+  <shortcut>pi</shortcut>
+  <options>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>Do not gzip the package file</doc>
+   </nocompress>
+   <showname>
+    <shortopt>n</shortopt>
+    <doc>Print the name of the packaged file.</doc>
+   </showname>
+  </options>
+  <doc>[descfile]
+Creates a PECL package from its package2.xml file.
+
+An automatic conversion will be made to a package.xml 1.0 and written out to
+disk in the current directory as &quot;package.xml&quot;.  Note that
+only simple package.xml 2.0 will be converted.  package.xml 2.0 with:
+
+ - dependency types other than required/optional PECL package/ext/php/pearinstaller
+ - more than one extsrcrelease or zendextsrcrelease
+ - zendextbinrelease, extbinrelease, phprelease, or bundle release type
+ - dependency groups
+ - ignore tags in release filelist
+ - tasks other than replace
+ - custom roles
+
+will cause pickle to fail, and output an error message.  If your package2.xml
+uses any of these features, you are best off using PEAR_PackageFileManager to
+generate both package.xml.
+</doc>
+ </pickle>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Registry.php b/WEB-INF/lib/pear/PEAR/Command/Registry.php
new file mode 100644 (file)
index 0000000..4304db5
--- /dev/null
@@ -0,0 +1,1145 @@
+<?php
+/**
+ * PEAR_Command_Registry (list, list-files, shell-test, info commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for registry manipulation
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Registry extends PEAR_Command_Common
+{
+    var $commands = array(
+        'list' => array(
+            'summary' => 'List Installed Packages In The Default Channel',
+            'function' => 'doList',
+            'shortcut' => 'l',
+            'options' => array(
+                'channel' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'list installed packages from this channel',
+                    'arg' => 'CHAN',
+                    ),
+                'allchannels' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'list installed packages from all channels',
+                    ),
+                'channelinfo' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'output fully channel-aware data, even on failure',
+                    ),
+                ),
+            'doc' => '<package>
+If invoked without parameters, this command lists the PEAR packages
+installed in your php_dir ({config php_dir}).  With a parameter, it
+lists the files in a package.
+',
+            ),
+        'list-files' => array(
+            'summary' => 'List Files In Installed Package',
+            'function' => 'doFileList',
+            'shortcut' => 'fl',
+            'options' => array(),
+            'doc' => '<package>
+List the files in an installed package.
+'
+            ),
+        'shell-test' => array(
+            'summary' => 'Shell Script Test',
+            'function' => 'doShellTest',
+            'shortcut' => 'st',
+            'options' => array(),
+            'doc' => '<package> [[relation] version]
+Tests if a package is installed in the system. Will exit(1) if it is not.
+   <relation>   The version comparison operator. One of:
+                <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne
+   <version>    The version to compare with
+'),
+        'info' => array(
+            'summary'  => 'Display information about a package',
+            'function' => 'doInfo',
+            'shortcut' => 'in',
+            'options'  => array(),
+            'doc'      => '<package>
+Displays information about a package. The package argument may be a
+local package file, an URL to a package file, or the name of an
+installed package.'
+            )
+        );
+
+    /**
+     * PEAR_Command_Registry constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Registry(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function _sortinfo($a, $b)
+    {
+        $apackage = isset($a['package']) ? $a['package'] : $a['name'];
+        $bpackage = isset($b['package']) ? $b['package'] : $b['name'];
+        return strcmp($apackage, $bpackage);
+    }
+
+    function doList($command, $options, $params)
+    {
+        $reg = &$this->config->getRegistry();
+        $channelinfo = isset($options['channelinfo']);
+        if (isset($options['allchannels']) && !$channelinfo) {
+            return $this->doListAll($command, array(), $params);
+        }
+
+        if (isset($options['allchannels']) && $channelinfo) {
+            // allchannels with $channelinfo
+            unset($options['allchannels']);
+            $channels = $reg->getChannels();
+            $errors = array();
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            foreach ($channels as $channel) {
+                $options['channel'] = $channel->getName();
+                $ret = $this->doList($command, $options, $params);
+
+                if (PEAR::isError($ret)) {
+                    $errors[] = $ret;
+                }
+            }
+
+            PEAR::staticPopErrorHandling();
+            if (count($errors)) {
+                // for now, only give first error
+                return PEAR::raiseError($errors[0]);
+            }
+
+            return true;
+        }
+
+        if (count($params) === 1) {
+            return $this->doFileList($command, $options, $params);
+        }
+
+        if (isset($options['channel'])) {
+            if (!$reg->channelExists($options['channel'])) {
+                return $this->raiseError('Channel "' . $options['channel'] .'" does not exist');
+            }
+
+            $channel = $reg->channelName($options['channel']);
+        } else {
+            $channel = $this->config->get('default_channel');
+        }
+
+        $installed = $reg->packageInfo(null, null, $channel);
+        usort($installed, array(&$this, '_sortinfo'));
+
+        $data = array(
+            'caption' => 'Installed packages, channel ' .
+                $channel . ':',
+            'border' => true,
+            'headline' => array('Package', 'Version', 'State'),
+            'channel' => $channel,
+            );
+        if ($channelinfo) {
+            $data['headline'] = array('Channel', 'Package', 'Version', 'State');
+        }
+
+        if (count($installed) && !isset($data['data'])) {
+            $data['data'] = array();
+        }
+
+        foreach ($installed as $package) {
+            $pobj = $reg->getPackage(isset($package['package']) ?
+                                        $package['package'] : $package['name'], $channel);
+            if ($channelinfo) {
+                $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(),
+                                    $pobj->getState() ? $pobj->getState() : null);
+            } else {
+                $packageinfo = array($pobj->getPackage(), $pobj->getVersion(),
+                                    $pobj->getState() ? $pobj->getState() : null);
+            }
+            $data['data'][] = $packageinfo;
+        }
+
+        if (count($installed) === 0) {
+            if (!$channelinfo) {
+                $data = '(no packages installed from channel ' . $channel . ')';
+            } else {
+                $data = array(
+                    'caption' => 'Installed packages, channel ' .
+                        $channel . ':',
+                    'border' => true,
+                    'channel' => $channel,
+                    'data' => array(array('(no packages installed)')),
+                );
+            }
+        }
+
+        $this->ui->outputData($data, $command);
+        return true;
+    }
+
+    function doListAll($command, $options, $params)
+    {
+        // This duplicate code is deprecated over
+        // list --channelinfo, which gives identical
+        // output for list and list --allchannels.
+        $reg = &$this->config->getRegistry();
+        $installed = $reg->packageInfo(null, null, null);
+        foreach ($installed as $channel => $packages) {
+            usort($packages, array($this, '_sortinfo'));
+            $data = array(
+                'caption'  => 'Installed packages, channel ' . $channel . ':',
+                'border'   => true,
+                'headline' => array('Package', 'Version', 'State'),
+                'channel'  => $channel
+            );
+
+            foreach ($packages as $package) {
+                $p = isset($package['package']) ? $package['package'] : $package['name'];
+                $pobj = $reg->getPackage($p, $channel);
+                $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(),
+                                        $pobj->getState() ? $pobj->getState() : null);
+            }
+
+            // Adds a blank line after each section
+            $data['data'][] = array();
+
+            if (count($packages) === 0) {
+                $data = array(
+                    'caption' => 'Installed packages, channel ' . $channel . ':',
+                    'border' => true,
+                    'data' => array(array('(no packages installed)'), array()),
+                    'channel' => $channel
+                    );
+            }
+            $this->ui->outputData($data, $command);
+        }
+        return true;
+    }
+
+    function doFileList($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError('list-files expects 1 parameter');
+        }
+
+        $reg = &$this->config->getRegistry();
+        $fp = false;
+        if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) {
+            if ($fp) {
+                fclose($fp);
+            }
+
+            if (!class_exists('PEAR_PackageFile')) {
+                require_once 'PEAR/PackageFile.php';
+            }
+
+            $pkg = &new PEAR_PackageFile($this->config, $this->_debug);
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+            PEAR::staticPopErrorHandling();
+            $headings = array('Package File', 'Install Path');
+            $installed = false;
+        } else {
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($parsed)) {
+                return $this->raiseError($parsed);
+            }
+
+            $info = &$reg->getPackage($parsed['package'], $parsed['channel']);
+            $headings = array('Type', 'Install Path');
+            $installed = true;
+        }
+
+        if (PEAR::isError($info)) {
+            return $this->raiseError($info);
+        }
+
+        if ($info === null) {
+            return $this->raiseError("`$params[0]' not installed");
+        }
+
+        $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ?
+            $info->getFilelist() : $info->getContents();
+        if ($installed) {
+            $caption = 'Installed Files For ' . $params[0];
+        } else {
+            $caption = 'Contents of ' . basename($params[0]);
+        }
+
+        $data = array(
+            'caption' => $caption,
+            'border' => true,
+            'headline' => $headings);
+        if ($info->getPackagexmlVersion() == '1.0' || $installed) {
+            foreach ($list as $file => $att) {
+                if ($installed) {
+                    if (empty($att['installed_as'])) {
+                        continue;
+                    }
+                    $data['data'][] = array($att['role'], $att['installed_as']);
+                } else {
+                    if (isset($att['baseinstalldir']) && !in_array($att['role'],
+                          array('test', 'data', 'doc'))) {
+                        $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR .
+                            $file;
+                    } else {
+                        $dest = $file;
+                    }
+                    switch ($att['role']) {
+                        case 'test':
+                        case 'data':
+                        case 'doc':
+                            $role = $att['role'];
+                            if ($role == 'test') {
+                                $role .= 's';
+                            }
+                            $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR .
+                                $info->getPackage() . DIRECTORY_SEPARATOR . $dest;
+                            break;
+                        case 'php':
+                        default:
+                            $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR .
+                                $dest;
+                    }
+                    $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+                    $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+                                                    array(DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR),
+                                                    $dest);
+                    $file = preg_replace('!/+!', '/', $file);
+                    $data['data'][] = array($file, $dest);
+                }
+            }
+        } else { // package.xml 2.0, not installed
+            if (!isset($list['dir']['file'][0])) {
+                $list['dir']['file'] = array($list['dir']['file']);
+            }
+
+            foreach ($list['dir']['file'] as $att) {
+                $att = $att['attribs'];
+                $file = $att['name'];
+                $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config);
+                $role->setup($this, $info, $att, $file);
+                if (!$role->isInstallable()) {
+                    $dest = '(not installable)';
+                } else {
+                    $dest = $role->processInstallation($info, $att, $file, '');
+                    if (PEAR::isError($dest)) {
+                        $dest = '(Unknown role "' . $att['role'] . ')';
+                    } else {
+                        list(,, $dest) = $dest;
+                    }
+                }
+                $data['data'][] = array($file, $dest);
+            }
+        }
+
+        $this->ui->outputData($data, $command);
+        return true;
+    }
+
+    function doShellTest($command, $options, $params)
+    {
+        if (count($params) < 1) {
+            return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]');
+        }
+
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $reg = &$this->config->getRegistry();
+        $info = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+        if (PEAR::isError($info)) {
+            exit(1); // invalid package name
+        }
+
+        $package = $info['package'];
+        $channel = $info['channel'];
+        // "pear shell-test Foo"
+        if (!$reg->packageExists($package, $channel)) {
+            if ($channel == 'pecl.php.net') {
+                if ($reg->packageExists($package, 'pear.php.net')) {
+                    $channel = 'pear.php.net'; // magically change channels for extensions
+                }
+            }
+        }
+
+        if (count($params) === 1) {
+            if (!$reg->packageExists($package, $channel)) {
+                exit(1);
+            }
+            // "pear shell-test Foo 1.0"
+        } elseif (count($params) === 2) {
+            $v = $reg->packageInfo($package, 'version', $channel);
+            if (!$v || !version_compare("$v", "{$params[1]}", "ge")) {
+                exit(1);
+            }
+            // "pear shell-test Foo ge 1.0"
+        } elseif (count($params) === 3) {
+            $v = $reg->packageInfo($package, 'version', $channel);
+            if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) {
+                exit(1);
+            }
+        } else {
+            PEAR::staticPopErrorHandling();
+            $this->raiseError("$command: expects 1 to 3 parameters");
+            exit(1);
+        }
+    }
+
+    function doInfo($command, $options, $params)
+    {
+        if (count($params) !== 1) {
+            return $this->raiseError('pear info expects 1 parameter');
+        }
+
+        $info = $fp = false;
+        $reg = &$this->config->getRegistry();
+        if (is_file($params[0]) && !is_dir($params[0]) &&
+            (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))
+        ) {
+            if ($fp) {
+                fclose($fp);
+            }
+
+            if (!class_exists('PEAR_PackageFile')) {
+                require_once 'PEAR/PackageFile.php';
+            }
+
+            $pkg = &new PEAR_PackageFile($this->config, $this->_debug);
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($obj)) {
+                $uinfo = $obj->getUserInfo();
+                if (is_array($uinfo)) {
+                    foreach ($uinfo as $message) {
+                        if (is_array($message)) {
+                            $message = $message['message'];
+                        }
+                        $this->ui->outputData($message);
+                    }
+                }
+
+                return $this->raiseError($obj);
+            }
+
+            if ($obj->getPackagexmlVersion() != '1.0') {
+                return $this->_doInfo2($command, $options, $params, $obj, false);
+            }
+
+            $info = $obj->toArray();
+        } else {
+            $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+            if (PEAR::isError($parsed)) {
+                return $this->raiseError($parsed);
+            }
+
+            $package = $parsed['package'];
+            $channel = $parsed['channel'];
+            $info = $reg->packageInfo($package, null, $channel);
+            if (isset($info['old'])) {
+                $obj = $reg->getPackage($package, $channel);
+                return $this->_doInfo2($command, $options, $params, $obj, true);
+            }
+        }
+
+        if (PEAR::isError($info)) {
+            return $info;
+        }
+
+        if (empty($info)) {
+            $this->raiseError("No information found for `$params[0]'");
+            return;
+        }
+
+        unset($info['filelist']);
+        unset($info['dirtree']);
+        unset($info['changelog']);
+        if (isset($info['xsdversion'])) {
+            $info['package.xml version'] = $info['xsdversion'];
+            unset($info['xsdversion']);
+        }
+
+        if (isset($info['packagerversion'])) {
+            $info['packaged with PEAR version'] = $info['packagerversion'];
+            unset($info['packagerversion']);
+        }
+
+        $keys = array_keys($info);
+        $longtext = array('description', 'summary');
+        foreach ($keys as $key) {
+            if (is_array($info[$key])) {
+                switch ($key) {
+                    case 'maintainers': {
+                        $i = 0;
+                        $mstr = '';
+                        foreach ($info[$key] as $m) {
+                            if ($i++ > 0) {
+                                $mstr .= "\n";
+                            }
+                            $mstr .= $m['name'] . " <";
+                            if (isset($m['email'])) {
+                                $mstr .= $m['email'];
+                            } else {
+                                $mstr .= $m['handle'] . '@php.net';
+                            }
+                            $mstr .= "> ($m[role])";
+                        }
+                        $info[$key] = $mstr;
+                        break;
+                    }
+                    case 'release_deps': {
+                        $i = 0;
+                        $dstr = '';
+                        foreach ($info[$key] as $d) {
+                            if (isset($this->_deps_rel_trans[$d['rel']])) {
+                                $rel = $this->_deps_rel_trans[$d['rel']];
+                            } else {
+                                $rel = $d['rel'];
+                            }
+                            if (isset($this->_deps_type_trans[$d['type']])) {
+                                $type = ucfirst($this->_deps_type_trans[$d['type']]);
+                            } else {
+                                $type = $d['type'];
+                            }
+                            if (isset($d['name'])) {
+                                $name = $d['name'] . ' ';
+                            } else {
+                                $name = '';
+                            }
+                            if (isset($d['version'])) {
+                                $version = $d['version'] . ' ';
+                            } else {
+                                $version = '';
+                            }
+                            if (isset($d['optional']) && $d['optional'] == 'yes') {
+                                $optional = ' (optional)';
+                            } else {
+                                $optional = '';
+                            }
+                            $dstr .= "$type $name$rel $version$optional\n";
+                        }
+                        $info[$key] = $dstr;
+                        break;
+                    }
+                    case 'provides' : {
+                        $debug = $this->config->get('verbose');
+                        if ($debug < 2) {
+                            $pstr = 'Classes: ';
+                        } else {
+                            $pstr = '';
+                        }
+                        $i = 0;
+                        foreach ($info[$key] as $p) {
+                            if ($debug < 2 && $p['type'] != "class") {
+                                continue;
+                            }
+                            // Only print classes when verbosity mode is < 2
+                            if ($debug < 2) {
+                                if ($i++ > 0) {
+                                    $pstr .= ", ";
+                                }
+                                $pstr .= $p['name'];
+                            } else {
+                                if ($i++ > 0) {
+                                    $pstr .= "\n";
+                                }
+                                $pstr .= ucfirst($p['type']) . " " . $p['name'];
+                                if (isset($p['explicit']) && $p['explicit'] == 1) {
+                                    $pstr .= " (explicit)";
+                                }
+                            }
+                        }
+                        $info[$key] = $pstr;
+                        break;
+                    }
+                    case 'configure_options' : {
+                        foreach ($info[$key] as $i => $p) {
+                            $info[$key][$i] = array_map(null, array_keys($p), array_values($p));
+                            $info[$key][$i] = array_map(create_function('$a',
+                                'return join(" = ",$a);'), $info[$key][$i]);
+                            $info[$key][$i] = implode(', ', $info[$key][$i]);
+                        }
+                        $info[$key] = implode("\n", $info[$key]);
+                        break;
+                    }
+                    default: {
+                        $info[$key] = implode(", ", $info[$key]);
+                        break;
+                    }
+                }
+            }
+
+            if ($key == '_lastmodified') {
+                $hdate = date('Y-m-d', $info[$key]);
+                unset($info[$key]);
+                $info['Last Modified'] = $hdate;
+            } elseif ($key == '_lastversion') {
+                $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -';
+                unset($info[$key]);
+            } else {
+                $info[$key] = trim($info[$key]);
+                if (in_array($key, $longtext)) {
+                    $info[$key] = preg_replace('/  +/', ' ', $info[$key]);
+                }
+            }
+        }
+
+        $caption = 'About ' . $info['package'] . '-' . $info['version'];
+        $data = array(
+            'caption' => $caption,
+            'border' => true);
+        foreach ($info as $key => $value) {
+            $key = ucwords(trim(str_replace('_', ' ', $key)));
+            $data['data'][] = array($key, $value);
+        }
+        $data['raw'] = $info;
+
+        $this->ui->outputData($data, 'package-info');
+    }
+
+    /**
+     * @access private
+     */
+    function _doInfo2($command, $options, $params, &$obj, $installed)
+    {
+        $reg = &$this->config->getRegistry();
+        $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' .
+            $obj->getVersion();
+        $data = array(
+            'caption' => $caption,
+            'border' => true);
+        switch ($obj->getPackageType()) {
+            case 'php' :
+                $release = 'PEAR-style PHP-based Package';
+            break;
+            case 'extsrc' :
+                $release = 'PECL-style PHP extension (source code)';
+            break;
+            case 'zendextsrc' :
+                $release = 'PECL-style Zend extension (source code)';
+            break;
+            case 'extbin' :
+                $release = 'PECL-style PHP extension (binary)';
+            break;
+            case 'zendextbin' :
+                $release = 'PECL-style Zend extension (binary)';
+            break;
+            case 'bundle' :
+                $release = 'Package bundle (collection of packages)';
+            break;
+        }
+        $extends = $obj->getExtends();
+        $extends = $extends ?
+            $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage();
+        if ($src = $obj->getSourcePackage()) {
+            $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')';
+        }
+
+        $info = array(
+            'Release Type' => $release,
+            'Name' => $extends,
+            'Channel' => $obj->getChannel(),
+            'Summary' => preg_replace('/  +/', ' ', $obj->getSummary()),
+            'Description' => preg_replace('/  +/', ' ', $obj->getDescription()),
+            );
+        $info['Maintainers'] = '';
+        foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+            $leads = $obj->{"get{$role}s"}();
+            if (!$leads) {
+                continue;
+            }
+
+            if (isset($leads['active'])) {
+                $leads = array($leads);
+            }
+
+            foreach ($leads as $lead) {
+                if (!empty($info['Maintainers'])) {
+                    $info['Maintainers'] .= "\n";
+                }
+
+                $active = $lead['active'] == 'no' ? ', inactive' : '';
+                $info['Maintainers'] .= $lead['name'] . ' <';
+                $info['Maintainers'] .= $lead['email'] . "> ($role$active)";
+            }
+        }
+
+        $info['Release Date'] = $obj->getDate();
+        if ($time = $obj->getTime()) {
+            $info['Release Date'] .= ' ' . $time;
+        }
+
+        $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')';
+        $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')';
+        $info['License'] = $obj->getLicense();
+        $uri = $obj->getLicenseLocation();
+        if ($uri) {
+            if (isset($uri['uri'])) {
+                $info['License'] .= ' (' . $uri['uri'] . ')';
+            } else {
+                $extra = $obj->getInstalledLocation($info['filesource']);
+                if ($extra) {
+                    $info['License'] .= ' (' . $uri['filesource'] . ')';
+                }
+            }
+        }
+
+        $info['Release Notes'] = $obj->getNotes();
+        if ($compat = $obj->getCompatible()) {
+            if (!isset($compat[0])) {
+                $compat = array($compat);
+            }
+
+            $info['Compatible with'] = '';
+            foreach ($compat as $package) {
+                $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] .
+                    "\nVersions >= " . $package['min'] . ', <= ' . $package['max'];
+                if (isset($package['exclude'])) {
+                    if (is_array($package['exclude'])) {
+                        $package['exclude'] = implode(', ', $package['exclude']);
+                    }
+
+                    if (!isset($info['Not Compatible with'])) {
+                        $info['Not Compatible with'] = '';
+                    } else {
+                        $info['Not Compatible with'] .= "\n";
+                    }
+                    $info['Not Compatible with'] .= $package['channel'] . '/' .
+                        $package['name'] . "\nVersions " . $package['exclude'];
+                }
+            }
+        }
+
+        $usesrole = $obj->getUsesrole();
+        if ($usesrole) {
+            if (!isset($usesrole[0])) {
+                $usesrole = array($usesrole);
+            }
+
+            foreach ($usesrole as $roledata) {
+                if (isset($info['Uses Custom Roles'])) {
+                    $info['Uses Custom Roles'] .= "\n";
+                } else {
+                    $info['Uses Custom Roles'] = '';
+                }
+
+                if (isset($roledata['package'])) {
+                    $rolepackage = $reg->parsedPackageNameToString($roledata, true);
+                } else {
+                    $rolepackage = $roledata['uri'];
+                }
+                $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')';
+            }
+        }
+
+        $usestask = $obj->getUsestask();
+        if ($usestask) {
+            if (!isset($usestask[0])) {
+                $usestask = array($usestask);
+            }
+
+            foreach ($usestask as $taskdata) {
+                if (isset($info['Uses Custom Tasks'])) {
+                    $info['Uses Custom Tasks'] .= "\n";
+                } else {
+                    $info['Uses Custom Tasks'] = '';
+                }
+
+                if (isset($taskdata['package'])) {
+                    $taskpackage = $reg->parsedPackageNameToString($taskdata, true);
+                } else {
+                    $taskpackage = $taskdata['uri'];
+                }
+                $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')';
+            }
+        }
+
+        $deps = $obj->getDependencies();
+        $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min'];
+        if (isset($deps['required']['php']['max'])) {
+            $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n";
+        } else {
+            $info['Required Dependencies'] .= "\n";
+        }
+
+        if (isset($deps['required']['php']['exclude'])) {
+            if (!isset($info['Not Compatible with'])) {
+                $info['Not Compatible with'] = '';
+            } else {
+                $info['Not Compatible with'] .= "\n";
+            }
+
+            if (is_array($deps['required']['php']['exclude'])) {
+                $deps['required']['php']['exclude'] =
+                    implode(', ', $deps['required']['php']['exclude']);
+            }
+            $info['Not Compatible with'] .= "PHP versions\n  " .
+                $deps['required']['php']['exclude'];
+        }
+
+        $info['Required Dependencies'] .= 'PEAR installer version';
+        if (isset($deps['required']['pearinstaller']['max'])) {
+            $info['Required Dependencies'] .= 's ' .
+                $deps['required']['pearinstaller']['min'] . '-' .
+                $deps['required']['pearinstaller']['max'];
+        } else {
+            $info['Required Dependencies'] .= ' ' .
+                $deps['required']['pearinstaller']['min'] . ' or newer';
+        }
+
+        if (isset($deps['required']['pearinstaller']['exclude'])) {
+            if (!isset($info['Not Compatible with'])) {
+                $info['Not Compatible with'] = '';
+            } else {
+                $info['Not Compatible with'] .= "\n";
+            }
+
+            if (is_array($deps['required']['pearinstaller']['exclude'])) {
+                $deps['required']['pearinstaller']['exclude'] =
+                    implode(', ', $deps['required']['pearinstaller']['exclude']);
+            }
+            $info['Not Compatible with'] .= "PEAR installer\n  Versions " .
+                $deps['required']['pearinstaller']['exclude'];
+        }
+
+        foreach (array('Package', 'Extension') as $type) {
+            $index = strtolower($type);
+            if (isset($deps['required'][$index])) {
+                if (isset($deps['required'][$index]['name'])) {
+                    $deps['required'][$index] = array($deps['required'][$index]);
+                }
+
+                foreach ($deps['required'][$index] as $package) {
+                    if (isset($package['conflicts'])) {
+                        $infoindex = 'Not Compatible with';
+                        if (!isset($info['Not Compatible with'])) {
+                            $info['Not Compatible with'] = '';
+                        } else {
+                            $info['Not Compatible with'] .= "\n";
+                        }
+                    } else {
+                        $infoindex = 'Required Dependencies';
+                        $info[$infoindex] .= "\n";
+                    }
+
+                    if ($index == 'extension') {
+                        $name = $package['name'];
+                    } else {
+                        if (isset($package['channel'])) {
+                            $name = $package['channel'] . '/' . $package['name'];
+                        } else {
+                            $name = '__uri/' . $package['name'] . ' (static URI)';
+                        }
+                    }
+
+                    $info[$infoindex] .= "$type $name";
+                    if (isset($package['uri'])) {
+                        $info[$infoindex] .= "\n  Download URI: $package[uri]";
+                        continue;
+                    }
+
+                    if (isset($package['max']) && isset($package['min'])) {
+                        $info[$infoindex] .= " \n  Versions " .
+                            $package['min'] . '-' . $package['max'];
+                    } elseif (isset($package['min'])) {
+                        $info[$infoindex] .= " \n  Version " .
+                            $package['min'] . ' or newer';
+                    } elseif (isset($package['max'])) {
+                        $info[$infoindex] .= " \n  Version " .
+                            $package['max'] . ' or older';
+                    }
+
+                    if (isset($package['recommended'])) {
+                        $info[$infoindex] .= "\n  Recommended version: $package[recommended]";
+                    }
+
+                    if (isset($package['exclude'])) {
+                        if (!isset($info['Not Compatible with'])) {
+                            $info['Not Compatible with'] = '';
+                        } else {
+                            $info['Not Compatible with'] .= "\n";
+                        }
+
+                        if (is_array($package['exclude'])) {
+                            $package['exclude'] = implode(', ', $package['exclude']);
+                        }
+
+                        $package['package'] = $package['name']; // for parsedPackageNameToString
+                         if (isset($package['conflicts'])) {
+                            $info['Not Compatible with'] .= '=> except ';
+                        }
+                       $info['Not Compatible with'] .= 'Package ' .
+                            $reg->parsedPackageNameToString($package, true);
+                        $info['Not Compatible with'] .= "\n  Versions " . $package['exclude'];
+                    }
+                }
+            }
+        }
+
+        if (isset($deps['required']['os'])) {
+            if (isset($deps['required']['os']['name'])) {
+                $dep['required']['os']['name'] = array($dep['required']['os']['name']);
+            }
+
+            foreach ($dep['required']['os'] as $os) {
+                if (isset($os['conflicts']) && $os['conflicts'] == 'yes') {
+                    if (!isset($info['Not Compatible with'])) {
+                        $info['Not Compatible with'] = '';
+                    } else {
+                        $info['Not Compatible with'] .= "\n";
+                    }
+                    $info['Not Compatible with'] .= "$os[name] Operating System";
+                } else {
+                    $info['Required Dependencies'] .= "\n";
+                    $info['Required Dependencies'] .= "$os[name] Operating System";
+                }
+            }
+        }
+
+        if (isset($deps['required']['arch'])) {
+            if (isset($deps['required']['arch']['pattern'])) {
+                $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']);
+            }
+
+            foreach ($dep['required']['arch'] as $os) {
+                if (isset($os['conflicts']) && $os['conflicts'] == 'yes') {
+                    if (!isset($info['Not Compatible with'])) {
+                        $info['Not Compatible with'] = '';
+                    } else {
+                        $info['Not Compatible with'] .= "\n";
+                    }
+                    $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'";
+                } else {
+                    $info['Required Dependencies'] .= "\n";
+                    $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'";
+                }
+            }
+        }
+
+        if (isset($deps['optional'])) {
+            foreach (array('Package', 'Extension') as $type) {
+                $index = strtolower($type);
+                if (isset($deps['optional'][$index])) {
+                    if (isset($deps['optional'][$index]['name'])) {
+                        $deps['optional'][$index] = array($deps['optional'][$index]);
+                    }
+
+                    foreach ($deps['optional'][$index] as $package) {
+                        if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+                            $infoindex = 'Not Compatible with';
+                            if (!isset($info['Not Compatible with'])) {
+                                $info['Not Compatible with'] = '';
+                            } else {
+                                $info['Not Compatible with'] .= "\n";
+                            }
+                        } else {
+                            $infoindex = 'Optional Dependencies';
+                            if (!isset($info['Optional Dependencies'])) {
+                                $info['Optional Dependencies'] = '';
+                            } else {
+                                $info['Optional Dependencies'] .= "\n";
+                            }
+                        }
+
+                        if ($index == 'extension') {
+                            $name = $package['name'];
+                        } else {
+                            if (isset($package['channel'])) {
+                                $name = $package['channel'] . '/' . $package['name'];
+                            } else {
+                                $name = '__uri/' . $package['name'] . ' (static URI)';
+                            }
+                        }
+
+                        $info[$infoindex] .= "$type $name";
+                        if (isset($package['uri'])) {
+                            $info[$infoindex] .= "\n  Download URI: $package[uri]";
+                            continue;
+                        }
+
+                        if ($infoindex == 'Not Compatible with') {
+                            // conflicts is only used to say that all versions conflict
+                            continue;
+                        }
+
+                        if (isset($package['max']) && isset($package['min'])) {
+                            $info[$infoindex] .= " \n  Versions " .
+                                $package['min'] . '-' . $package['max'];
+                        } elseif (isset($package['min'])) {
+                            $info[$infoindex] .= " \n  Version " .
+                                $package['min'] . ' or newer';
+                        } elseif (isset($package['max'])) {
+                            $info[$infoindex] .= " \n  Version " .
+                                $package['min'] . ' or older';
+                        }
+
+                        if (isset($package['recommended'])) {
+                            $info[$infoindex] .= "\n  Recommended version: $package[recommended]";
+                        }
+
+                        if (isset($package['exclude'])) {
+                            if (!isset($info['Not Compatible with'])) {
+                                $info['Not Compatible with'] = '';
+                            } else {
+                                $info['Not Compatible with'] .= "\n";
+                            }
+
+                            if (is_array($package['exclude'])) {
+                                $package['exclude'] = implode(', ', $package['exclude']);
+                            }
+
+                            $info['Not Compatible with'] .= "Package $package\n  Versions " .
+                                $package['exclude'];
+                        }
+                    }
+                }
+            }
+        }
+
+        if (isset($deps['group'])) {
+            if (!isset($deps['group'][0])) {
+                $deps['group'] = array($deps['group']);
+            }
+
+            foreach ($deps['group'] as $group) {
+                $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint'];
+                $groupindex = $group['attribs']['name'] . ' Contents';
+                $info[$groupindex] = '';
+                foreach (array('Package', 'Extension') as $type) {
+                    $index = strtolower($type);
+                    if (isset($group[$index])) {
+                        if (isset($group[$index]['name'])) {
+                            $group[$index] = array($group[$index]);
+                        }
+
+                        foreach ($group[$index] as $package) {
+                            if (!empty($info[$groupindex])) {
+                                $info[$groupindex] .= "\n";
+                            }
+
+                            if ($index == 'extension') {
+                                $name = $package['name'];
+                            } else {
+                                if (isset($package['channel'])) {
+                                    $name = $package['channel'] . '/' . $package['name'];
+                                } else {
+                                    $name = '__uri/' . $package['name'] . ' (static URI)';
+                                }
+                            }
+
+                            if (isset($package['uri'])) {
+                                if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+                                    $info[$groupindex] .= "Not Compatible with $type $name";
+                                } else {
+                                    $info[$groupindex] .= "$type $name";
+                                }
+
+                                $info[$groupindex] .= "\n  Download URI: $package[uri]";
+                                continue;
+                            }
+
+                            if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+                                $info[$groupindex] .= "Not Compatible with $type $name";
+                                continue;
+                            }
+
+                            $info[$groupindex] .= "$type $name";
+                            if (isset($package['max']) && isset($package['min'])) {
+                                $info[$groupindex] .= " \n  Versions " .
+                                    $package['min'] . '-' . $package['max'];
+                            } elseif (isset($package['min'])) {
+                                $info[$groupindex] .= " \n  Version " .
+                                    $package['min'] . ' or newer';
+                            } elseif (isset($package['max'])) {
+                                $info[$groupindex] .= " \n  Version " .
+                                    $package['min'] . ' or older';
+                            }
+
+                            if (isset($package['recommended'])) {
+                                $info[$groupindex] .= "\n  Recommended version: $package[recommended]";
+                            }
+
+                            if (isset($package['exclude'])) {
+                                if (!isset($info['Not Compatible with'])) {
+                                    $info['Not Compatible with'] = '';
+                                } else {
+                                    $info[$groupindex] .= "Not Compatible with\n";
+                                }
+
+                                if (is_array($package['exclude'])) {
+                                    $package['exclude'] = implode(', ', $package['exclude']);
+                                }
+                                $info[$groupindex] .= "  Package $package\n  Versions " .
+                                    $package['exclude'];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if ($obj->getPackageType() == 'bundle') {
+            $info['Bundled Packages'] = '';
+            foreach ($obj->getBundledPackages() as $package) {
+                if (!empty($info['Bundled Packages'])) {
+                    $info['Bundled Packages'] .= "\n";
+                }
+
+                if (isset($package['uri'])) {
+                    $info['Bundled Packages'] .= '__uri/' . $package['name'];
+                    $info['Bundled Packages'] .= "\n  (URI: $package[uri]";
+                } else {
+                    $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name'];
+                }
+            }
+        }
+
+        $info['package.xml version'] = '2.0';
+        if ($installed) {
+            if ($obj->getLastModified()) {
+                $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified());
+            }
+
+            $v = $obj->getLastInstalledVersion();
+            $info['Previous Installed Version'] = $v ? $v : '- None -';
+        }
+
+        foreach ($info as $key => $value) {
+            $data['data'][] = array($key, $value);
+        }
+
+        $data['raw'] = $obj->getArray(); // no validation needed
+        $this->ui->outputData($data, 'package-info');
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Registry.xml b/WEB-INF/lib/pear/PEAR/Command/Registry.xml
new file mode 100644 (file)
index 0000000..9f4e214
--- /dev/null
@@ -0,0 +1,58 @@
+<commands version="1.0">
+ <list>
+  <summary>List Installed Packages In The Default Channel</summary>
+  <function>doList</function>
+  <shortcut>l</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>list installed packages from this channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+   <allchannels>
+    <shortopt>a</shortopt>
+    <doc>list installed packages from all channels</doc>
+   </allchannels>
+   <channelinfo>
+    <shortopt>i</shortopt>
+    <doc>output fully channel-aware data, even on failure</doc>
+   </channelinfo>
+  </options>
+  <doc>&lt;package&gt;
+If invoked without parameters, this command lists the PEAR packages
+installed in your php_dir ({config php_dir}).  With a parameter, it
+lists the files in a package.
+</doc>
+ </list>
+ <list-files>
+  <summary>List Files In Installed Package</summary>
+  <function>doFileList</function>
+  <shortcut>fl</shortcut>
+  <options />
+  <doc>&lt;package&gt;
+List the files in an installed package.
+</doc>
+ </list-files>
+ <shell-test>
+  <summary>Shell Script Test</summary>
+  <function>doShellTest</function>
+  <shortcut>st</shortcut>
+  <options />
+  <doc>&lt;package&gt; [[relation] version]
+Tests if a package is installed in the system. Will exit(1) if it is not.
+   &lt;relation&gt;   The version comparison operator. One of:
+                &lt;, lt, &lt;=, le, &gt;, gt, &gt;=, ge, ==, =, eq, !=, &lt;&gt;, ne
+   &lt;version&gt;    The version to compare with
+</doc>
+ </shell-test>
+ <info>
+  <summary>Display information about a package</summary>
+  <function>doInfo</function>
+  <shortcut>in</shortcut>
+  <options />
+  <doc>&lt;package&gt;
+Displays information about a package. The package argument may be a
+local package file, an URL to a package file, or the name of an
+installed package.</doc>
+ </info>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Remote.php b/WEB-INF/lib/pear/PEAR/Command/Remote.php
new file mode 100644 (file)
index 0000000..74478d8
--- /dev/null
@@ -0,0 +1,810 @@
+<?php
+/**
+ * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download,
+ * clear-cache commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Remote.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+require_once 'PEAR/REST.php';
+
+/**
+ * PEAR commands for remote server querying
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Command_Remote extends PEAR_Command_Common
+{
+    var $commands = array(
+        'remote-info' => array(
+            'summary' => 'Information About Remote Packages',
+            'function' => 'doRemoteInfo',
+            'shortcut' => 'ri',
+            'options' => array(),
+            'doc' => '<package>
+Get details on a package from the server.',
+            ),
+        'list-upgrades' => array(
+            'summary' => 'List Available Upgrades',
+            'function' => 'doListUpgrades',
+            'shortcut' => 'lu',
+            'options' => array(
+                'channelinfo' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'output fully channel-aware data, even on failure',
+                    ),
+            ),
+            'doc' => '[preferred_state]
+List releases on the server of packages you have installed where
+a newer version is available with the same release state (stable etc.)
+or the state passed as the second parameter.'
+            ),
+        'remote-list' => array(
+            'summary' => 'List Remote Packages',
+            'function' => 'doRemoteList',
+            'shortcut' => 'rl',
+            'options' => array(
+                'channel' =>
+                    array(
+                    'shortopt' => 'c',
+                    'doc' => 'specify a channel other than the default channel',
+                    'arg' => 'CHAN',
+                    )
+                ),
+            'doc' => '
+Lists the packages available on the configured server along with the
+latest stable release of each package.',
+            ),
+        'search' => array(
+            'summary' => 'Search remote package database',
+            'function' => 'doSearch',
+            'shortcut' => 'sp',
+            'options' => array(
+                'channel' =>
+                    array(
+                    'shortopt' => 'c',
+                    'doc' => 'specify a channel other than the default channel',
+                    'arg' => 'CHAN',
+                    ),
+                'allchannels' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'search packages from all known channels',
+                    ),
+                'channelinfo' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'output fully channel-aware data, even on failure',
+                    ),
+                ),
+            'doc' => '[packagename] [packageinfo]
+Lists all packages which match the search parameters.  The first
+parameter is a fragment of a packagename.  The default channel
+will be used unless explicitly overridden.  The second parameter
+will be used to match any portion of the summary/description',
+            ),
+        'list-all' => array(
+            'summary' => 'List All Packages',
+            'function' => 'doListAll',
+            'shortcut' => 'la',
+            'options' => array(
+                'channel' =>
+                    array(
+                    'shortopt' => 'c',
+                    'doc' => 'specify a channel other than the default channel',
+                    'arg' => 'CHAN',
+                    ),
+                'channelinfo' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'output fully channel-aware data, even on failure',
+                    ),
+                ),
+            'doc' => '
+Lists the packages available on the configured server along with the
+latest stable release of each package.',
+            ),
+        'download' => array(
+            'summary' => 'Download Package',
+            'function' => 'doDownload',
+            'shortcut' => 'd',
+            'options' => array(
+                'nocompress' => array(
+                    'shortopt' => 'Z',
+                    'doc' => 'download an uncompressed (.tar) file',
+                    ),
+                ),
+            'doc' => '<package>...
+Download package tarballs.  The files will be named as suggested by the
+server, for example if you download the DB package and the latest stable
+version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.',
+            ),
+        'clear-cache' => array(
+            'summary' => 'Clear Web Services Cache',
+            'function' => 'doClearCache',
+            'shortcut' => 'cc',
+            'options' => array(),
+            'doc' => '
+Clear the REST cache. See also the cache_ttl configuration
+parameter.
+',
+            ),
+        );
+
+    /**
+     * PEAR_Command_Remote constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Remote(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function _checkChannelForStatus($channel, $chan)
+    {
+        if (PEAR::isError($chan)) {
+            $this->raiseError($chan);
+        }
+        if (!is_a($chan, 'PEAR_ChannelFile')) {
+            return $this->raiseError('Internal corruption error: invalid channel "' .
+                $channel . '"');
+        }
+        $rest = new PEAR_REST($this->config);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $mirror = $this->config->get('preferred_mirror', null,
+                                     $channel);
+        $a = $rest->downloadHttp('http://' . $channel .
+            '/channel.xml', $chan->lastModified());
+        PEAR::staticPopErrorHandling();
+        if (!PEAR::isError($a) && $a) {
+            $this->ui->outputData('WARNING: channel "' . $channel . '" has ' .
+                'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel .
+                '" to update');
+        }
+    }
+
+    function doRemoteInfo($command, $options, $params)
+    {
+        if (sizeof($params) != 1) {
+            return $this->raiseError("$command expects one param: the remote package name");
+        }
+        $savechannel = $channel = $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        $package = $params[0];
+        $parsed = $reg->parsePackageName($package, $channel);
+        if (PEAR::isError($parsed)) {
+            return $this->raiseError('Invalid package name "' . $package . '"');
+        }
+
+        $channel = $parsed['channel'];
+        $this->config->set('default_channel', $channel);
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+            return $e;
+        }
+
+        $mirror = $this->config->get('preferred_mirror');
+        if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) {
+            $rest = &$this->config->getREST('1.0', array());
+            $info = $rest->packageInfo($base, $parsed['package'], $channel);
+        }
+
+        if (!isset($info)) {
+            return $this->raiseError('No supported protocol was found');
+        }
+
+        if (PEAR::isError($info)) {
+            $this->config->set('default_channel', $savechannel);
+            return $this->raiseError($info);
+        }
+
+        if (!isset($info['name'])) {
+            return $this->raiseError('No remote package "' . $package . '" was found');
+        }
+
+        $installed = $reg->packageInfo($info['name'], null, $channel);
+        $info['installed'] = $installed['version'] ? $installed['version'] : '- no -';
+        if (is_array($info['installed'])) {
+            $info['installed'] = $info['installed']['release'];
+        }
+
+        $this->ui->outputData($info, $command);
+        $this->config->set('default_channel', $savechannel);
+
+        return true;
+    }
+
+    function doRemoteList($command, $options, $params)
+    {
+        $savechannel = $channel = $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        if (isset($options['channel'])) {
+            $channel = $options['channel'];
+            if (!$reg->channelExists($channel)) {
+                return $this->raiseError('Channel "' . $channel . '" does not exist');
+            }
+
+            $this->config->set('default_channel', $channel);
+        }
+
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+            return $e;
+        }
+
+        $list_options = false;
+        if ($this->config->get('preferred_state') == 'stable') {
+            $list_options = true;
+        }
+
+        $available = array();
+        if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))
+        ) {
+            // use faster list-all if available
+            $rest = &$this->config->getREST('1.1', array());
+            $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
+        } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+            $rest = &$this->config->getREST('1.0', array());
+            $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
+        }
+
+        if (PEAR::isError($available)) {
+            $this->config->set('default_channel', $savechannel);
+            return $this->raiseError($available);
+        }
+
+        $i = $j = 0;
+        $data = array(
+            'caption' => 'Channel ' . $channel . ' Available packages:',
+            'border' => true,
+            'headline' => array('Package', 'Version'),
+            'channel' => $channel
+            );
+
+        if (count($available) == 0) {
+            $data = '(no packages available yet)';
+        } else {
+            foreach ($available as $name => $info) {
+                $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-';
+                $data['data'][] = array($name, $version);
+            }
+        }
+        $this->ui->outputData($data, $command);
+        $this->config->set('default_channel', $savechannel);
+        return true;
+    }
+
+    function doListAll($command, $options, $params)
+    {
+        $savechannel = $channel = $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        if (isset($options['channel'])) {
+            $channel = $options['channel'];
+            if (!$reg->channelExists($channel)) {
+                return $this->raiseError("Channel \"$channel\" does not exist");
+            }
+
+            $this->config->set('default_channel', $channel);
+        }
+
+        $list_options = false;
+        if ($this->config->get('preferred_state') == 'stable') {
+            $list_options = true;
+        }
+
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+            return $e;
+        }
+
+        if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) {
+            // use faster list-all if available
+            $rest = &$this->config->getREST('1.1', array());
+            $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
+        } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+            $rest = &$this->config->getREST('1.0', array());
+            $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
+        }
+
+        if (PEAR::isError($available)) {
+            $this->config->set('default_channel', $savechannel);
+            return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")');
+        }
+
+        $data = array(
+            'caption' => 'All packages [Channel ' . $channel . ']:',
+            'border' => true,
+            'headline' => array('Package', 'Latest', 'Local'),
+            'channel' => $channel,
+            );
+
+        if (isset($options['channelinfo'])) {
+            // add full channelinfo
+            $data['caption'] = 'Channel ' . $channel . ' All packages:';
+            $data['headline'] = array('Channel', 'Package', 'Latest', 'Local',
+                'Description', 'Dependencies');
+        }
+        $local_pkgs = $reg->listPackages($channel);
+
+        foreach ($available as $name => $info) {
+            $installed = $reg->packageInfo($name, null, $channel);
+            if (is_array($installed['version'])) {
+                $installed['version'] = $installed['version']['release'];
+            }
+            $desc = $info['summary'];
+            if (isset($params[$name])) {
+                $desc .= "\n\n".$info['description'];
+            }
+            if (isset($options['mode']))
+            {
+                if ($options['mode'] == 'installed' && !isset($installed['version'])) {
+                    continue;
+                }
+                if ($options['mode'] == 'notinstalled' && isset($installed['version'])) {
+                    continue;
+                }
+                if ($options['mode'] == 'upgrades'
+                      && (!isset($installed['version']) || version_compare($installed['version'],
+                      $info['stable'], '>='))) {
+                    continue;
+                }
+            }
+            $pos = array_search(strtolower($name), $local_pkgs);
+            if ($pos !== false) {
+                unset($local_pkgs[$pos]);
+            }
+
+            if (isset($info['stable']) && !$info['stable']) {
+                $info['stable'] = null;
+            }
+
+            if (isset($options['channelinfo'])) {
+                // add full channelinfo
+                if ($info['stable'] === $info['unstable']) {
+                    $state = $info['state'];
+                } else {
+                    $state = 'stable';
+                }
+                $latest = $info['stable'].' ('.$state.')';
+                $local = '';
+                if (isset($installed['version'])) {
+                    $inst_state = $reg->packageInfo($name, 'release_state', $channel);
+                    $local = $installed['version'].' ('.$inst_state.')';
+                }
+
+                $packageinfo = array(
+                    $channel,
+                    $name,
+                    $latest,
+                    $local,
+                    isset($desc) ? $desc : null,
+                    isset($info['deps']) ? $info['deps'] : null,
+                );
+            } else {
+                $packageinfo = array(
+                    $reg->channelAlias($channel) . '/' . $name,
+                    isset($info['stable']) ? $info['stable'] : null,
+                    isset($installed['version']) ? $installed['version'] : null,
+                    isset($desc) ? $desc : null,
+                    isset($info['deps']) ? $info['deps'] : null,
+                );
+            }
+            $data['data'][$info['category']][] = $packageinfo;
+        }
+
+        if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) {
+            $this->config->set('default_channel', $savechannel);
+            $this->ui->outputData($data, $command);
+            return true;
+        }
+
+        foreach ($local_pkgs as $name) {
+            $info = &$reg->getPackage($name, $channel);
+            $data['data']['Local'][] = array(
+                $reg->channelAlias($channel) . '/' . $info->getPackage(),
+                '',
+                $info->getVersion(),
+                $info->getSummary(),
+                $info->getDeps()
+                );
+        }
+
+        $this->config->set('default_channel', $savechannel);
+        $this->ui->outputData($data, $command);
+        return true;
+    }
+
+    function doSearch($command, $options, $params)
+    {
+        if ((!isset($params[0]) || empty($params[0]))
+            && (!isset($params[1]) || empty($params[1])))
+        {
+            return $this->raiseError('no valid search string supplied');
+        }
+
+        $channelinfo = isset($options['channelinfo']);
+        $reg = &$this->config->getRegistry();
+        if (isset($options['allchannels'])) {
+            // search all channels
+            unset($options['allchannels']);
+            $channels = $reg->getChannels();
+            $errors = array();
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            foreach ($channels as $channel) {
+                if ($channel->getName() != '__uri') {
+                    $options['channel'] = $channel->getName();
+                    $ret = $this->doSearch($command, $options, $params);
+                    if (PEAR::isError($ret)) {
+                        $errors[] = $ret;
+                    }
+                }
+            }
+
+            PEAR::staticPopErrorHandling();
+            if (count($errors) !== 0) {
+                // for now, only give first error
+                return PEAR::raiseError($errors[0]);
+            }
+
+            return true;
+        }
+
+        $savechannel = $channel = $this->config->get('default_channel');
+        $package = strtolower($params[0]);
+        $summary = isset($params[1]) ? $params[1] : false;
+        if (isset($options['channel'])) {
+            $reg = &$this->config->getRegistry();
+            $channel = $options['channel'];
+            if (!$reg->channelExists($channel)) {
+                return $this->raiseError('Channel "' . $channel . '" does not exist');
+            }
+
+            $this->config->set('default_channel', $channel);
+        }
+
+        $chan = $reg->getChannel($channel);
+        if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+            return $e;
+        }
+
+        if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+              $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+            $rest = &$this->config->getREST('1.0', array());
+            $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName());
+        }
+
+        if (PEAR::isError($available)) {
+            $this->config->set('default_channel', $savechannel);
+            return $this->raiseError($available);
+        }
+
+        if (!$available && !$channelinfo) {
+            // clean exit when not found, no error !
+            $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.';
+            $this->ui->outputData($data);
+            $this->config->set('default_channel', $channel);
+            return true;
+        }
+
+        if ($channelinfo) {
+            $data = array(
+                'caption' => 'Matched packages, channel ' . $channel . ':',
+                'border' => true,
+                'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'),
+                'channel' => $channel
+                );
+        } else {
+            $data = array(
+                'caption' => 'Matched packages, channel ' . $channel . ':',
+                'border' => true,
+                'headline' => array('Package', 'Stable/(Latest)', 'Local'),
+                'channel' => $channel
+                );
+        }
+
+        if (!$available && $channelinfo) {
+            unset($data['headline']);
+            $data['data'] = 'No packages found that match pattern "' . $package . '".';
+            $available = array();
+        }
+
+        foreach ($available as $name => $info) {
+            $installed = $reg->packageInfo($name, null, $channel);
+            $desc = $info['summary'];
+            if (isset($params[$name]))
+                $desc .= "\n\n".$info['description'];
+
+            if (!isset($info['stable']) || !$info['stable']) {
+                $version_remote = 'none';
+            } else {
+                if ($info['unstable']) {
+                    $version_remote = $info['unstable'];
+                } else {
+                    $version_remote = $info['stable'];
+                }
+                $version_remote .= ' ('.$info['state'].')';
+            }
+            $version = is_array($installed['version']) ? $installed['version']['release'] :
+                $installed['version'];
+            if ($channelinfo) {
+                $packageinfo = array(
+                    $channel,
+                    $name,
+                    $version_remote,
+                    $version,
+                    $desc,
+                );
+            } else {
+                $packageinfo = array(
+                    $name,
+                    $version_remote,
+                    $version,
+                    $desc,
+                );
+            }
+            $data['data'][$info['category']][] = $packageinfo;
+        }
+
+        $this->ui->outputData($data, $command);
+        $this->config->set('default_channel', $channel);
+        return true;
+    }
+
+    function &getDownloader($options)
+    {
+        if (!class_exists('PEAR_Downloader')) {
+            require_once 'PEAR/Downloader.php';
+        }
+        $a = &new PEAR_Downloader($this->ui, $options, $this->config);
+        return $a;
+    }
+
+    function doDownload($command, $options, $params)
+    {
+        // make certain that dependencies are ignored
+        $options['downloadonly'] = 1;
+
+        // eliminate error messages for preferred_state-related errors
+        /* TODO: Should be an option, but until now download does respect
+           prefered state */
+        /* $options['ignorepreferred_state'] = 1; */
+        // eliminate error messages for preferred_state-related errors
+
+        $downloader = &$this->getDownloader($options);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $e = $downloader->setDownloadDir(getcwd());
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($e)) {
+            return $this->raiseError('Current directory is not writeable, cannot download');
+        }
+
+        $errors = array();
+        $downloaded = array();
+        $err = $downloader->download($params);
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+
+        $errors = $downloader->getErrorMsgs();
+        if (count($errors)) {
+            foreach ($errors as $error) {
+                if ($error !== null) {
+                    $this->ui->outputData($error);
+                }
+            }
+
+            return $this->raiseError("$command failed");
+        }
+
+        $downloaded = $downloader->getDownloadedPackages();
+        foreach ($downloaded as $pkg) {
+            $this->ui->outputData("File $pkg[file] downloaded", $command);
+        }
+
+        return true;
+    }
+
+    function downloadCallback($msg, $params = null)
+    {
+        if ($msg == 'done') {
+            $this->bytes_downloaded = $params;
+        }
+    }
+
+    function doListUpgrades($command, $options, $params)
+    {
+        require_once 'PEAR/Common.php';
+        if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) {
+            return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"');
+        }
+
+        $savechannel = $channel = $this->config->get('default_channel');
+        $reg = &$this->config->getRegistry();
+        foreach ($reg->listChannels() as $channel) {
+            $inst = array_flip($reg->listPackages($channel));
+            if (!count($inst)) {
+                continue;
+            }
+
+            if ($channel == '__uri') {
+                continue;
+            }
+
+            $this->config->set('default_channel', $channel);
+            $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0];
+
+            $caption = $channel . ' Available Upgrades';
+            $chan = $reg->getChannel($channel);
+            if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+                return $e;
+            }
+
+            $latest = array();
+            $base2  = false;
+            $preferred_mirror = $this->config->get('preferred_mirror');
+            if ($chan->supportsREST($preferred_mirror) &&
+                (
+                   //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
+                   ($base  = $chan->getBaseURL('REST1.0', $preferred_mirror))
+                )
+
+            ) {
+                if ($base2) {
+                    $rest = &$this->config->getREST('1.4', array());
+                    $base = $base2;
+                } else {
+                    $rest = &$this->config->getREST('1.0', array());
+                }
+
+                if (empty($state) || $state == 'any') {
+                    $state = false;
+                } else {
+                    $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
+                }
+
+                PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg);
+                PEAR::staticPopErrorHandling();
+            }
+
+            if (PEAR::isError($latest)) {
+                $this->ui->outputData($latest->getMessage());
+                continue;
+            }
+
+            $caption .= ':';
+            if (PEAR::isError($latest)) {
+                $this->config->set('default_channel', $savechannel);
+                return $latest;
+            }
+
+            $data = array(
+                'caption' => $caption,
+                'border' => 1,
+                'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'),
+                'channel' => $channel
+                );
+
+            foreach ((array)$latest as $pkg => $info) {
+                $package = strtolower($pkg);
+                if (!isset($inst[$package])) {
+                    // skip packages we don't have installed
+                    continue;
+                }
+
+                extract($info);
+                $inst_version = $reg->packageInfo($package, 'version', $channel);
+                $inst_state   = $reg->packageInfo($package, 'release_state', $channel);
+                if (version_compare("$version", "$inst_version", "le")) {
+                    // installed version is up-to-date
+                    continue;
+                }
+
+                if ($filesize >= 20480) {
+                    $filesize += 1024 - ($filesize % 1024);
+                    $fs = sprintf("%dkB", $filesize / 1024);
+                } elseif ($filesize > 0) {
+                    $filesize += 103 - ($filesize % 103);
+                    $fs = sprintf("%.1fkB", $filesize / 1024.0);
+                } else {
+                    $fs = "  -"; // XXX center instead
+                }
+
+                $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
+            }
+
+            if (isset($options['channelinfo'])) {
+                if (empty($data['data'])) {
+                    unset($data['headline']);
+                    if (count($inst) == 0) {
+                        $data['data'] = '(no packages installed)';
+                    } else {
+                        $data['data'] = '(no upgrades available)';
+                    }
+                }
+                $this->ui->outputData($data, $command);
+            } else {
+                if (empty($data['data'])) {
+                    $this->ui->outputData('Channel ' . $channel . ': No upgrades available');
+                } else {
+                    $this->ui->outputData($data, $command);
+                }
+            }
+        }
+
+        $this->config->set('default_channel', $savechannel);
+        return true;
+    }
+
+    function doClearCache($command, $options, $params)
+    {
+        $cache_dir = $this->config->get('cache_dir');
+        $verbose   = $this->config->get('verbose');
+        $output = '';
+        if (!file_exists($cache_dir) || !is_dir($cache_dir)) {
+            return $this->raiseError("$cache_dir does not exist or is not a directory");
+        }
+
+        if (!($dp = @opendir($cache_dir))) {
+            return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
+        }
+
+        if ($verbose >= 1) {
+            $output .= "reading directory $cache_dir\n";
+        }
+
+        $num = 0;
+        while ($ent = readdir($dp)) {
+            if (preg_match('/rest.cache(file|id)\\z/', $ent)) {
+                $path = $cache_dir . DIRECTORY_SEPARATOR . $ent;
+                if (file_exists($path)) {
+                    $ok = @unlink($path);
+                } else {
+                    $ok = false;
+                    $php_errormsg = '';
+                }
+
+                if ($ok) {
+                    if ($verbose >= 2) {
+                        $output .= "deleted $path\n";
+                    }
+                    $num++;
+                } elseif ($verbose >= 1) {
+                    $output .= "failed to delete $path $php_errormsg\n";
+                }
+            }
+        }
+
+        closedir($dp);
+        if ($verbose >= 1) {
+            $output .= "$num cache entries cleared\n";
+        }
+
+        $this->ui->outputData(rtrim($output), $command);
+        return $num;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Remote.xml b/WEB-INF/lib/pear/PEAR/Command/Remote.xml
new file mode 100644 (file)
index 0000000..b4f6100
--- /dev/null
@@ -0,0 +1,109 @@
+<commands version="1.0">
+ <remote-info>
+  <summary>Information About Remote Packages</summary>
+  <function>doRemoteInfo</function>
+  <shortcut>ri</shortcut>
+  <options />
+  <doc>&lt;package&gt;
+Get details on a package from the server.</doc>
+ </remote-info>
+ <list-upgrades>
+  <summary>List Available Upgrades</summary>
+  <function>doListUpgrades</function>
+  <shortcut>lu</shortcut>
+  <options>
+   <channelinfo>
+    <shortopt>i</shortopt>
+    <doc>output fully channel-aware data, even on failure</doc>
+   </channelinfo>
+  </options>
+  <doc>[preferred_state]
+List releases on the server of packages you have installed where
+a newer version is available with the same release state (stable etc.)
+or the state passed as the second parameter.</doc>
+ </list-upgrades>
+ <remote-list>
+  <summary>List Remote Packages</summary>
+  <function>doRemoteList</function>
+  <shortcut>rl</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>specify a channel other than the default channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+  </options>
+  <doc>
+Lists the packages available on the configured server along with the
+latest stable release of each package.</doc>
+ </remote-list>
+ <search>
+  <summary>Search remote package database</summary>
+  <function>doSearch</function>
+  <shortcut>sp</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>specify a channel other than the default channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+   <allchannels>
+    <shortopt>a</shortopt>
+    <doc>search packages from all known channels</doc>
+   </allchannels>
+   <channelinfo>
+    <shortopt>i</shortopt>
+    <doc>output fully channel-aware data, even on failure</doc>
+   </channelinfo>
+  </options>
+  <doc>[packagename] [packageinfo]
+Lists all packages which match the search parameters.  The first
+parameter is a fragment of a packagename.  The default channel
+will be used unless explicitly overridden.  The second parameter
+will be used to match any portion of the summary/description</doc>
+ </search>
+ <list-all>
+  <summary>List All Packages</summary>
+  <function>doListAll</function>
+  <shortcut>la</shortcut>
+  <options>
+   <channel>
+    <shortopt>c</shortopt>
+    <doc>specify a channel other than the default channel</doc>
+    <arg>CHAN</arg>
+   </channel>
+   <channelinfo>
+    <shortopt>i</shortopt>
+    <doc>output fully channel-aware data, even on failure</doc>
+   </channelinfo>
+  </options>
+  <doc>
+Lists the packages available on the configured server along with the
+latest stable release of each package.</doc>
+ </list-all>
+ <download>
+  <summary>Download Package</summary>
+  <function>doDownload</function>
+  <shortcut>d</shortcut>
+  <options>
+   <nocompress>
+    <shortopt>Z</shortopt>
+    <doc>download an uncompressed (.tar) file</doc>
+   </nocompress>
+  </options>
+  <doc>&lt;package&gt;...
+Download package tarballs.  The files will be named as suggested by the
+server, for example if you download the DB package and the latest stable
+version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.</doc>
+ </download>
+ <clear-cache>
+  <summary>Clear Web Services Cache</summary>
+  <function>doClearCache</function>
+  <shortcut>cc</shortcut>
+  <options />
+  <doc>
+Clear the XML-RPC/REST cache.  See also the cache_ttl configuration
+parameter.
+</doc>
+ </clear-cache>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Test.php b/WEB-INF/lib/pear/PEAR/Command/Test.php
new file mode 100644 (file)
index 0000000..a757d9e
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+/**
+ * PEAR_Command_Test (run-tests)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+
+class PEAR_Command_Test extends PEAR_Command_Common
+{
+    var $commands = array(
+        'run-tests' => array(
+            'summary' => 'Run Regression Tests',
+            'function' => 'doRunTests',
+            'shortcut' => 'rt',
+            'options' => array(
+                'recur' => array(
+                    'shortopt' => 'r',
+                    'doc' => 'Run tests in child directories, recursively.  4 dirs deep maximum',
+                ),
+                'ini' => array(
+                    'shortopt' => 'i',
+                    'doc' => 'actual string of settings to pass to php in format " -d setting=blah"',
+                    'arg' => 'SETTINGS'
+                ),
+                'realtimelog' => array(
+                    'shortopt' => 'l',
+                    'doc' => 'Log test runs/results as they are run',
+                ),
+                'quiet' => array(
+                    'shortopt' => 'q',
+                    'doc' => 'Only display detail for failed tests',
+                ),
+                'simple' => array(
+                    'shortopt' => 's',
+                    'doc' => 'Display simple output for all tests',
+                ),
+                'package' => array(
+                    'shortopt' => 'p',
+                    'doc' => 'Treat parameters as installed packages from which to run tests',
+                ),
+                'phpunit' => array(
+                    'shortopt' => 'u',
+                    'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests
+If none is found, all .phpt tests will be tried instead.',
+                ),
+                'tapoutput' => array(
+                    'shortopt' => 't',
+                    'doc' => 'Output run-tests.log in TAP-compliant format',
+                ),
+                'cgi' => array(
+                    'shortopt' => 'c',
+                    'doc' => 'CGI php executable (needed for tests with POST/GET section)',
+                    'arg' => 'PHPCGI',
+                ),
+                'coverage' => array(
+                    'shortopt' => 'x',
+                    'doc'      => 'Generate a code coverage report (requires Xdebug 2.0.0+)',
+                ),
+            ),
+            'doc' => '[testfile|dir ...]
+Run regression tests with PHP\'s regression testing script (run-tests.php).',
+            ),
+        );
+
+    var $output;
+
+    /**
+     * PEAR_Command_Test constructor.
+     *
+     * @access public
+     */
+    function PEAR_Command_Test(&$ui, &$config)
+    {
+        parent::PEAR_Command_Common($ui, $config);
+    }
+
+    function doRunTests($command, $options, $params)
+    {
+        if (isset($options['phpunit']) && isset($options['tapoutput'])) {
+            return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time');
+        }
+
+        require_once 'PEAR/Common.php';
+        require_once 'System.php';
+        $log = new PEAR_Common;
+        $log->ui = &$this->ui; // slightly hacky, but it will work
+        $tests = array();
+        $depth = isset($options['recur']) ? 14 : 1;
+
+        if (!count($params)) {
+            $params[] = '.';
+        }
+
+        if (isset($options['package'])) {
+            $oldparams = $params;
+            $params = array();
+            $reg = &$this->config->getRegistry();
+            foreach ($oldparams as $param) {
+                $pname = $reg->parsePackageName($param, $this->config->get('default_channel'));
+                if (PEAR::isError($pname)) {
+                    return $this->raiseError($pname);
+                }
+
+                $package = &$reg->getPackage($pname['package'], $pname['channel']);
+                if (!$package) {
+                    return PEAR::raiseError('Unknown package "' .
+                        $reg->parsedPackageNameToString($pname) . '"');
+                }
+
+                $filelist = $package->getFilelist();
+                foreach ($filelist as $name => $atts) {
+                    if (isset($atts['role']) && $atts['role'] != 'test') {
+                        continue;
+                    }
+
+                    if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) {
+                        $params[] = $atts['installed_as'];
+                        continue;
+                    } elseif (!preg_match('/\.phpt\\z/', $name)) {
+                        continue;
+                    }
+                    $params[] = $atts['installed_as'];
+                }
+            }
+        }
+
+        foreach ($params as $p) {
+            if (is_dir($p)) {
+                if (isset($options['phpunit'])) {
+                    $dir = System::find(array($p, '-type', 'f',
+                                                '-maxdepth', $depth,
+                                                '-name', 'AllTests.php'));
+                    if (count($dir)) {
+                        foreach ($dir as $p) {
+                            $p = realpath($p);
+                            if (!count($tests) ||
+                                  (count($tests) && strlen($p) < strlen($tests[0]))) {
+                                // this is in a higher-level directory, use this one instead.
+                                $tests = array($p);
+                            }
+                        }
+                    }
+                    continue;
+                }
+
+                $args  = array($p, '-type', 'f', '-name', '*.phpt');
+            } else {
+                if (isset($options['phpunit'])) {
+                    if (preg_match('/AllTests\.php\\z/i', $p)) {
+                        $p = realpath($p);
+                        if (!count($tests) ||
+                              (count($tests) && strlen($p) < strlen($tests[0]))) {
+                            // this is in a higher-level directory, use this one instead.
+                            $tests = array($p);
+                        }
+                    }
+                    continue;
+                }
+
+                if (file_exists($p) && preg_match('/\.phpt$/', $p)) {
+                    $tests[] = $p;
+                    continue;
+                }
+
+                if (!preg_match('/\.phpt\\z/', $p)) {
+                    $p .= '.phpt';
+                }
+
+                $args  = array(dirname($p), '-type', 'f', '-name', $p);
+            }
+
+            if (!isset($options['recur'])) {
+                $args[] = '-maxdepth';
+                $args[] = 1;
+            }
+
+            $dir   = System::find($args);
+            $tests = array_merge($tests, $dir);
+        }
+
+        $ini_settings = '';
+        if (isset($options['ini'])) {
+            $ini_settings .= $options['ini'];
+        }
+
+        if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) {
+            $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}";
+        }
+
+        if ($ini_settings) {
+            $this->ui->outputData('Using INI settings: "' . $ini_settings . '"');
+        }
+
+        $skipped = $passed = $failed = array();
+        $tests_count = count($tests);
+        $this->ui->outputData('Running ' . $tests_count . ' tests', $command);
+        $start = time();
+        if (isset($options['realtimelog']) && file_exists('run-tests.log')) {
+            unlink('run-tests.log');
+        }
+
+        if (isset($options['tapoutput'])) {
+            $tap = '1..' . $tests_count . "\n";
+        }
+
+        require_once 'PEAR/RunTest.php';
+        $run = new PEAR_RunTest($log, $options);
+        $run->tests_count = $tests_count;
+
+        if (isset($options['coverage']) && extension_loaded('xdebug')){
+            $run->xdebug_loaded = true;
+        } else {
+            $run->xdebug_loaded = false;
+        }
+
+        $j = $i = 1;
+        foreach ($tests as $t) {
+            if (isset($options['realtimelog'])) {
+                $fp = @fopen('run-tests.log', 'a');
+                if ($fp) {
+                    fwrite($fp, "Running test [$i / $tests_count] $t...");
+                    fclose($fp);
+                }
+            }
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            if (isset($options['phpunit'])) {
+                $result = $run->runPHPUnit($t, $ini_settings);
+            } else {
+                $result = $run->run($t, $ini_settings, $j);
+            }
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($result)) {
+                $this->ui->log($result->getMessage());
+                continue;
+            }
+
+            if (isset($options['tapoutput'])) {
+                $tap .= $result[0] . ' ' . $i . $result[1] . "\n";
+                continue;
+            }
+
+            if (isset($options['realtimelog'])) {
+                $fp = @fopen('run-tests.log', 'a');
+                if ($fp) {
+                    fwrite($fp, "$result\n");
+                    fclose($fp);
+                }
+            }
+
+            if ($result == 'FAILED') {
+                $failed[] = $t;
+            }
+            if ($result == 'PASSED') {
+                $passed[] = $t;
+            }
+            if ($result == 'SKIPPED') {
+                $skipped[] = $t;
+            }
+
+            $j++;
+        }
+
+        $total = date('i:s', time() - $start);
+        if (isset($options['tapoutput'])) {
+            $fp = @fopen('run-tests.log', 'w');
+            if ($fp) {
+                fwrite($fp, $tap, strlen($tap));
+                fclose($fp);
+                $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') .
+                    '"', $command);
+            }
+        } else {
+            if (count($failed)) {
+                $output = "TOTAL TIME: $total\n";
+                $output .= count($passed) . " PASSED TESTS\n";
+                $output .= count($skipped) . " SKIPPED TESTS\n";
+                $output .= count($failed) . " FAILED TESTS:\n";
+                foreach ($failed as $failure) {
+                    $output .= $failure . "\n";
+                }
+
+                $mode = isset($options['realtimelog']) ? 'a' : 'w';
+                $fp   = @fopen('run-tests.log', $mode);
+
+                if ($fp) {
+                    fwrite($fp, $output, strlen($output));
+                    fclose($fp);
+                    $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command);
+                }
+            } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) {
+                @unlink('run-tests.log');
+            }
+        }
+        $this->ui->outputData('TOTAL TIME: ' . $total);
+        $this->ui->outputData(count($passed) . ' PASSED TESTS', $command);
+        $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command);
+        if (count($failed)) {
+            $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command);
+            foreach ($failed as $failure) {
+                $this->ui->outputData($failure, $command);
+            }
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Command/Test.xml b/WEB-INF/lib/pear/PEAR/Command/Test.xml
new file mode 100644 (file)
index 0000000..bbe9fcc
--- /dev/null
@@ -0,0 +1,54 @@
+<commands version="1.0">
+ <run-tests>
+  <summary>Run Regression Tests</summary>
+  <function>doRunTests</function>
+  <shortcut>rt</shortcut>
+  <options>
+   <recur>
+    <shortopt>r</shortopt>
+    <doc>Run tests in child directories, recursively.  4 dirs deep maximum</doc>
+   </recur>
+   <ini>
+    <shortopt>i</shortopt>
+    <doc>actual string of settings to pass to php in format &quot; -d setting=blah&quot;</doc>
+    <arg>SETTINGS</arg>
+   </ini>
+   <realtimelog>
+    <shortopt>l</shortopt>
+    <doc>Log test runs/results as they are run</doc>
+   </realtimelog>
+   <quiet>
+    <shortopt>q</shortopt>
+    <doc>Only display detail for failed tests</doc>
+   </quiet>
+   <simple>
+    <shortopt>s</shortopt>
+    <doc>Display simple output for all tests</doc>
+   </simple>
+   <package>
+    <shortopt>p</shortopt>
+    <doc>Treat parameters as installed packages from which to run tests</doc>
+   </package>
+   <phpunit>
+    <shortopt>u</shortopt>
+    <doc>Search parameters for AllTests.php, and use that to run phpunit-based tests
+If none is found, all .phpt tests will be tried instead.</doc>
+   </phpunit>
+   <tapoutput>
+    <shortopt>t</shortopt>
+    <doc>Output run-tests.log in TAP-compliant format</doc>
+   </tapoutput>
+   <cgi>
+    <shortopt>c</shortopt>
+    <doc>CGI php executable (needed for tests with POST/GET section)</doc>
+    <arg>PHPCGI</arg>
+   </cgi>
+   <coverage>
+    <shortopt>x</shortopt>
+    <doc>Generate a code coverage report (requires Xdebug 2.0.0+)</doc>
+   </coverage>
+  </options>
+  <doc>[testfile|dir ...]
+Run regression tests with PHP&#039;s regression testing script (run-tests.php).</doc>
+ </run-tests>
+</commands>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Common.php b/WEB-INF/lib/pear/PEAR/Common.php
new file mode 100644 (file)
index 0000000..3a8c7e8
--- /dev/null
@@ -0,0 +1,837 @@
+<?php
+/**
+ * PEAR_Common, the base class for the PEAR Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1.0
+ * @deprecated File deprecated since Release 1.4.0a1
+ */
+
+/**
+ * Include error handling
+ */
+require_once 'PEAR.php';
+
+/**
+ * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
+ */
+define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
+define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
+define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/');
+
+// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
+define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?');
+define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i');
+
+// XXX far from perfect :-)
+define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG .
+    ')(-([.0-9a-zA-Z]+))?');
+define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG .
+    '\\z/');
+
+define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+');
+define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/');
+
+// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED
+define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*');
+define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i');
+
+define('_PEAR_CHANNELS_PACKAGE_PREG',  '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/('
+         . _PEAR_COMMON_PACKAGE_NAME_PREG . ')');
+define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i');
+
+define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::('
+    . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?');
+define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/');
+
+/**
+ * List of temporary files and directories registered by
+ * PEAR_Common::addTempFile().
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_tempfiles'] = array();
+
+/**
+ * Valid maintainer roles
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
+
+/**
+ * Valid release states
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
+
+/**
+ * Valid dependency types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
+
+/**
+ * Valid dependency relations
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne');
+
+/**
+ * Valid file roles
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
+
+/**
+ * Valid replacement types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
+
+/**
+ * Valid "provide" types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
+
+/**
+ * Valid "provide" types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup');
+
+/**
+ * Class providing common functionality for PEAR administration classes.
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ * @deprecated This class will disappear, and its components will be spread
+ *             into smaller classes, like the AT&T breakup, as of Release 1.4.0a1
+ */
+class PEAR_Common extends PEAR
+{
+    /**
+     * User Interface object (PEAR_Frontend_* class).  If null,
+     * the log() method uses print.
+     * @var object
+     */
+    var $ui = null;
+
+    /**
+     * Configuration object (PEAR_Config).
+     * @var PEAR_Config
+     */
+    var $config = null;
+
+    /** stack of elements, gives some sort of XML context */
+    var $element_stack = array();
+
+    /** name of currently parsed XML element */
+    var $current_element;
+
+    /** array of attributes of the currently parsed XML element */
+    var $current_attributes = array();
+
+    /** assoc with information about a package */
+    var $pkginfo = array();
+
+    var $current_path = null;
+
+    /**
+     * Flag variable used to mark a valid package file
+     * @var boolean
+     * @access private
+     */
+    var $_validPackageFile;
+
+    /**
+     * PEAR_Common constructor
+     *
+     * @access public
+     */
+    function PEAR_Common()
+    {
+        parent::PEAR();
+        $this->config = &PEAR_Config::singleton();
+        $this->debug = $this->config->get('verbose');
+    }
+
+    /**
+     * PEAR_Common destructor
+     *
+     * @access private
+     */
+    function _PEAR_Common()
+    {
+        // doesn't work due to bug #14744
+        //$tempfiles = $this->_tempfiles;
+        $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
+        while ($file = array_shift($tempfiles)) {
+            if (@is_dir($file)) {
+                if (!class_exists('System')) {
+                    require_once 'System.php';
+                }
+
+                System::rm(array('-rf', $file));
+            } elseif (file_exists($file)) {
+                unlink($file);
+            }
+        }
+    }
+
+    /**
+     * Register a temporary file or directory.  When the destructor is
+     * executed, all registered temporary files and directories are
+     * removed.
+     *
+     * @param string  $file  name of file or directory
+     *
+     * @return void
+     *
+     * @access public
+     */
+    function addTempFile($file)
+    {
+        if (!class_exists('PEAR_Frontend')) {
+            require_once 'PEAR/Frontend.php';
+        }
+        PEAR_Frontend::addTempFile($file);
+    }
+
+    /**
+     * Wrapper to System::mkDir(), creates a directory as well as
+     * any necessary parent directories.
+     *
+     * @param string  $dir  directory name
+     *
+     * @return bool TRUE on success, or a PEAR error
+     *
+     * @access public
+     */
+    function mkDirHier($dir)
+    {
+        // Only used in Installer - move it there ?
+        $this->log(2, "+ create dir $dir");
+        if (!class_exists('System')) {
+            require_once 'System.php';
+        }
+        return System::mkDir(array('-p', $dir));
+    }
+
+    /**
+     * Logging method.
+     *
+     * @param int    $level  log level (0 is quiet, higher is noisier)
+     * @param string $msg    message to write to the log
+     *
+     * @return void
+     *
+     * @access public
+     * @static
+     */
+    function log($level, $msg, $append_crlf = true)
+    {
+        if ($this->debug >= $level) {
+            if (!class_exists('PEAR_Frontend')) {
+                require_once 'PEAR/Frontend.php';
+            }
+
+            $ui = &PEAR_Frontend::singleton();
+            if (is_a($ui, 'PEAR_Frontend')) {
+                $ui->log($msg, $append_crlf);
+            } else {
+                print "$msg\n";
+            }
+        }
+    }
+
+    /**
+     * Create and register a temporary directory.
+     *
+     * @param string $tmpdir (optional) Directory to use as tmpdir.
+     *                       Will use system defaults (for example
+     *                       /tmp or c:\windows\temp) if not specified
+     *
+     * @return string name of created directory
+     *
+     * @access public
+     */
+    function mkTempDir($tmpdir = '')
+    {
+        $topt = $tmpdir ? array('-t', $tmpdir) : array();
+        $topt = array_merge($topt, array('-d', 'pear'));
+        if (!class_exists('System')) {
+            require_once 'System.php';
+        }
+
+        if (!$tmpdir = System::mktemp($topt)) {
+            return false;
+        }
+
+        $this->addTempFile($tmpdir);
+        return $tmpdir;
+    }
+
+    /**
+     * Set object that represents the frontend to be used.
+     *
+     * @param  object Reference of the frontend object
+     * @return void
+     * @access public
+     */
+    function setFrontendObject(&$ui)
+    {
+        $this->ui = &$ui;
+    }
+
+    /**
+     * Return an array containing all of the states that are more stable than
+     * or equal to the passed in state
+     *
+     * @param string Release state
+     * @param boolean Determines whether to include $state in the list
+     * @return false|array False if $state is not a valid release state
+     */
+    function betterStates($state, $include = false)
+    {
+        static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+        $i = array_search($state, $states);
+        if ($i === false) {
+            return false;
+        }
+        if ($include) {
+            $i--;
+        }
+        return array_slice($states, $i + 1);
+    }
+
+    /**
+     * Get the valid roles for a PEAR package maintainer
+     *
+     * @return array
+     * @static
+     */
+    function getUserRoles()
+    {
+        return $GLOBALS['_PEAR_Common_maintainer_roles'];
+    }
+
+    /**
+     * Get the valid package release states of packages
+     *
+     * @return array
+     * @static
+     */
+    function getReleaseStates()
+    {
+        return $GLOBALS['_PEAR_Common_release_states'];
+    }
+
+    /**
+     * Get the implemented dependency types (php, ext, pkg etc.)
+     *
+     * @return array
+     * @static
+     */
+    function getDependencyTypes()
+    {
+        return $GLOBALS['_PEAR_Common_dependency_types'];
+    }
+
+    /**
+     * Get the implemented dependency relations (has, lt, ge etc.)
+     *
+     * @return array
+     * @static
+     */
+    function getDependencyRelations()
+    {
+        return $GLOBALS['_PEAR_Common_dependency_relations'];
+    }
+
+    /**
+     * Get the implemented file roles
+     *
+     * @return array
+     * @static
+     */
+    function getFileRoles()
+    {
+        return $GLOBALS['_PEAR_Common_file_roles'];
+    }
+
+    /**
+     * Get the implemented file replacement types in
+     *
+     * @return array
+     * @static
+     */
+    function getReplacementTypes()
+    {
+        return $GLOBALS['_PEAR_Common_replacement_types'];
+    }
+
+    /**
+     * Get the implemented file replacement types in
+     *
+     * @return array
+     * @static
+     */
+    function getProvideTypes()
+    {
+        return $GLOBALS['_PEAR_Common_provide_types'];
+    }
+
+    /**
+     * Get the implemented file replacement types in
+     *
+     * @return array
+     * @static
+     */
+    function getScriptPhases()
+    {
+        return $GLOBALS['_PEAR_Common_script_phases'];
+    }
+
+    /**
+     * Test whether a string contains a valid package name.
+     *
+     * @param string $name the package name to test
+     *
+     * @return bool
+     *
+     * @access public
+     */
+    function validPackageName($name)
+    {
+        return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name);
+    }
+
+    /**
+     * Test whether a string contains a valid package version.
+     *
+     * @param string $ver the package version to test
+     *
+     * @return bool
+     *
+     * @access public
+     */
+    function validPackageVersion($ver)
+    {
+        return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
+    }
+
+    /**
+     * @param string $path relative or absolute include path
+     * @return boolean
+     * @static
+     */
+    function isIncludeable($path)
+    {
+        if (file_exists($path) && is_readable($path)) {
+            return true;
+        }
+
+        $ipath = explode(PATH_SEPARATOR, ini_get('include_path'));
+        foreach ($ipath as $include) {
+            $test = realpath($include . DIRECTORY_SEPARATOR . $path);
+            if (file_exists($test) && is_readable($test)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    function _postProcessChecks($pf)
+    {
+        if (!PEAR::isError($pf)) {
+            return $this->_postProcessValidPackagexml($pf);
+        }
+
+        $errs = $pf->getUserinfo();
+        if (is_array($errs)) {
+            foreach ($errs as $error) {
+                $e = $this->raiseError($error['message'], $error['code'], null, null, $error);
+            }
+        }
+
+        return $pf;
+    }
+
+    /**
+     * Returns information about a package file.  Expects the name of
+     * a gzipped tar file as input.
+     *
+     * @param string  $file  name of .tgz file
+     *
+     * @return array  array with package information
+     *
+     * @access public
+     * @deprecated use PEAR_PackageFile->fromTgzFile() instead
+     *
+     */
+    function infoFromTgzFile($file)
+    {
+        $packagefile = &new PEAR_PackageFile($this->config);
+        $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL);
+        return $this->_postProcessChecks($pf);
+    }
+
+    /**
+     * Returns information about a package file.  Expects the name of
+     * a package xml file as input.
+     *
+     * @param string  $descfile  name of package xml file
+     *
+     * @return array  array with package information
+     *
+     * @access public
+     * @deprecated use PEAR_PackageFile->fromPackageFile() instead
+     *
+     */
+    function infoFromDescriptionFile($descfile)
+    {
+        $packagefile = &new PEAR_PackageFile($this->config);
+        $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+        return $this->_postProcessChecks($pf);
+    }
+
+    /**
+     * Returns information about a package file.  Expects the contents
+     * of a package xml file as input.
+     *
+     * @param string  $data  contents of package.xml file
+     *
+     * @return array   array with package information
+     *
+     * @access public
+     * @deprecated use PEAR_PackageFile->fromXmlstring() instead
+     *
+     */
+    function infoFromString($data)
+    {
+        $packagefile = &new PEAR_PackageFile($this->config);
+        $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false);
+        return $this->_postProcessChecks($pf);
+    }
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @return array
+     */
+    function _postProcessValidPackagexml(&$pf)
+    {
+        if (!is_a($pf, 'PEAR_PackageFile_v2')) {
+            $this->pkginfo = $pf->toArray();
+            return $this->pkginfo;
+        }
+
+        // sort of make this into a package.xml 1.0-style array
+        // changelog is not converted to old format.
+        $arr = $pf->toArray(true);
+        $arr = array_merge($arr, $arr['old']);
+        unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'],
+              $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'],
+              $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'],
+              $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'],
+              $arr['helper'], $arr['contributor']);
+        $arr['filelist'] = $pf->getFilelist();
+        $this->pkginfo = $arr;
+        return $arr;
+    }
+
+    /**
+     * Returns package information from different sources
+     *
+     * This method is able to extract information about a package
+     * from a .tgz archive or from a XML package definition file.
+     *
+     * @access public
+     * @param  string Filename of the source ('package.xml', '<package>.tgz')
+     * @return string
+     * @deprecated use PEAR_PackageFile->fromAnyFile() instead
+     */
+    function infoFromAny($info)
+    {
+        if (is_string($info) && file_exists($info)) {
+            $packagefile = &new PEAR_PackageFile($this->config);
+            $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL);
+            if (PEAR::isError($pf)) {
+                $errs = $pf->getUserinfo();
+                if (is_array($errs)) {
+                    foreach ($errs as $error) {
+                        $e = $this->raiseError($error['message'], $error['code'], null, null, $error);
+                    }
+                }
+
+                return $pf;
+            }
+
+            return $this->_postProcessValidPackagexml($pf);
+        }
+
+        return $info;
+    }
+
+    /**
+     * Return an XML document based on the package info (as returned
+     * by the PEAR_Common::infoFrom* methods).
+     *
+     * @param array  $pkginfo  package info
+     *
+     * @return string XML data
+     *
+     * @access public
+     * @deprecated use a PEAR_PackageFile_v* object's generator instead
+     */
+    function xmlFromInfo($pkginfo)
+    {
+        $config      = &PEAR_Config::singleton();
+        $packagefile = &new PEAR_PackageFile($config);
+        $pf = &$packagefile->fromArray($pkginfo);
+        $gen = &$pf->getDefaultGenerator();
+        return $gen->toXml(PEAR_VALIDATE_PACKAGING);
+    }
+
+    /**
+     * Validate XML package definition file.
+     *
+     * @param  string $info Filename of the package archive or of the
+     *                package definition file
+     * @param  array $errors Array that will contain the errors
+     * @param  array $warnings Array that will contain the warnings
+     * @param  string $dir_prefix (optional) directory where source files
+     *                may be found, or empty if they are not available
+     * @access public
+     * @return boolean
+     * @deprecated use the validation of PEAR_PackageFile objects
+     */
+    function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
+    {
+        $config      = &PEAR_Config::singleton();
+        $packagefile = &new PEAR_PackageFile($config);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        if (strpos($info, '<?xml') !== false) {
+            $pf = &$packagefile->fromXmlString($info, PEAR_VALIDATE_NORMAL, '');
+        } else {
+            $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL);
+        }
+
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($pf)) {
+            $errs = $pf->getUserinfo();
+            if (is_array($errs)) {
+                foreach ($errs as $error) {
+                    if ($error['level'] == 'error') {
+                        $errors[] = $error['message'];
+                    } else {
+                        $warnings[] = $error['message'];
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Build a "provides" array from data returned by
+     * analyzeSourceCode().  The format of the built array is like
+     * this:
+     *
+     *  array(
+     *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+     *    ...
+     *  )
+     *
+     *
+     * @param array $srcinfo array with information about a source file
+     * as returned by the analyzeSourceCode() method.
+     *
+     * @return void
+     *
+     * @access public
+     *
+     */
+    function buildProvidesArray($srcinfo)
+    {
+        $file = basename($srcinfo['source_file']);
+        $pn = '';
+        if (isset($this->_packageName)) {
+            $pn = $this->_packageName;
+        }
+
+        $pnl = strlen($pn);
+        foreach ($srcinfo['declared_classes'] as $class) {
+            $key = "class;$class";
+            if (isset($this->pkginfo['provides'][$key])) {
+                continue;
+            }
+
+            $this->pkginfo['provides'][$key] =
+                array('file'=> $file, 'type' => 'class', 'name' => $class);
+            if (isset($srcinfo['inheritance'][$class])) {
+                $this->pkginfo['provides'][$key]['extends'] =
+                    $srcinfo['inheritance'][$class];
+            }
+        }
+
+        foreach ($srcinfo['declared_methods'] as $class => $methods) {
+            foreach ($methods as $method) {
+                $function = "$class::$method";
+                $key = "function;$function";
+                if ($method{0} == '_' || !strcasecmp($method, $class) ||
+                    isset($this->pkginfo['provides'][$key])) {
+                    continue;
+                }
+
+                $this->pkginfo['provides'][$key] =
+                    array('file'=> $file, 'type' => 'function', 'name' => $function);
+            }
+        }
+
+        foreach ($srcinfo['declared_functions'] as $function) {
+            $key = "function;$function";
+            if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
+                continue;
+            }
+
+            if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+                $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+            }
+
+            $this->pkginfo['provides'][$key] =
+                array('file'=> $file, 'type' => 'function', 'name' => $function);
+        }
+    }
+
+    /**
+     * Analyze the source code of the given PHP file
+     *
+     * @param  string Filename of the PHP file
+     * @return mixed
+     * @access public
+     */
+    function analyzeSourceCode($file)
+    {
+        if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+            require_once 'PEAR/PackageFile/v2/Validator.php';
+        }
+
+        $a = new PEAR_PackageFile_v2_Validator;
+        return $a->analyzeSourceCode($file);
+    }
+
+    function detectDependencies($any, $status_callback = null)
+    {
+        if (!function_exists("token_get_all")) {
+            return false;
+        }
+
+        if (PEAR::isError($info = $this->infoFromAny($any))) {
+            return $this->raiseError($info);
+        }
+
+        if (!is_array($info)) {
+            return false;
+        }
+
+        $deps = array();
+        $used_c = $decl_c = $decl_f = $decl_m = array();
+        foreach ($info['filelist'] as $file => $fa) {
+            $tmp = $this->analyzeSourceCode($file);
+            $used_c = @array_merge($used_c, $tmp['used_classes']);
+            $decl_c = @array_merge($decl_c, $tmp['declared_classes']);
+            $decl_f = @array_merge($decl_f, $tmp['declared_functions']);
+            $decl_m = @array_merge($decl_m, $tmp['declared_methods']);
+            $inheri = @array_merge($inheri, $tmp['inheritance']);
+        }
+
+        $used_c = array_unique($used_c);
+        $decl_c = array_unique($decl_c);
+        $undecl_c = array_diff($used_c, $decl_c);
+
+        return array('used_classes' => $used_c,
+                     'declared_classes' => $decl_c,
+                     'declared_methods' => $decl_m,
+                     'declared_functions' => $decl_f,
+                     'undeclared_classes' => $undecl_c,
+                     'inheritance' => $inheri,
+                     );
+    }
+
+    /**
+     * Download a file through HTTP.  Considers suggested file name in
+     * Content-disposition: header and can run a callback function for
+     * different events.  The callback will be called with two
+     * parameters: the callback type, and parameters.  The implemented
+     * callback types are:
+     *
+     *  'setup'       called at the very beginning, parameter is a UI object
+     *                that should be used for all output
+     *  'message'     the parameter is a string with an informational message
+     *  'saveas'      may be used to save with a different file name, the
+     *                parameter is the filename that is about to be used.
+     *                If a 'saveas' callback returns a non-empty string,
+     *                that file name will be used as the filename instead.
+     *                Note that $save_dir will not be affected by this, only
+     *                the basename of the file.
+     *  'start'       download is starting, parameter is number of bytes
+     *                that are expected, or -1 if unknown
+     *  'bytesread'   parameter is the number of bytes read so far
+     *  'done'        download is complete, parameter is the total number
+     *                of bytes read
+     *  'connfailed'  if the TCP connection fails, this callback is called
+     *                with array(host,port,errno,errmsg)
+     *  'writefailed' if writing to disk fails, this callback is called
+     *                with array(destfile,errmsg)
+     *
+     * If an HTTP proxy has been configured (http_proxy PEAR_Config
+     * setting), the proxy will be used.
+     *
+     * @param string  $url       the URL to download
+     * @param object  $ui        PEAR_Frontend_* instance
+     * @param object  $config    PEAR_Config instance
+     * @param string  $save_dir  (optional) directory to save file in
+     * @param mixed   $callback  (optional) function/method to call for status
+     *                           updates
+     *
+     * @return string  Returns the full path of the downloaded file or a PEAR
+     *                 error on failure.  If the error is caused by
+     *                 socket-related errors, the error object will
+     *                 have the fsockopen error code available through
+     *                 getCode().
+     *
+     * @access public
+     * @deprecated in favor of PEAR_Downloader::downloadHttp()
+     */
+    function downloadHttp($url, &$ui, $save_dir = '.', $callback = null)
+    {
+        if (!class_exists('PEAR_Downloader')) {
+            require_once 'PEAR/Downloader.php';
+        }
+        return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback);
+    }
+}
+
+require_once 'PEAR/Config.php';
+require_once 'PEAR/PackageFile.php';
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Config.php b/WEB-INF/lib/pear/PEAR/Config.php
new file mode 100644 (file)
index 0000000..86a7db3
--- /dev/null
@@ -0,0 +1,2097 @@
+<?php
+/**
+ * PEAR_Config, customized configuration handling for the PEAR Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Config.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * Required for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Registry.php';
+require_once 'PEAR/Installer/Role.php';
+require_once 'System.php';
+
+/**
+ * Last created PEAR_Config instance.
+ * @var object
+ */
+$GLOBALS['_PEAR_Config_instance'] = null;
+if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) {
+    $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear';
+} else {
+    $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR;
+}
+
+// Below we define constants with default values for all configuration
+// parameters except username/password.  All of them can have their
+// defaults set through environment variables.  The reason we use the
+// PHP_ prefix is for some security, PHP protects environment
+// variables starting with PHP_*.
+
+// default channel and preferred mirror is based on whether we are invoked through
+// the "pear" or the "pecl" command
+if (!defined('PEAR_RUNTYPE')) {
+    define('PEAR_RUNTYPE', 'pear');
+}
+
+if (PEAR_RUNTYPE == 'pear') {
+    define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net');
+} else {
+    define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net');
+}
+
+if (getenv('PHP_PEAR_SYSCONF_DIR')) {
+    define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR'));
+} elseif (getenv('SystemRoot')) {
+    define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot'));
+} else {
+    define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR);
+}
+
+// Default for master_server
+if (getenv('PHP_PEAR_MASTER_SERVER')) {
+    define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net');
+}
+
+// Default for http_proxy
+if (getenv('PHP_PEAR_HTTP_PROXY')) {
+    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY'));
+} elseif (getenv('http_proxy')) {
+    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', '');
+}
+
+// Default for php_dir
+if (getenv('PHP_PEAR_INSTALL_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR'));
+} else {
+    if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) {
+        define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
+    } else {
+        define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
+    }
+}
+
+// Default for ext_dir
+if (getenv('PHP_PEAR_EXTENSION_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR'));
+} else {
+    if (ini_get('extension_dir')) {
+        define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir'));
+    } elseif (defined('PEAR_EXTENSION_DIR') &&
+              file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) {
+        define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR);
+    } elseif (defined('PHP_EXTENSION_DIR')) {
+        define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR);
+    } else {
+        define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.');
+    }
+}
+
+// Default for doc_dir
+if (getenv('PHP_PEAR_DOC_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_DOC_DIR',
+           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs');
+}
+
+// Default for bin_dir
+if (getenv('PHP_PEAR_BIN_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR);
+}
+
+// Default for data_dir
+if (getenv('PHP_PEAR_DATA_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_DATA_DIR',
+           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data');
+}
+
+// Default for cfg_dir
+if (getenv('PHP_PEAR_CFG_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_CFG_DIR',
+           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg');
+}
+
+// Default for www_dir
+if (getenv('PHP_PEAR_WWW_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_WWW_DIR',
+           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www');
+}
+
+// Default for test_dir
+if (getenv('PHP_PEAR_TEST_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_TEST_DIR',
+           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests');
+}
+
+// Default for temp_dir
+if (getenv('PHP_PEAR_TEMP_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_TEMP_DIR',
+           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+           DIRECTORY_SEPARATOR . 'temp');
+}
+
+// Default for cache_dir
+if (getenv('PHP_PEAR_CACHE_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_CACHE_DIR',
+           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+           DIRECTORY_SEPARATOR . 'cache');
+}
+
+// Default for download_dir
+if (getenv('PHP_PEAR_DOWNLOAD_DIR')) {
+    define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR',
+           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+           DIRECTORY_SEPARATOR . 'download');
+}
+
+// Default for php_bin
+if (getenv('PHP_PEAR_PHP_BIN')) {
+    define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR.
+           DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : ''));
+}
+
+// Default for verbose
+if (getenv('PHP_PEAR_VERBOSE')) {
+    define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_VERBOSE', 1);
+}
+
+// Default for preferred_state
+if (getenv('PHP_PEAR_PREFERRED_STATE')) {
+    define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable');
+}
+
+// Default for umask
+if (getenv('PHP_PEAR_UMASK')) {
+    define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask()));
+}
+
+// Default for cache_ttl
+if (getenv('PHP_PEAR_CACHE_TTL')) {
+    define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600);
+}
+
+// Default for sig_type
+if (getenv('PHP_PEAR_SIG_TYPE')) {
+    define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg');
+}
+
+// Default for sig_bin
+if (getenv('PHP_PEAR_SIG_BIN')) {
+    define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_SIG_BIN',
+           System::which(
+               'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg'));
+}
+
+// Default for sig_keydir
+if (getenv('PHP_PEAR_SIG_KEYDIR')) {
+    define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR'));
+} else {
+    define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR',
+           PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys');
+}
+
+/**
+ * This is a class for storing configuration data, keeping track of
+ * which are system-defined, user-defined or defaulted.
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Config extends PEAR
+{
+    /**
+     * Array of config files used.
+     *
+     * @var array layer => config file
+     */
+    var $files = array(
+        'system' => '',
+        'user' => '',
+        );
+
+    var $layers = array();
+
+    /**
+     * Configuration data, two-dimensional array where the first
+     * dimension is the config layer ('user', 'system' and 'default'),
+     * and the second dimension is keyname => value.
+     *
+     * The order in the first dimension is important!  Earlier
+     * layers will shadow later ones when a config value is
+     * requested (if a 'user' value exists, it will be returned first,
+     * then 'system' and finally 'default').
+     *
+     * @var array layer => array(keyname => value, ...)
+     */
+    var $configuration = array(
+        'user' => array(),
+        'system' => array(),
+        'default' => array(),
+        );
+
+    /**
+     * Configuration values that can be set for a channel
+     *
+     * All other configuration values can only have a global value
+     * @var array
+     * @access private
+     */
+    var $_channelConfigInfo = array(
+        'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir',
+        'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username',
+        'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini'
+        );
+
+    /**
+     * Channels that can be accessed
+     * @see setChannels()
+     * @var array
+     * @access private
+     */
+    var $_channels = array('pear.php.net', 'pecl.php.net', '__uri');
+
+    /**
+     * This variable is used to control the directory values returned
+     * @see setInstallRoot();
+     * @var string|false
+     * @access private
+     */
+    var $_installRoot = false;
+
+    /**
+     * If requested, this will always refer to the registry
+     * contained in php_dir
+     * @var PEAR_Registry
+     */
+    var $_registry = array();
+
+    /**
+     * @var array
+     * @access private
+     */
+    var $_regInitialized = array();
+
+    /**
+     * @var bool
+     * @access private
+     */
+    var $_noRegistry = false;
+
+    /**
+     * amount of errors found while parsing config
+     * @var integer
+     * @access private
+     */
+    var $_errorsFound = 0;
+    var $_lastError = null;
+
+    /**
+     * Information about the configuration data.  Stores the type,
+     * default value and a documentation string for each configuration
+     * value.
+     *
+     * @var array layer => array(infotype => value, ...)
+     */
+    var $configuration_info = array(
+        // Channels/Internet Access
+        'default_channel' => array(
+            'type' => 'string',
+            'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
+            'doc' => 'the default channel to use for all non explicit commands',
+            'prompt' => 'Default Channel',
+            'group' => 'Internet Access',
+            ),
+        'preferred_mirror' => array(
+            'type' => 'string',
+            'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
+            'doc' => 'the default server or mirror to use for channel actions',
+            'prompt' => 'Default Channel Mirror',
+            'group' => 'Internet Access',
+            ),
+        'remote_config' => array(
+            'type' => 'password',
+            'default' => '',
+            'doc' => 'ftp url of remote configuration file to use for synchronized install',
+            'prompt' => 'Remote Configuration File',
+            'group' => 'Internet Access',
+            ),
+        'auto_discover' => array(
+            'type' => 'integer',
+            'default' => 0,
+            'doc' => 'whether to automatically discover new channels',
+            'prompt' => 'Auto-discover new Channels',
+            'group' => 'Internet Access',
+            ),
+        // Internet Access
+        'master_server' => array(
+            'type' => 'string',
+            'default' => 'pear.php.net',
+            'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]',
+            'prompt' => 'PEAR server [DEPRECATED]',
+            'group' => 'Internet Access',
+            ),
+        'http_proxy' => array(
+            'type' => 'string',
+            'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY,
+            'doc' => 'HTTP proxy (host:port) to use when downloading packages',
+            'prompt' => 'HTTP Proxy Server Address',
+            'group' => 'Internet Access',
+            ),
+        // File Locations
+        'php_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_PHP_DIR,
+            'doc' => 'directory where .php files are installed',
+            'prompt' => 'PEAR directory',
+            'group' => 'File Locations',
+            ),
+        'ext_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_EXT_DIR,
+            'doc' => 'directory where loadable extensions are installed',
+            'prompt' => 'PHP extension directory',
+            'group' => 'File Locations',
+            ),
+        'doc_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_DOC_DIR,
+            'doc' => 'directory where documentation is installed',
+            'prompt' => 'PEAR documentation directory',
+            'group' => 'File Locations',
+            ),
+        'bin_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_BIN_DIR,
+            'doc' => 'directory where executables are installed',
+            'prompt' => 'PEAR executables directory',
+            'group' => 'File Locations',
+            ),
+        'data_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_DATA_DIR,
+            'doc' => 'directory where data files are installed',
+            'prompt' => 'PEAR data directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'cfg_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_CFG_DIR,
+            'doc' => 'directory where modifiable configuration files are installed',
+            'prompt' => 'PEAR configuration file directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'www_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_WWW_DIR,
+            'doc' => 'directory where www frontend files (html/js) are installed',
+            'prompt' => 'PEAR www files directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'test_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_TEST_DIR,
+            'doc' => 'directory where regression tests are installed',
+            'prompt' => 'PEAR test directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'cache_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR,
+            'doc' => 'directory which is used for web service cache',
+            'prompt' => 'PEAR Installer cache directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'temp_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR,
+            'doc' => 'directory which is used for all temp files',
+            'prompt' => 'PEAR Installer temp directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'download_dir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR,
+            'doc' => 'directory which is used for all downloaded files',
+            'prompt' => 'PEAR Installer download directory',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'php_bin' => array(
+            'type' => 'file',
+            'default' => PEAR_CONFIG_DEFAULT_PHP_BIN,
+            'doc' => 'PHP CLI/CGI binary for executing scripts',
+            'prompt' => 'PHP CLI/CGI binary',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'php_prefix' => array(
+            'type' => 'string',
+            'default' => '',
+            'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs',
+            'prompt' => '--program-prefix passed to PHP\'s ./configure',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'php_suffix' => array(
+            'type' => 'string',
+            'default' => '',
+            'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs',
+            'prompt' => '--program-suffix passed to PHP\'s ./configure',
+            'group' => 'File Locations (Advanced)',
+            ),
+        'php_ini' => array(
+            'type' => 'file',
+            'default' => '',
+            'doc' => 'location of php.ini in which to enable PECL extensions on install',
+            'prompt' => 'php.ini location',
+            'group' => 'File Locations (Advanced)',
+            ),
+        // Maintainers
+        'username' => array(
+            'type' => 'string',
+            'default' => '',
+            'doc' => '(maintainers) your PEAR account name',
+            'prompt' => 'PEAR username (for maintainers)',
+            'group' => 'Maintainers',
+            ),
+        'password' => array(
+            'type' => 'password',
+            'default' => '',
+            'doc' => '(maintainers) your PEAR account password',
+            'prompt' => 'PEAR password (for maintainers)',
+            'group' => 'Maintainers',
+            ),
+        // Advanced
+        'verbose' => array(
+            'type' => 'integer',
+            'default' => PEAR_CONFIG_DEFAULT_VERBOSE,
+            'doc' => 'verbosity level
+0: really quiet
+1: somewhat quiet
+2: verbose
+3: debug',
+            'prompt' => 'Debug Log Level',
+            'group' => 'Advanced',
+            ),
+        'preferred_state' => array(
+            'type' => 'set',
+            'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE,
+            'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified',
+            'valid_set' => array(
+                'stable', 'beta', 'alpha', 'devel', 'snapshot'),
+            'prompt' => 'Preferred Package State',
+            'group' => 'Advanced',
+            ),
+        'umask' => array(
+            'type' => 'mask',
+            'default' => PEAR_CONFIG_DEFAULT_UMASK,
+            'doc' => 'umask used when creating files (Unix-like systems only)',
+            'prompt' => 'Unix file mask',
+            'group' => 'Advanced',
+            ),
+        'cache_ttl' => array(
+            'type' => 'integer',
+            'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL,
+            'doc' => 'amount of secs where the local cache is used and not updated',
+            'prompt' => 'Cache TimeToLive',
+            'group' => 'Advanced',
+            ),
+        'sig_type' => array(
+            'type' => 'set',
+            'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE,
+            'doc' => 'which package signature mechanism to use',
+            'valid_set' => array('gpg'),
+            'prompt' => 'Package Signature Type',
+            'group' => 'Maintainers',
+            ),
+        'sig_bin' => array(
+            'type' => 'string',
+            'default' => PEAR_CONFIG_DEFAULT_SIG_BIN,
+            'doc' => 'which package signature mechanism to use',
+            'prompt' => 'Signature Handling Program',
+            'group' => 'Maintainers',
+            ),
+        'sig_keyid' => array(
+            'type' => 'string',
+            'default' => '',
+            'doc' => 'which key to use for signing with',
+            'prompt' => 'Signature Key Id',
+            'group' => 'Maintainers',
+            ),
+        'sig_keydir' => array(
+            'type' => 'directory',
+            'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR,
+            'doc' => 'directory where signature keys are located',
+            'prompt' => 'Signature Key Directory',
+            'group' => 'Maintainers',
+            ),
+        // __channels is reserved - used for channel-specific configuration
+        );
+
+    /**
+     * Constructor.
+     *
+     * @param string file to read user-defined options from
+     * @param string file to read system-wide defaults from
+     * @param bool   determines whether a registry object "follows"
+     *               the value of php_dir (is automatically created
+     *               and moved when php_dir is changed)
+     * @param bool   if true, fails if configuration files cannot be loaded
+     *
+     * @access public
+     *
+     * @see PEAR_Config::singleton
+     */
+    function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false,
+                         $strict = true)
+    {
+        $this->PEAR();
+        PEAR_Installer_Role::initializeConfig($this);
+        $sl = DIRECTORY_SEPARATOR;
+        if (empty($user_file)) {
+            if (OS_WINDOWS) {
+                $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini';
+            } else {
+                $user_file = getenv('HOME') . $sl . '.pearrc';
+            }
+        }
+
+        if (empty($system_file)) {
+            $system_file = PEAR_CONFIG_SYSCONFDIR . $sl;
+            if (OS_WINDOWS) {
+                $system_file .= 'pearsys.ini';
+            } else {
+                $system_file .= 'pear.conf';
+            }
+        }
+
+        $this->layers = array_keys($this->configuration);
+        $this->files['user']   = $user_file;
+        $this->files['system'] = $system_file;
+        if ($user_file && file_exists($user_file)) {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $this->readConfigFile($user_file, 'user', $strict);
+            $this->popErrorHandling();
+            if ($this->_errorsFound > 0) {
+                return;
+            }
+        }
+
+        if ($system_file && @file_exists($system_file)) {
+            $this->mergeConfigFile($system_file, false, 'system', $strict);
+            if ($this->_errorsFound > 0) {
+                return;
+            }
+
+        }
+
+        if (!$ftp_file) {
+            $ftp_file = $this->get('remote_config');
+        }
+
+        if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) {
+            $this->readFTPConfigFile($ftp_file);
+        }
+
+        foreach ($this->configuration_info as $key => $info) {
+            $this->configuration['default'][$key] = $info['default'];
+        }
+
+        $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']);
+        $this->_registry['default']->setConfig($this, false);
+        $this->_regInitialized['default'] = false;
+        //$GLOBALS['_PEAR_Config_instance'] = &$this;
+    }
+
+    /**
+     * Return the default locations of user and system configuration files
+     * @static
+     */
+    function getDefaultConfigFiles()
+    {
+        $sl = DIRECTORY_SEPARATOR;
+        if (OS_WINDOWS) {
+            return array(
+                'user'   => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini',
+                'system' =>  PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini'
+            );
+        }
+
+        return array(
+            'user'   => getenv('HOME') . $sl . '.pearrc',
+            'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf'
+        );
+    }
+
+    /**
+     * Static singleton method.  If you want to keep only one instance
+     * of this class in use, this method will give you a reference to
+     * the last created PEAR_Config object if one exists, or create a
+     * new object.
+     *
+     * @param string (optional) file to read user-defined options from
+     * @param string (optional) file to read system-wide defaults from
+     *
+     * @return object an existing or new PEAR_Config instance
+     *
+     * @access public
+     *
+     * @see PEAR_Config::PEAR_Config
+     */
+    function &singleton($user_file = '', $system_file = '', $strict = true)
+    {
+        if (is_object($GLOBALS['_PEAR_Config_instance'])) {
+            return $GLOBALS['_PEAR_Config_instance'];
+        }
+
+        $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict);
+        if ($t_conf->_errorsFound > 0) {
+             return $t_conf->lastError;
+        }
+
+        $GLOBALS['_PEAR_Config_instance'] = &$t_conf;
+        return $GLOBALS['_PEAR_Config_instance'];
+    }
+
+    /**
+     * Determine whether any configuration files have been detected, and whether a
+     * registry object can be retrieved from this configuration.
+     * @return bool
+     * @since PEAR 1.4.0a1
+     */
+    function validConfiguration()
+    {
+        if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Reads configuration data from a file.  All existing values in
+     * the config layer are discarded and replaced with data from the
+     * file.
+     * @param string file to read from, if NULL or not specified, the
+     *               last-used file for the same layer (second param) is used
+     * @param string config layer to insert data into ('user' or 'system')
+     * @return bool TRUE on success or a PEAR error on failure
+     */
+    function readConfigFile($file = null, $layer = 'user', $strict = true)
+    {
+        if (empty($this->files[$layer])) {
+            return $this->raiseError("unknown config layer `$layer'");
+        }
+
+        if ($file === null) {
+            $file = $this->files[$layer];
+        }
+
+        $data = $this->_readConfigDataFrom($file);
+        if (PEAR::isError($data)) {
+            if (!$strict) {
+                return true;
+            }
+
+            $this->_errorsFound++;
+            $this->lastError = $data;
+
+            return $data;
+        }
+
+        $this->files[$layer] = $file;
+        $this->_decodeInput($data);
+        $this->configuration[$layer] = $data;
+        $this->_setupChannels();
+        if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
+            $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+            $this->_registry[$layer]->setConfig($this, false);
+            $this->_regInitialized[$layer] = false;
+        } else {
+            unset($this->_registry[$layer]);
+        }
+        return true;
+    }
+
+    /**
+     * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini
+     * @return true|PEAR_Error
+     */
+    function readFTPConfigFile($path)
+    {
+        do { // poor man's try
+            if (!class_exists('PEAR_FTP')) {
+                if (!class_exists('PEAR_Common')) {
+                    require_once 'PEAR/Common.php';
+                }
+                if (PEAR_Common::isIncludeable('PEAR/FTP.php')) {
+                    require_once 'PEAR/FTP.php';
+                }
+            }
+
+            if (!class_exists('PEAR_FTP')) {
+                return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config');
+            }
+
+            $this->_ftp = &new PEAR_FTP;
+            $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN);
+            $e = $this->_ftp->init($path);
+            if (PEAR::isError($e)) {
+                $this->_ftp->popErrorHandling();
+                return $e;
+            }
+
+            $tmp = System::mktemp('-d');
+            PEAR_Common::addTempFile($tmp);
+            $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR .
+                'pear.ini', false, FTP_BINARY);
+            if (PEAR::isError($e)) {
+                $this->_ftp->popErrorHandling();
+                return $e;
+            }
+
+            PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini');
+            $this->_ftp->disconnect();
+            $this->_ftp->popErrorHandling();
+            $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini';
+            $e = $this->readConfigFile(null, 'ftp');
+            if (PEAR::isError($e)) {
+                return $e;
+            }
+
+            $fail = array();
+            foreach ($this->configuration_info as $key => $val) {
+                if (in_array($this->getGroup($key),
+                      array('File Locations', 'File Locations (Advanced)')) &&
+                      $this->getType($key) == 'directory') {
+                    // any directory configs must be set for this to work
+                    if (!isset($this->configuration['ftp'][$key])) {
+                        $fail[] = $key;
+                    }
+                }
+            }
+
+            if (!count($fail)) {
+                return true;
+            }
+
+            $fail = '"' . implode('", "', $fail) . '"';
+            unset($this->files['ftp']);
+            unset($this->configuration['ftp']);
+            return PEAR::raiseError('ERROR: Ftp configuration file must set all ' .
+                'directory configuration variables.  These variables were not set: ' .
+                $fail);
+        } while (false); // poor man's catch
+        unset($this->files['ftp']);
+        return PEAR::raiseError('no remote host specified');
+    }
+
+    /**
+     * Reads the existing configurations and creates the _channels array from it
+     */
+    function _setupChannels()
+    {
+        $set = array_flip(array_values($this->_channels));
+        foreach ($this->configuration as $layer => $data) {
+            $i = 1000;
+            if (isset($data['__channels']) && is_array($data['__channels'])) {
+                foreach ($data['__channels'] as $channel => $info) {
+                    $set[$channel] = $i++;
+                }
+            }
+        }
+        $this->_channels = array_values(array_flip($set));
+        $this->setChannels($this->_channels);
+    }
+
+    function deleteChannel($channel)
+    {
+        $ch = strtolower($channel);
+        foreach ($this->configuration as $layer => $data) {
+            if (isset($data['__channels']) && isset($data['__channels'][$ch])) {
+                unset($this->configuration[$layer]['__channels'][$ch]);
+            }
+        }
+
+        $this->_channels = array_flip($this->_channels);
+        unset($this->_channels[$ch]);
+        $this->_channels = array_flip($this->_channels);
+    }
+
+    /**
+     * Merges data into a config layer from a file.  Does the same
+     * thing as readConfigFile, except it does not replace all
+     * existing values in the config layer.
+     * @param string file to read from
+     * @param bool whether to overwrite existing data (default TRUE)
+     * @param string config layer to insert data into ('user' or 'system')
+     * @param string if true, errors are returned if file opening fails
+     * @return bool TRUE on success or a PEAR error on failure
+     */
+    function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true)
+    {
+        if (empty($this->files[$layer])) {
+            return $this->raiseError("unknown config layer `$layer'");
+        }
+
+        if ($file === null) {
+            $file = $this->files[$layer];
+        }
+
+        $data = $this->_readConfigDataFrom($file);
+        if (PEAR::isError($data)) {
+            if (!$strict) {
+                return true;
+            }
+
+            $this->_errorsFound++;
+            $this->lastError = $data;
+
+            return $data;
+        }
+
+        $this->_decodeInput($data);
+        if ($override) {
+            $this->configuration[$layer] =
+                PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data);
+        } else {
+            $this->configuration[$layer] =
+                PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]);
+        }
+
+        $this->_setupChannels();
+        if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
+            $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+            $this->_registry[$layer]->setConfig($this, false);
+            $this->_regInitialized[$layer] = false;
+        } else {
+            unset($this->_registry[$layer]);
+        }
+        return true;
+    }
+
+    /**
+     * @param array
+     * @param array
+     * @return array
+     * @static
+     */
+    function arrayMergeRecursive($arr2, $arr1)
+    {
+        $ret = array();
+        foreach ($arr2 as $key => $data) {
+            if (!isset($arr1[$key])) {
+                $ret[$key] = $data;
+                unset($arr1[$key]);
+                continue;
+            }
+            if (is_array($data)) {
+                if (!is_array($arr1[$key])) {
+                    $ret[$key] = $arr1[$key];
+                    unset($arr1[$key]);
+                    continue;
+                }
+                $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]);
+                unset($arr1[$key]);
+            }
+        }
+
+        return array_merge($ret, $arr1);
+    }
+
+    /**
+     * Writes data into a config layer from a file.
+     *
+     * @param string|null file to read from, or null for default
+     * @param string config layer to insert data into ('user' or
+     *               'system')
+     * @param string|null data to write to config file or null for internal data [DEPRECATED]
+     * @return bool TRUE on success or a PEAR error on failure
+     */
+    function writeConfigFile($file = null, $layer = 'user', $data = null)
+    {
+        $this->_lazyChannelSetup($layer);
+        if ($layer == 'both' || $layer == 'all') {
+            foreach ($this->files as $type => $file) {
+                $err = $this->writeConfigFile($file, $type, $data);
+                if (PEAR::isError($err)) {
+                    return $err;
+                }
+            }
+            return true;
+        }
+
+        if (empty($this->files[$layer])) {
+            return $this->raiseError("unknown config file type `$layer'");
+        }
+
+        if ($file === null) {
+            $file = $this->files[$layer];
+        }
+
+        $data = ($data === null) ? $this->configuration[$layer] : $data;
+        $this->_encodeOutput($data);
+        $opt = array('-p', dirname($file));
+        if (!@System::mkDir($opt)) {
+            return $this->raiseError("could not create directory: " . dirname($file));
+        }
+
+        if (file_exists($file) && is_file($file) && !is_writeable($file)) {
+            return $this->raiseError("no write access to $file!");
+        }
+
+        $fp = @fopen($file, "w");
+        if (!$fp) {
+            return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)");
+        }
+
+        $contents = "#PEAR_Config 0.9\n" . serialize($data);
+        if (!@fwrite($fp, $contents)) {
+            return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)");
+        }
+        return true;
+    }
+
+    /**
+     * Reads configuration data from a file and returns the parsed data
+     * in an array.
+     *
+     * @param string file to read from
+     * @return array configuration data or a PEAR error on failure
+     * @access private
+     */
+    function _readConfigDataFrom($file)
+    {
+        $fp = false;
+        if (file_exists($file)) {
+            $fp = @fopen($file, "r");
+        }
+
+        if (!$fp) {
+            return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed");
+        }
+
+        $size = filesize($file);
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        fclose($fp);
+        $contents = file_get_contents($file);
+        if (empty($contents)) {
+            return $this->raiseError('Configuration file "' . $file . '" is empty');
+        }
+
+        set_magic_quotes_runtime($rt);
+
+        $version = false;
+        if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) {
+            $version = $matches[1];
+            $contents = substr($contents, strlen($matches[0]));
+        } else {
+            // Museum config file
+            if (substr($contents,0,2) == 'a:') {
+                $version = '0.1';
+            }
+        }
+
+        if ($version && version_compare("$version", '1', '<')) {
+            // no '@', it is possible that unserialize
+            // raises a notice but it seems to block IO to
+            // STDOUT if a '@' is used and a notice is raise
+            $data = unserialize($contents);
+
+            if (!is_array($data) && !$data) {
+                if ($contents == serialize(false)) {
+                    $data = array();
+                } else {
+                    $err = $this->raiseError("PEAR_Config: bad data in $file");
+                    return $err;
+                }
+            }
+            if (!is_array($data)) {
+                if (strlen(trim($contents)) > 0) {
+                    $error = "PEAR_Config: bad data in $file";
+                    $err = $this->raiseError($error);
+                    return $err;
+                }
+
+                $data = array();
+            }
+        // add parsing of newer formats here...
+        } else {
+            $err = $this->raiseError("$file: unknown version `$version'");
+            return $err;
+        }
+
+        return $data;
+    }
+
+    /**
+    * Gets the file used for storing the config for a layer
+    *
+    * @param string $layer 'user' or 'system'
+    */
+    function getConfFile($layer)
+    {
+        return $this->files[$layer];
+    }
+
+    /**
+     * @param string Configuration class name, used for detecting duplicate calls
+     * @param array information on a role as parsed from its xml file
+     * @return true|PEAR_Error
+     * @access private
+     */
+    function _addConfigVars($class, $vars)
+    {
+        static $called = array();
+        if (isset($called[$class])) {
+            return;
+        }
+
+        $called[$class] = 1;
+        if (count($vars) > 3) {
+            return $this->raiseError('Roles can only define 3 new config variables or less');
+        }
+
+        foreach ($vars as $name => $var) {
+            if (!is_array($var)) {
+                return $this->raiseError('Configuration information must be an array');
+            }
+
+            if (!isset($var['type'])) {
+                return $this->raiseError('Configuration information must contain a type');
+            } elseif (!in_array($var['type'],
+                    array('string', 'mask', 'password', 'directory', 'file', 'set'))) {
+                  return $this->raiseError(
+                      'Configuration type must be one of directory, file, string, ' .
+                      'mask, set, or password');
+            }
+            if (!isset($var['default'])) {
+                return $this->raiseError(
+                    'Configuration information must contain a default value ("default" index)');
+            }
+
+            if (is_array($var['default'])) {
+                $real_default = '';
+                foreach ($var['default'] as $config_var => $val) {
+                    if (strpos($config_var, 'text') === 0) {
+                        $real_default .= $val;
+                    } elseif (strpos($config_var, 'constant') === 0) {
+                        if (!defined($val)) {
+                            return $this->raiseError(
+                                'Unknown constant "' . $val . '" requested in ' .
+                                'default value for configuration variable "' .
+                                $name . '"');
+                        }
+
+                        $real_default .= constant($val);
+                    } elseif (isset($this->configuration_info[$config_var])) {
+                        $real_default .=
+                            $this->configuration_info[$config_var]['default'];
+                    } else {
+                        return $this->raiseError(
+                            'Unknown request for "' . $config_var . '" value in ' .
+                            'default value for configuration variable "' .
+                            $name . '"');
+                    }
+                }
+                $var['default'] = $real_default;
+            }
+
+            if ($var['type'] == 'integer') {
+                $var['default'] = (integer) $var['default'];
+            }
+
+            if (!isset($var['doc'])) {
+                return $this->raiseError(
+                    'Configuration information must contain a summary ("doc" index)');
+            }
+
+            if (!isset($var['prompt'])) {
+                return $this->raiseError(
+                    'Configuration information must contain a simple prompt ("prompt" index)');
+            }
+
+            if (!isset($var['group'])) {
+                return $this->raiseError(
+                    'Configuration information must contain a simple group ("group" index)');
+            }
+
+            if (isset($this->configuration_info[$name])) {
+                return $this->raiseError('Configuration variable "' . $name .
+                    '" already exists');
+            }
+
+            $this->configuration_info[$name] = $var;
+            // fix bug #7351: setting custom config variable in a channel fails
+            $this->_channelConfigInfo[] = $name;
+        }
+
+        return true;
+    }
+
+    /**
+     * Encodes/scrambles configuration data before writing to files.
+     * Currently, 'password' values will be base64-encoded as to avoid
+     * that people spot cleartext passwords by accident.
+     *
+     * @param array (reference) array to encode values in
+     * @return bool TRUE on success
+     * @access private
+     */
+    function _encodeOutput(&$data)
+    {
+        foreach ($data as $key => $value) {
+            if ($key == '__channels') {
+                foreach ($data['__channels'] as $channel => $blah) {
+                    $this->_encodeOutput($data['__channels'][$channel]);
+                }
+            }
+
+            if (!isset($this->configuration_info[$key])) {
+                continue;
+            }
+
+            $type = $this->configuration_info[$key]['type'];
+            switch ($type) {
+                // we base64-encode passwords so they are at least
+                // not shown in plain by accident
+                case 'password': {
+                    $data[$key] = base64_encode($data[$key]);
+                    break;
+                }
+                case 'mask': {
+                    $data[$key] = octdec($data[$key]);
+                    break;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Decodes/unscrambles configuration data after reading from files.
+     *
+     * @param array (reference) array to encode values in
+     * @return bool TRUE on success
+     * @access private
+     *
+     * @see PEAR_Config::_encodeOutput
+     */
+    function _decodeInput(&$data)
+    {
+        if (!is_array($data)) {
+            return true;
+        }
+
+        foreach ($data as $key => $value) {
+            if ($key == '__channels') {
+                foreach ($data['__channels'] as $channel => $blah) {
+                    $this->_decodeInput($data['__channels'][$channel]);
+                }
+            }
+
+            if (!isset($this->configuration_info[$key])) {
+                continue;
+            }
+
+            $type = $this->configuration_info[$key]['type'];
+            switch ($type) {
+                case 'password': {
+                    $data[$key] = base64_decode($data[$key]);
+                    break;
+                }
+                case 'mask': {
+                    $data[$key] = decoct($data[$key]);
+                    break;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Retrieve the default channel.
+     *
+     * On startup, channels are not initialized, so if the default channel is not
+     * pear.php.net, then initialize the config.
+     * @param string registry layer
+     * @return string|false
+     */
+    function getDefaultChannel($layer = null)
+    {
+        $ret = false;
+        if ($layer === null) {
+            foreach ($this->layers as $layer) {
+                if (isset($this->configuration[$layer]['default_channel'])) {
+                    $ret = $this->configuration[$layer]['default_channel'];
+                    break;
+                }
+            }
+        } elseif (isset($this->configuration[$layer]['default_channel'])) {
+            $ret = $this->configuration[$layer]['default_channel'];
+        }
+
+        if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') {
+            $ret = 'pecl.php.net';
+        }
+
+        if ($ret) {
+            if ($ret != 'pear.php.net') {
+                $this->_lazyChannelSetup();
+            }
+
+            return $ret;
+        }
+
+        return PEAR_CONFIG_DEFAULT_CHANNEL;
+    }
+
+    /**
+     * Returns a configuration value, prioritizing layers as per the
+     * layers property.
+     *
+     * @param string config key
+     * @return mixed the config value, or NULL if not found
+     * @access public
+     */
+    function get($key, $layer = null, $channel = false)
+    {
+        if (!isset($this->configuration_info[$key])) {
+            return null;
+        }
+
+        if ($key == '__channels') {
+            return null;
+        }
+
+        if ($key == 'default_channel') {
+            return $this->getDefaultChannel($layer);
+        }
+
+        if (!$channel) {
+            $channel = $this->getDefaultChannel();
+        } elseif ($channel != 'pear.php.net') {
+            $this->_lazyChannelSetup();
+        }
+        $channel = strtolower($channel);
+
+        $test = (in_array($key, $this->_channelConfigInfo)) ?
+            $this->_getChannelValue($key, $layer, $channel) :
+            null;
+        if ($test !== null) {
+            if ($this->_installRoot) {
+                if (in_array($this->getGroup($key),
+                      array('File Locations', 'File Locations (Advanced)')) &&
+                      $this->getType($key) == 'directory') {
+                    return $this->_prependPath($test, $this->_installRoot);
+                }
+            }
+            return $test;
+        }
+
+        if ($layer === null) {
+            foreach ($this->layers as $layer) {
+                if (isset($this->configuration[$layer][$key])) {
+                    $test = $this->configuration[$layer][$key];
+                    if ($this->_installRoot) {
+                        if (in_array($this->getGroup($key),
+                              array('File Locations', 'File Locations (Advanced)')) &&
+                              $this->getType($key) == 'directory') {
+                            return $this->_prependPath($test, $this->_installRoot);
+                        }
+                    }
+
+                    if ($key == 'preferred_mirror') {
+                        $reg = &$this->getRegistry();
+                        if (is_object($reg)) {
+                            $chan = &$reg->getChannel($channel);
+                            if (PEAR::isError($chan)) {
+                                return $channel;
+                            }
+
+                            if (!$chan->getMirror($test) && $chan->getName() != $test) {
+                                return $channel; // mirror does not exist
+                            }
+                        }
+                    }
+                    return $test;
+                }
+            }
+        } elseif (isset($this->configuration[$layer][$key])) {
+            $test = $this->configuration[$layer][$key];
+            if ($this->_installRoot) {
+                if (in_array($this->getGroup($key),
+                      array('File Locations', 'File Locations (Advanced)')) &&
+                      $this->getType($key) == 'directory') {
+                    return $this->_prependPath($test, $this->_installRoot);
+                }
+            }
+
+            if ($key == 'preferred_mirror') {
+                $reg = &$this->getRegistry();
+                if (is_object($reg)) {
+                    $chan = &$reg->getChannel($channel);
+                    if (PEAR::isError($chan)) {
+                        return $channel;
+                    }
+
+                    if (!$chan->getMirror($test) && $chan->getName() != $test) {
+                        return $channel; // mirror does not exist
+                    }
+                }
+            }
+
+            return $test;
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a channel-specific configuration value, prioritizing layers as per the
+     * layers property.
+     *
+     * @param string config key
+     * @return mixed the config value, or NULL if not found
+     * @access private
+     */
+    function _getChannelValue($key, $layer, $channel)
+    {
+        if ($key == '__channels' || $channel == 'pear.php.net') {
+            return null;
+        }
+
+        $ret = null;
+        if ($layer === null) {
+            foreach ($this->layers as $ilayer) {
+                if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) {
+                    $ret = $this->configuration[$ilayer]['__channels'][$channel][$key];
+                    break;
+                }
+            }
+        } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+            $ret = $this->configuration[$layer]['__channels'][$channel][$key];
+        }
+
+        if ($key != 'preferred_mirror') {
+            return $ret;
+        }
+
+
+        if ($ret !== null) {
+            $reg = &$this->getRegistry($layer);
+            if (is_object($reg)) {
+                $chan = &$reg->getChannel($channel);
+                if (PEAR::isError($chan)) {
+                    return $channel;
+                }
+
+                if (!$chan->getMirror($ret) && $chan->getName() != $ret) {
+                    return $channel; // mirror does not exist
+                }
+            }
+
+            return $ret;
+        }
+
+        if ($channel != $this->getDefaultChannel($layer)) {
+            return $channel; // we must use the channel name as the preferred mirror
+                             // if the user has not chosen an alternate
+        }
+
+        return $this->getDefaultChannel($layer);
+    }
+
+    /**
+     * Set a config value in a specific layer (defaults to 'user').
+     * Enforces the types defined in the configuration_info array.  An
+     * integer config variable will be cast to int, and a set config
+     * variable will be validated against its legal values.
+     *
+     * @param string config key
+     * @param string config value
+     * @param string (optional) config layer
+     * @param string channel to set this value for, or null for global value
+     * @return bool TRUE on success, FALSE on failure
+     */
+    function set($key, $value, $layer = 'user', $channel = false)
+    {
+        if ($key == '__channels') {
+            return false;
+        }
+
+        if (!isset($this->configuration[$layer])) {
+            return false;
+        }
+
+        if ($key == 'default_channel') {
+            // can only set this value globally
+            $channel = 'pear.php.net';
+            if ($value != 'pear.php.net') {
+                $this->_lazyChannelSetup($layer);
+            }
+        }
+
+        if ($key == 'preferred_mirror') {
+            if ($channel == '__uri') {
+                return false; // can't set the __uri pseudo-channel's mirror
+            }
+
+            $reg = &$this->getRegistry($layer);
+            if (is_object($reg)) {
+                $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net');
+                if (PEAR::isError($chan)) {
+                    return false;
+                }
+
+                if (!$chan->getMirror($value) && $chan->getName() != $value) {
+                    return false; // mirror does not exist
+                }
+            }
+        }
+
+        if (!isset($this->configuration_info[$key])) {
+            return false;
+        }
+
+        extract($this->configuration_info[$key]);
+        switch ($type) {
+            case 'integer':
+                $value = (int)$value;
+                break;
+            case 'set': {
+                // If a valid_set is specified, require the value to
+                // be in the set.  If there is no valid_set, accept
+                // any value.
+                if ($valid_set) {
+                    reset($valid_set);
+                    if ((key($valid_set) === 0 && !in_array($value, $valid_set)) ||
+                        (key($valid_set) !== 0 && empty($valid_set[$value])))
+                    {
+                        return false;
+                    }
+                }
+                break;
+            }
+        }
+
+        if (!$channel) {
+            $channel = $this->get('default_channel', null, 'pear.php.net');
+        }
+
+        if (!in_array($channel, $this->_channels)) {
+            $this->_lazyChannelSetup($layer);
+            $reg = &$this->getRegistry($layer);
+            if ($reg) {
+                $channel = $reg->channelName($channel);
+            }
+
+            if (!in_array($channel, $this->_channels)) {
+                return false;
+            }
+        }
+
+        if ($channel != 'pear.php.net') {
+            if (in_array($key, $this->_channelConfigInfo)) {
+                $this->configuration[$layer]['__channels'][$channel][$key] = $value;
+                return true;
+            }
+
+            return false;
+        }
+
+        if ($key == 'default_channel') {
+            if (!isset($reg)) {
+                $reg = &$this->getRegistry($layer);
+                if (!$reg) {
+                    $reg = &$this->getRegistry();
+                }
+            }
+
+            if ($reg) {
+                $value = $reg->channelName($value);
+            }
+
+            if (!$value) {
+                return false;
+            }
+        }
+
+        $this->configuration[$layer][$key] = $value;
+        if ($key == 'php_dir' && !$this->_noRegistry) {
+            if (!isset($this->_registry[$layer]) ||
+                  $value != $this->_registry[$layer]->install_dir) {
+                $this->_registry[$layer] = &new PEAR_Registry($value);
+                $this->_regInitialized[$layer] = false;
+                $this->_registry[$layer]->setConfig($this, false);
+            }
+        }
+
+        return true;
+    }
+
+    function _lazyChannelSetup($uselayer = false)
+    {
+        if ($this->_noRegistry) {
+            return;
+        }
+
+        $merge = false;
+        foreach ($this->_registry as $layer => $p) {
+            if ($uselayer && $uselayer != $layer) {
+                continue;
+            }
+
+            if (!$this->_regInitialized[$layer]) {
+                if ($layer == 'default' && isset($this->_registry['user']) ||
+                      isset($this->_registry['system'])) {
+                    // only use the default registry if there are no alternatives
+                    continue;
+                }
+
+                if (!is_object($this->_registry[$layer])) {
+                    if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) {
+                        $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+                        $this->_registry[$layer]->setConfig($this, false);
+                        $this->_regInitialized[$layer] = false;
+                    } else {
+                        unset($this->_registry[$layer]);
+                        return;
+                    }
+                }
+
+                $this->setChannels($this->_registry[$layer]->listChannels(), $merge);
+                $this->_regInitialized[$layer] = true;
+                $merge = true;
+            }
+        }
+    }
+
+    /**
+     * Set the list of channels.
+     *
+     * This should be set via a call to {@link PEAR_Registry::listChannels()}
+     * @param array
+     * @param bool
+     * @return bool success of operation
+     */
+    function setChannels($channels, $merge = false)
+    {
+        if (!is_array($channels)) {
+            return false;
+        }
+
+        if ($merge) {
+            $this->_channels = array_merge($this->_channels, $channels);
+        } else {
+            $this->_channels = $channels;
+        }
+
+        foreach ($channels as $channel) {
+            $channel = strtolower($channel);
+            if ($channel == 'pear.php.net') {
+                continue;
+            }
+
+            foreach ($this->layers as $layer) {
+                if (!isset($this->configuration[$layer]['__channels'])) {
+                    $this->configuration[$layer]['__channels'] = array();
+                }
+                if (!isset($this->configuration[$layer]['__channels'][$channel])
+                      || !is_array($this->configuration[$layer]['__channels'][$channel])) {
+                    $this->configuration[$layer]['__channels'][$channel] = array();
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Get the type of a config value.
+     *
+     * @param string  config key
+     *
+     * @return string type, one of "string", "integer", "file",
+     * "directory", "set" or "password".
+     *
+     * @access public
+     *
+     */
+    function getType($key)
+    {
+        if (isset($this->configuration_info[$key])) {
+            return $this->configuration_info[$key]['type'];
+        }
+        return false;
+    }
+
+    /**
+     * Get the documentation for a config value.
+     *
+     * @param string  config key
+     * @return string documentation string
+     *
+     * @access public
+     *
+     */
+    function getDocs($key)
+    {
+        if (isset($this->configuration_info[$key])) {
+            return $this->configuration_info[$key]['doc'];
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the short documentation for a config value.
+     *
+     * @param string  config key
+     * @return string short documentation string
+     *
+     * @access public
+     *
+     */
+    function getPrompt($key)
+    {
+        if (isset($this->configuration_info[$key])) {
+            return $this->configuration_info[$key]['prompt'];
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the parameter group for a config key.
+     *
+     * @param string  config key
+     * @return string parameter group
+     *
+     * @access public
+     *
+     */
+    function getGroup($key)
+    {
+        if (isset($this->configuration_info[$key])) {
+            return $this->configuration_info[$key]['group'];
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the list of parameter groups.
+     *
+     * @return array list of parameter groups
+     *
+     * @access public
+     *
+     */
+    function getGroups()
+    {
+        $tmp = array();
+        foreach ($this->configuration_info as $key => $info) {
+            $tmp[$info['group']] = 1;
+        }
+
+        return array_keys($tmp);
+    }
+
+    /**
+     * Get the list of the parameters in a group.
+     *
+     * @param string $group parameter group
+     * @return array list of parameters in $group
+     *
+     * @access public
+     *
+     */
+    function getGroupKeys($group)
+    {
+        $keys = array();
+        foreach ($this->configuration_info as $key => $info) {
+            if ($info['group'] == $group) {
+                $keys[] = $key;
+            }
+        }
+
+        return $keys;
+    }
+
+    /**
+     * Get the list of allowed set values for a config value.  Returns
+     * NULL for config values that are not sets.
+     *
+     * @param string  config key
+     * @return array enumerated array of set values, or NULL if the
+     *               config key is unknown or not a set
+     *
+     * @access public
+     *
+     */
+    function getSetValues($key)
+    {
+        if (isset($this->configuration_info[$key]) &&
+            isset($this->configuration_info[$key]['type']) &&
+            $this->configuration_info[$key]['type'] == 'set')
+        {
+            $valid_set = $this->configuration_info[$key]['valid_set'];
+            reset($valid_set);
+            if (key($valid_set) === 0) {
+                return $valid_set;
+            }
+
+            return array_keys($valid_set);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get all the current config keys.
+     *
+     * @return array simple array of config keys
+     *
+     * @access public
+     */
+    function getKeys()
+    {
+        $keys = array();
+        foreach ($this->layers as $layer) {
+            $test = $this->configuration[$layer];
+            if (isset($test['__channels'])) {
+                foreach ($test['__channels'] as $channel => $configs) {
+                    $keys = array_merge($keys, $configs);
+                }
+            }
+
+            unset($test['__channels']);
+            $keys = array_merge($keys, $test);
+
+        }
+        return array_keys($keys);
+    }
+
+    /**
+     * Remove the a config key from a specific config layer.
+     *
+     * @param string config key
+     * @param string (optional) config layer
+     * @param string (optional) channel (defaults to default channel)
+     * @return bool TRUE on success, FALSE on failure
+     *
+     * @access public
+     */
+    function remove($key, $layer = 'user', $channel = null)
+    {
+        if ($channel === null) {
+            $channel = $this->getDefaultChannel();
+        }
+
+        if ($channel !== 'pear.php.net') {
+            if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+                unset($this->configuration[$layer]['__channels'][$channel][$key]);
+                return true;
+            }
+        }
+
+        if (isset($this->configuration[$layer][$key])) {
+            unset($this->configuration[$layer][$key]);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Temporarily remove an entire config layer.  USE WITH CARE!
+     *
+     * @param string config key
+     * @param string (optional) config layer
+     * @return bool TRUE on success, FALSE on failure
+     *
+     * @access public
+     */
+    function removeLayer($layer)
+    {
+        if (isset($this->configuration[$layer])) {
+            $this->configuration[$layer] = array();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Stores configuration data in a layer.
+     *
+     * @param string config layer to store
+     * @return bool TRUE on success, or PEAR error on failure
+     *
+     * @access public
+     */
+    function store($layer = 'user', $data = null)
+    {
+        return $this->writeConfigFile(null, $layer, $data);
+    }
+
+    /**
+     * Tells what config layer that gets to define a key.
+     *
+     * @param string config key
+     * @param boolean return the defining channel
+     *
+     * @return string|array the config layer, or an empty string if not found.
+     *
+     *         if $returnchannel, the return is an array array('layer' => layername,
+     *         'channel' => channelname), or an empty string if not found
+     *
+     * @access public
+     */
+    function definedBy($key, $returnchannel = false)
+    {
+        foreach ($this->layers as $layer) {
+            $channel = $this->getDefaultChannel();
+            if ($channel !== 'pear.php.net') {
+                if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+                    if ($returnchannel) {
+                        return array('layer' => $layer, 'channel' => $channel);
+                    }
+                    return $layer;
+                }
+            }
+
+            if (isset($this->configuration[$layer][$key])) {
+                if ($returnchannel) {
+                    return array('layer' => $layer, 'channel' => 'pear.php.net');
+                }
+                return $layer;
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Tells whether a given key exists as a config value.
+     *
+     * @param string config key
+     * @return bool whether <config key> exists in this object
+     *
+     * @access public
+     */
+    function isDefined($key)
+    {
+        foreach ($this->layers as $layer) {
+            if (isset($this->configuration[$layer][$key])) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Tells whether a given config layer exists.
+     *
+     * @param string config layer
+     * @return bool whether <config layer> exists in this object
+     *
+     * @access public
+     */
+    function isDefinedLayer($layer)
+    {
+        return isset($this->configuration[$layer]);
+    }
+
+    /**
+     * Returns the layers defined (except the 'default' one)
+     *
+     * @return array of the defined layers
+     */
+    function getLayers()
+    {
+        $cf = $this->configuration;
+        unset($cf['default']);
+        return array_keys($cf);
+    }
+
+    function apiVersion()
+    {
+        return '1.1';
+    }
+
+    /**
+     * @return PEAR_Registry
+     */
+    function &getRegistry($use = null)
+    {
+        $layer = $use === null ? 'user' : $use;
+        if (isset($this->_registry[$layer])) {
+            return $this->_registry[$layer];
+        } elseif ($use === null && isset($this->_registry['system'])) {
+            return $this->_registry['system'];
+        } elseif ($use === null && isset($this->_registry['default'])) {
+            return $this->_registry['default'];
+        } elseif ($use) {
+            $a = false;
+            return $a;
+        }
+
+        // only go here if null was passed in
+        echo "CRITICAL ERROR: Registry could not be initialized from any value";
+        exit(1);
+    }
+
+    /**
+     * This is to allow customization like the use of installroot
+     * @param PEAR_Registry
+     * @return bool
+     */
+    function setRegistry(&$reg, $layer = 'user')
+    {
+        if ($this->_noRegistry) {
+            return false;
+        }
+
+        if (!in_array($layer, array('user', 'system'))) {
+            return false;
+        }
+
+        $this->_registry[$layer] = &$reg;
+        if (is_object($reg)) {
+            $this->_registry[$layer]->setConfig($this, false);
+        }
+
+        return true;
+    }
+
+    function noRegistry()
+    {
+        $this->_noRegistry = true;
+    }
+
+    /**
+     * @return PEAR_REST
+     */
+    function &getREST($version, $options = array())
+    {
+        $version = str_replace('.', '', $version);
+        if (!class_exists($class = 'PEAR_REST_' . $version)) {
+            require_once 'PEAR/REST/' . $version . '.php';
+        }
+
+        $remote = &new $class($this, $options);
+        return $remote;
+    }
+
+    /**
+     * The ftp server is set in {@link readFTPConfigFile()}.  It exists only if a
+     * remote configuration file has been specified
+     * @return PEAR_FTP|false
+     */
+    function &getFTP()
+    {
+        if (isset($this->_ftp)) {
+            return $this->_ftp;
+        }
+
+        $a = false;
+        return $a;
+    }
+
+    function _prependPath($path, $prepend)
+    {
+        if (strlen($prepend) > 0) {
+            if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
+                if (preg_match('/^[a-z]:/i', $prepend)) {
+                    $prepend = substr($prepend, 2);
+                } elseif ($prepend{0} != '\\') {
+                    $prepend = "\\$prepend";
+                }
+                $path = substr($path, 0, 2) . $prepend . substr($path, 2);
+            } else {
+                $path = $prepend . $path;
+            }
+        }
+        return $path;
+    }
+
+    /**
+     * @param string|false installation directory to prepend to all _dir variables, or false to
+     *                     disable
+     */
+    function setInstallRoot($root)
+    {
+        if (substr($root, -1) == DIRECTORY_SEPARATOR) {
+            $root = substr($root, 0, -1);
+        }
+        $old = $this->_installRoot;
+        $this->_installRoot = $root;
+        if (($old != $root) && !$this->_noRegistry) {
+            foreach (array_keys($this->_registry) as $layer) {
+                if ($layer == 'ftp' || !isset($this->_registry[$layer])) {
+                    continue;
+                }
+                $this->_registry[$layer] =
+                    &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net'));
+                $this->_registry[$layer]->setConfig($this, false);
+                $this->_regInitialized[$layer] = false;
+            }
+        }
+    }
+}
diff --git a/WEB-INF/lib/pear/PEAR/Dependency2.php b/WEB-INF/lib/pear/PEAR/Dependency2.php
new file mode 100644 (file)
index 0000000..f3ddeb1
--- /dev/null
@@ -0,0 +1,1358 @@
+<?php
+/**
+ * PEAR_Dependency2, advanced dependency validation
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Dependency2.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Required for the PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+
+/**
+ * Dependency check for PEAR packages
+ *
+ * This class handles both version 1.0 and 2.0 dependencies
+ * WARNING: *any* changes to this class must be duplicated in the
+ * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc,
+ * or unit tests will not actually validate the changes
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Dependency2
+{
+    /**
+     * One of the PEAR_VALIDATE_* states
+     * @see PEAR_VALIDATE_NORMAL
+     * @var integer
+     */
+    var $_state;
+
+    /**
+     * Command-line options to install/upgrade/uninstall commands
+     * @param array
+     */
+    var $_options;
+
+    /**
+     * @var OS_Guess
+     */
+    var $_os;
+
+    /**
+     * @var PEAR_Registry
+     */
+    var $_registry;
+
+    /**
+     * @var PEAR_Config
+     */
+    var $_config;
+
+    /**
+     * @var PEAR_DependencyDB
+     */
+    var $_dependencydb;
+
+    /**
+     * Output of PEAR_Registry::parsedPackageName()
+     * @var array
+     */
+    var $_currentPackage;
+
+    /**
+     * @param PEAR_Config
+     * @param array installation options
+     * @param array format of PEAR_Registry::parsedPackageName()
+     * @param int installation state (one of PEAR_VALIDATE_*)
+     */
+    function PEAR_Dependency2(&$config, $installoptions, $package,
+                              $state = PEAR_VALIDATE_INSTALLING)
+    {
+        $this->_config = &$config;
+        if (!class_exists('PEAR_DependencyDB')) {
+            require_once 'PEAR/DependencyDB.php';
+        }
+
+        if (isset($installoptions['packagingroot'])) {
+            // make sure depdb is in the right location
+            $config->setInstallRoot($installoptions['packagingroot']);
+        }
+
+        $this->_registry = &$config->getRegistry();
+        $this->_dependencydb = &PEAR_DependencyDB::singleton($config);
+        if (isset($installoptions['packagingroot'])) {
+            $config->setInstallRoot(false);
+        }
+
+        $this->_options = $installoptions;
+        $this->_state = $state;
+        if (!class_exists('OS_Guess')) {
+            require_once 'OS/Guess.php';
+        }
+
+        $this->_os = new OS_Guess;
+        $this->_currentPackage = $package;
+    }
+
+    function _getExtraString($dep)
+    {
+        $extra = ' (';
+        if (isset($dep['uri'])) {
+            return '';
+        }
+
+        if (isset($dep['recommended'])) {
+            $extra .= 'recommended version ' . $dep['recommended'];
+        } else {
+            if (isset($dep['min'])) {
+                $extra .= 'version >= ' . $dep['min'];
+            }
+
+            if (isset($dep['max'])) {
+                if ($extra != ' (') {
+                    $extra .= ', ';
+                }
+                $extra .= 'version <= ' . $dep['max'];
+            }
+
+            if (isset($dep['exclude'])) {
+                if (!is_array($dep['exclude'])) {
+                    $dep['exclude'] = array($dep['exclude']);
+                }
+
+                if ($extra != ' (') {
+                    $extra .= ', ';
+                }
+
+                $extra .= 'excluded versions: ';
+                foreach ($dep['exclude'] as $i => $exclude) {
+                    if ($i) {
+                        $extra .= ', ';
+                    }
+                    $extra .= $exclude;
+                }
+            }
+        }
+
+        $extra .= ')';
+        if ($extra == ' ()') {
+            $extra = '';
+        }
+
+        return $extra;
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function getPHP_OS()
+    {
+        return PHP_OS;
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function getsysname()
+    {
+        return $this->_os->getSysname();
+    }
+
+    /**
+     * Specify a dependency on an OS.  Use arch for detailed os/processor information
+     *
+     * There are two generic OS dependencies that will be the most common, unix and windows.
+     * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix
+     */
+    function validateOsDependency($dep)
+    {
+        if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+            return true;
+        }
+
+        if ($dep['name'] == '*') {
+            return true;
+        }
+
+        $not = isset($dep['conflicts']) ? true : false;
+        switch (strtolower($dep['name'])) {
+            case 'windows' :
+                if ($not) {
+                    if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') {
+                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                            return $this->raiseError("Cannot install %s on Windows");
+                        }
+
+                        return $this->warning("warning: Cannot install %s on Windows");
+                    }
+                } else {
+                    if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') {
+                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                            return $this->raiseError("Can only install %s on Windows");
+                        }
+
+                        return $this->warning("warning: Can only install %s on Windows");
+                    }
+                }
+            break;
+            case 'unix' :
+                $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix');
+                if ($not) {
+                    if (in_array($this->getSysname(), $unices)) {
+                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                            return $this->raiseError("Cannot install %s on any Unix system");
+                        }
+
+                        return $this->warning( "warning: Cannot install %s on any Unix system");
+                    }
+                } else {
+                    if (!in_array($this->getSysname(), $unices)) {
+                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                            return $this->raiseError("Can only install %s on a Unix system");
+                        }
+
+                        return $this->warning("warning: Can only install %s on a Unix system");
+                    }
+                }
+            break;
+            default :
+                if ($not) {
+                    if (strtolower($dep['name']) == strtolower($this->getSysname())) {
+                        if (!isset($this->_options['nodeps']) &&
+                              !isset($this->_options['force'])) {
+                            return $this->raiseError('Cannot install %s on ' . $dep['name'] .
+                                ' operating system');
+                        }
+
+                        return $this->warning('warning: Cannot install %s on ' .
+                            $dep['name'] . ' operating system');
+                    }
+                } else {
+                    if (strtolower($dep['name']) != strtolower($this->getSysname())) {
+                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                            return $this->raiseError('Cannot install %s on ' .
+                                $this->getSysname() .
+                                ' operating system, can only install on ' . $dep['name']);
+                        }
+
+                        return $this->warning('warning: Cannot install %s on ' .
+                            $this->getSysname() .
+                            ' operating system, can only install on ' . $dep['name']);
+                    }
+                }
+        }
+        return true;
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function matchSignature($pattern)
+    {
+        return $this->_os->matchSignature($pattern);
+    }
+
+    /**
+     * Specify a complex dependency on an OS/processor/kernel version,
+     * Use OS for simple operating system dependency.
+     *
+     * This is the only dependency that accepts an eregable pattern.  The pattern
+     * will be matched against the php_uname() output parsed by OS_Guess
+     */
+    function validateArchDependency($dep)
+    {
+        if ($this->_state != PEAR_VALIDATE_INSTALLING) {
+            return true;
+        }
+
+        $not = isset($dep['conflicts']) ? true : false;
+        if (!$this->matchSignature($dep['pattern'])) {
+            if (!$not) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s Architecture dependency failed, does not ' .
+                        'match "' . $dep['pattern'] . '"');
+                }
+
+                return $this->warning('warning: %s Architecture dependency failed, does ' .
+                    'not match "' . $dep['pattern'] . '"');
+            }
+
+            return true;
+        }
+
+        if ($not) {
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s Architecture dependency failed, required "' .
+                    $dep['pattern'] . '"');
+            }
+
+            return $this->warning('warning: %s Architecture dependency failed, ' .
+                'required "' . $dep['pattern'] . '"');
+        }
+
+        return true;
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function extension_loaded($name)
+    {
+        return extension_loaded($name);
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function phpversion($name = null)
+    {
+        if ($name !== null) {
+            return phpversion($name);
+        }
+
+        return phpversion();
+    }
+
+    function validateExtensionDependency($dep, $required = true)
+    {
+        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+            return true;
+        }
+
+        $loaded = $this->extension_loaded($dep['name']);
+        $extra  = $this->_getExtraString($dep);
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+        }
+
+        if (!isset($dep['min']) && !isset($dep['max']) &&
+            !isset($dep['recommended']) && !isset($dep['exclude'])
+        ) {
+            if ($loaded) {
+                if (isset($dep['conflicts'])) {
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s conflicts with PHP extension "' .
+                            $dep['name'] . '"' . $extra);
+                    }
+
+                    return $this->warning('warning: %s conflicts with PHP extension "' .
+                        $dep['name'] . '"' . $extra);
+                }
+
+                return true;
+            }
+
+            if (isset($dep['conflicts'])) {
+                return true;
+            }
+
+            if ($required) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires PHP extension "' .
+                        $dep['name'] . '"' . $extra);
+                }
+
+                return $this->warning('warning: %s requires PHP extension "' .
+                    $dep['name'] . '"' . $extra);
+            }
+
+            return $this->warning('%s can optionally use PHP extension "' .
+                $dep['name'] . '"' . $extra);
+        }
+
+        if (!$loaded) {
+            if (isset($dep['conflicts'])) {
+                return true;
+            }
+
+            if (!$required) {
+                return $this->warning('%s can optionally use PHP extension "' .
+                    $dep['name'] . '"' . $extra);
+            }
+
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
+                    '"' . $extra);
+            }
+
+            return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
+                    '"' . $extra);
+        }
+
+        $version = (string) $this->phpversion($dep['name']);
+        if (empty($version)) {
+            $version = '0';
+        }
+
+        $fail = false;
+        if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) {
+            $fail = true;
+        }
+
+        if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) {
+            $fail = true;
+        }
+
+        if ($fail && !isset($dep['conflicts'])) {
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
+                    '"' . $extra . ', installed version is ' . $version);
+            }
+
+            return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
+                '"' . $extra . ', installed version is ' . $version);
+        } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) {
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s conflicts with PHP extension "' .
+                    $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+            }
+
+            return $this->warning('warning: %s conflicts with PHP extension "' .
+                $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+        }
+
+        if (isset($dep['exclude'])) {
+            foreach ($dep['exclude'] as $exclude) {
+                if (version_compare($version, $exclude, '==')) {
+                    if (isset($dep['conflicts'])) {
+                        continue;
+                    }
+
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s is not compatible with PHP extension "' .
+                            $dep['name'] . '" version ' .
+                            $exclude);
+                    }
+
+                    return $this->warning('warning: %s is not compatible with PHP extension "' .
+                        $dep['name'] . '" version ' .
+                        $exclude);
+                } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s conflicts with PHP extension "' .
+                            $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+                    }
+
+                    return $this->warning('warning: %s conflicts with PHP extension "' .
+                        $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+                }
+            }
+        }
+
+        if (isset($dep['recommended'])) {
+            if (version_compare($version, $dep['recommended'], '==')) {
+                return true;
+            }
+
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] .
+                    ' version "' . $version . '"' .
+                    ' is not the recommended version "' . $dep['recommended'] .
+                    '", but may be compatible, use --force to install');
+            }
+
+            return $this->warning('warning: %s dependency: PHP extension ' .
+                $dep['name'] . ' version "' . $version . '"' .
+                ' is not the recommended version "' . $dep['recommended'].'"');
+        }
+
+        return true;
+    }
+
+    function validatePhpDependency($dep)
+    {
+        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+            return true;
+        }
+
+        $version = $this->phpversion();
+        $extra   = $this->_getExtraString($dep);
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+        }
+
+        if (isset($dep['min'])) {
+            if (!version_compare($version, $dep['min'], '>=')) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires PHP' .
+                        $extra . ', installed version is ' . $version);
+                }
+
+                return $this->warning('warning: %s requires PHP' .
+                    $extra . ', installed version is ' . $version);
+            }
+        }
+
+        if (isset($dep['max'])) {
+            if (!version_compare($version, $dep['max'], '<=')) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires PHP' .
+                        $extra . ', installed version is ' . $version);
+                }
+
+                return $this->warning('warning: %s requires PHP' .
+                    $extra . ', installed version is ' . $version);
+            }
+        }
+
+        if (isset($dep['exclude'])) {
+            foreach ($dep['exclude'] as $exclude) {
+                if (version_compare($version, $exclude, '==')) {
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s is not compatible with PHP version ' .
+                            $exclude);
+                    }
+
+                    return $this->warning(
+                        'warning: %s is not compatible with PHP version ' .
+                        $exclude);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * This makes unit-testing a heck of a lot easier
+     */
+    function getPEARVersion()
+    {
+        return '1.9.4';
+    }
+
+    function validatePearinstallerDependency($dep)
+    {
+        $pearversion = $this->getPEARVersion();
+        $extra = $this->_getExtraString($dep);
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+        }
+
+        if (version_compare($pearversion, $dep['min'], '<')) {
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s requires PEAR Installer' . $extra .
+                    ', installed version is ' . $pearversion);
+            }
+
+            return $this->warning('warning: %s requires PEAR Installer' . $extra .
+                ', installed version is ' . $pearversion);
+        }
+
+        if (isset($dep['max'])) {
+            if (version_compare($pearversion, $dep['max'], '>')) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires PEAR Installer' . $extra .
+                        ', installed version is ' . $pearversion);
+                }
+
+                return $this->warning('warning: %s requires PEAR Installer' . $extra .
+                    ', installed version is ' . $pearversion);
+            }
+        }
+
+        if (isset($dep['exclude'])) {
+            if (!isset($dep['exclude'][0])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+
+            foreach ($dep['exclude'] as $exclude) {
+                if (version_compare($exclude, $pearversion, '==')) {
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s is not compatible with PEAR Installer ' .
+                            'version ' . $exclude);
+                    }
+
+                    return $this->warning('warning: %s is not compatible with PEAR ' .
+                        'Installer version ' . $exclude);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    function validateSubpackageDependency($dep, $required, $params)
+    {
+        return $this->validatePackageDependency($dep, $required, $params);
+    }
+
+    /**
+     * @param array dependency information (2.0 format)
+     * @param boolean whether this is a required dependency
+     * @param array a list of downloaded packages to be installed, if any
+     * @param boolean if true, then deps on pear.php.net that fail will also check
+     *                against pecl.php.net packages to accomodate extensions that have
+     *                moved to pecl.php.net from pear.php.net
+     */
+    function validatePackageDependency($dep, $required, $params, $depv1 = false)
+    {
+        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+            return true;
+        }
+
+        if (isset($dep['providesextension'])) {
+            if ($this->extension_loaded($dep['providesextension'])) {
+                $save = $dep;
+                $subdep = $dep;
+                $subdep['name'] = $subdep['providesextension'];
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                $ret = $this->validateExtensionDependency($subdep, $required);
+                PEAR::popErrorHandling();
+                if (!PEAR::isError($ret)) {
+                    return true;
+                }
+            }
+        }
+
+        if ($this->_state == PEAR_VALIDATE_INSTALLING) {
+            return $this->_validatePackageInstall($dep, $required, $depv1);
+        }
+
+        if ($this->_state == PEAR_VALIDATE_DOWNLOADING) {
+            return $this->_validatePackageDownload($dep, $required, $params, $depv1);
+        }
+    }
+
+    function _validatePackageDownload($dep, $required, $params, $depv1 = false)
+    {
+        $dep['package'] = $dep['name'];
+        if (isset($dep['uri'])) {
+            $dep['channel'] = '__uri';
+        }
+
+        $depname = $this->_registry->parsedPackageNameToString($dep, true);
+        $found = false;
+        foreach ($params as $param) {
+            if ($param->isEqual(
+                  array('package' => $dep['name'],
+                        'channel' => $dep['channel']))) {
+                $found = true;
+                break;
+            }
+
+            if ($depv1 && $dep['channel'] == 'pear.php.net') {
+                if ($param->isEqual(
+                  array('package' => $dep['name'],
+                        'channel' => 'pecl.php.net'))) {
+                    $found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!$found && isset($dep['providesextension'])) {
+            foreach ($params as $param) {
+                if ($param->isExtension($dep['providesextension'])) {
+                    $found = true;
+                    break;
+                }
+            }
+        }
+
+        if ($found) {
+            $version = $param->getVersion();
+            $installed = false;
+            $downloaded = true;
+        } else {
+            if ($this->_registry->packageExists($dep['name'], $dep['channel'])) {
+                $installed = true;
+                $downloaded = false;
+                $version = $this->_registry->packageinfo($dep['name'], 'version',
+                    $dep['channel']);
+            } else {
+                if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'],
+                      'pear.php.net')) {
+                    $installed = true;
+                    $downloaded = false;
+                    $version = $this->_registry->packageinfo($dep['name'], 'version',
+                        'pear.php.net');
+                } else {
+                    $version = 'not installed or downloaded';
+                    $installed = false;
+                    $downloaded = false;
+                }
+            }
+        }
+
+        $extra = $this->_getExtraString($dep);
+        if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
+            $dep['exclude'] = array($dep['exclude']);
+        }
+
+        if (!isset($dep['min']) && !isset($dep['max']) &&
+              !isset($dep['recommended']) && !isset($dep['exclude'])
+        ) {
+            if ($installed || $downloaded) {
+                $installed = $installed ? 'installed' : 'downloaded';
+                if (isset($dep['conflicts'])) {
+                    $rest = '';
+                    if ($version) {
+                        $rest = ", $installed version is " . $version;
+                    }
+
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest);
+                    }
+
+                    return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest);
+                }
+
+                return true;
+            }
+
+            if (isset($dep['conflicts'])) {
+                return true;
+            }
+
+            if ($required) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
+                }
+
+                return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
+            }
+
+            return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
+        }
+
+        if (!$installed && !$downloaded) {
+            if (isset($dep['conflicts'])) {
+                return true;
+            }
+
+            if ($required) {
+                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                    return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
+                }
+
+                return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
+            }
+
+            return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
+        }
+
+        $fail = false;
+        if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) {
+            $fail = true;
+        }
+
+        if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) {
+            $fail = true;
+        }
+
+        if ($fail && !isset($dep['conflicts'])) {
+            $installed = $installed ? 'installed' : 'downloaded';
+            $dep['package'] = $dep['name'];
+            $dep = $this->_registry->parsedPackageNameToString($dep, true);
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s requires package "' . $depname . '"' .
+                    $extra . ", $installed version is " . $version);
+            }
+
+            return $this->warning('warning: %s requires package "' . $depname . '"' .
+                $extra . ", $installed version is " . $version);
+        } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail &&
+              isset($dep['conflicts']) && !isset($dep['exclude'])) {
+            $installed = $installed ? 'installed' : 'downloaded';
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra .
+                    ", $installed version is " . $version);
+            }
+
+            return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
+                $extra . ", $installed version is " . $version);
+        }
+
+        if (isset($dep['exclude'])) {
+            $installed = $installed ? 'installed' : 'downloaded';
+            foreach ($dep['exclude'] as $exclude) {
+                if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) {
+                    if (!isset($this->_options['nodeps']) &&
+                          !isset($this->_options['force'])
+                    ) {
+                        return $this->raiseError('%s is not compatible with ' .
+                            $installed . ' package "' .
+                            $depname . '" version ' .
+                            $exclude);
+                    }
+
+                    return $this->warning('warning: %s is not compatible with ' .
+                        $installed . ' package "' .
+                        $depname . '" version ' .
+                        $exclude);
+                } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
+                    $installed = $installed ? 'installed' : 'downloaded';
+                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                        return $this->raiseError('%s conflicts with package "' . $depname . '"' .
+                            $extra . ", $installed version is " . $version);
+                    }
+
+                    return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
+                        $extra . ", $installed version is " . $version);
+                }
+            }
+        }
+
+        if (isset($dep['recommended'])) {
+            $installed = $installed ? 'installed' : 'downloaded';
+            if (version_compare($version, $dep['recommended'], '==')) {
+                return true;
+            }
+
+            if (!$found && $installed) {
+                $param = $this->_registry->getPackage($dep['name'], $dep['channel']);
+            }
+
+            if ($param) {
+                $found = false;
+                foreach ($params as $parent) {
+                    if ($parent->isEqual($this->_currentPackage)) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if ($found) {
+                    if ($param->isCompatible($parent)) {
+                        return true;
+                    }
+                } else { // this is for validPackage() calls
+                    $parent = $this->_registry->getPackage($this->_currentPackage['package'],
+                        $this->_currentPackage['channel']);
+                    if ($parent !== null && $param->isCompatible($parent)) {
+                        return true;
+                    }
+                }
+            }
+
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) &&
+                  !isset($this->_options['loose'])
+            ) {
+                return $this->raiseError('%s dependency package "' . $depname .
+                    '" ' . $installed . ' version ' . $version .
+                    ' is not the recommended version ' . $dep['recommended'] .
+                    ', but may be compatible, use --force to install');
+            }
+
+            return $this->warning('warning: %s dependency package "' . $depname .
+                '" ' . $installed . ' version ' . $version .
+                ' is not the recommended version ' . $dep['recommended']);
+        }
+
+        return true;
+    }
+
+    function _validatePackageInstall($dep, $required, $depv1 = false)
+    {
+        return $this->_validatePackageDownload($dep, $required, array(), $depv1);
+    }
+
+    /**
+     * Verify that uninstalling packages passed in to command line is OK.
+     *
+     * @param PEAR_Installer $dl
+     * @return PEAR_Error|true
+     */
+    function validatePackageUninstall(&$dl)
+    {
+        if (PEAR::isError($this->_dependencydb)) {
+            return $this->_dependencydb;
+        }
+
+        $params = array();
+        // construct an array of "downloaded" packages to fool the package dependency checker
+        // into using these to validate uninstalls of circular dependencies
+        $downloaded = &$dl->getUninstallPackages();
+        foreach ($downloaded as $i => $pf) {
+            if (!class_exists('PEAR_Downloader_Package')) {
+                require_once 'PEAR/Downloader/Package.php';
+            }
+            $dp = &new PEAR_Downloader_Package($dl);
+            $dp->setPackageFile($downloaded[$i]);
+            $params[$i] = &$dp;
+        }
+
+        // check cache
+        $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' .
+            strtolower($this->_currentPackage['package']);
+        if (isset($dl->___uninstall_package_cache)) {
+            $badpackages = $dl->___uninstall_package_cache;
+            if (isset($badpackages[$memyselfandI]['warnings'])) {
+                foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
+                    $dl->log(0, $warning[0]);
+                }
+            }
+
+            if (isset($badpackages[$memyselfandI]['errors'])) {
+                foreach ($badpackages[$memyselfandI]['errors'] as $error) {
+                    if (is_array($error)) {
+                        $dl->log(0, $error[0]);
+                    } else {
+                        $dl->log(0, $error->getMessage());
+                    }
+                }
+
+                if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
+                    return $this->warning(
+                        'warning: %s should not be uninstalled, other installed packages depend ' .
+                        'on this package');
+                }
+
+                return $this->raiseError(
+                    '%s cannot be uninstalled, other installed packages depend on this package');
+            }
+
+            return true;
+        }
+
+        // first, list the immediate parents of each package to be uninstalled
+        $perpackagelist = array();
+        $allparents = array();
+        foreach ($params as $i => $param) {
+            $a = array(
+                'channel' => strtolower($param->getChannel()),
+                'package' => strtolower($param->getPackage())
+            );
+
+            $deps = $this->_dependencydb->getDependentPackages($a);
+            if ($deps) {
+                foreach ($deps as $d) {
+                    $pardeps = $this->_dependencydb->getDependencies($d);
+                    foreach ($pardeps as $dep) {
+                        if (strtolower($dep['dep']['channel']) == $a['channel'] &&
+                              strtolower($dep['dep']['name']) == $a['package']) {
+                            if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) {
+                                $perpackagelist[$a['channel'] . '/' . $a['package']] = array();
+                            }
+                            $perpackagelist[$a['channel'] . '/' . $a['package']][]
+                                = array($d['channel'] . '/' . $d['package'], $dep);
+                            if (!isset($allparents[$d['channel'] . '/' . $d['package']])) {
+                                $allparents[$d['channel'] . '/' . $d['package']] = array();
+                            }
+                            if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) {
+                                $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array();
+                            }
+                            $allparents[$d['channel'] . '/' . $d['package']]
+                                       [$a['channel'] . '/' . $a['package']][]
+                                = array($d, $dep);
+                        }
+                    }
+                }
+            }
+        }
+
+        // next, remove any packages from the parents list that are not installed
+        $remove = array();
+        foreach ($allparents as $parent => $d1) {
+            foreach ($d1 as $d) {
+                if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) {
+                    continue;
+                }
+                $remove[$parent] = true;
+            }
+        }
+
+        // next remove any packages from the parents list that are not passed in for
+        // uninstallation
+        foreach ($allparents as $parent => $d1) {
+            foreach ($d1 as $d) {
+                foreach ($params as $param) {
+                    if (strtolower($param->getChannel()) == $d[0][0]['channel'] &&
+                          strtolower($param->getPackage()) == $d[0][0]['package']) {
+                        // found it
+                        continue 3;
+                    }
+                }
+                $remove[$parent] = true;
+            }
+        }
+
+        // remove all packages whose dependencies fail
+        // save which ones failed for error reporting
+        $badchildren = array();
+        do {
+            $fail = false;
+            foreach ($remove as $package => $unused) {
+                if (!isset($allparents[$package])) {
+                    continue;
+                }
+
+                foreach ($allparents[$package] as $kid => $d1) {
+                    foreach ($d1 as $depinfo) {
+                        if ($depinfo[1]['type'] != 'optional') {
+                            if (isset($badchildren[$kid])) {
+                                continue;
+                            }
+                            $badchildren[$kid] = true;
+                            $remove[$kid] = true;
+                            $fail = true;
+                            continue 2;
+                        }
+                    }
+                }
+                if ($fail) {
+                    // start over, we removed some children
+                    continue 2;
+                }
+            }
+        } while ($fail);
+
+        // next, construct the list of packages that can't be uninstalled
+        $badpackages = array();
+        $save = $this->_currentPackage;
+        foreach ($perpackagelist as $package => $packagedeps) {
+            foreach ($packagedeps as $parent) {
+                if (!isset($remove[$parent[0]])) {
+                    continue;
+                }
+
+                $packagename = $this->_registry->parsePackageName($parent[0]);
+                $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']);
+                $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']);
+                $packagename['package'] = $pa->getPackage();
+                $this->_currentPackage = $packagename;
+                // parent is not present in uninstall list, make sure we can actually
+                // uninstall it (parent dep is optional)
+                $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']);
+                $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']);
+                $parentname['package'] = $pa->getPackage();
+                $parent[1]['dep']['package'] = $parentname['package'];
+                $parent[1]['dep']['channel'] = $parentname['channel'];
+                if ($parent[1]['type'] == 'optional') {
+                    $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl);
+                    if ($test !== true) {
+                        $badpackages[$package]['warnings'][] = $test;
+                    }
+                } else {
+                    $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl);
+                    if ($test !== true) {
+                        $badpackages[$package]['errors'][] = $test;
+                    }
+                }
+            }
+        }
+
+        $this->_currentPackage          = $save;
+        $dl->___uninstall_package_cache = $badpackages;
+        if (isset($badpackages[$memyselfandI])) {
+            if (isset($badpackages[$memyselfandI]['warnings'])) {
+                foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
+                    $dl->log(0, $warning[0]);
+                }
+            }
+
+            if (isset($badpackages[$memyselfandI]['errors'])) {
+                foreach ($badpackages[$memyselfandI]['errors'] as $error) {
+                    if (is_array($error)) {
+                        $dl->log(0, $error[0]);
+                    } else {
+                        $dl->log(0, $error->getMessage());
+                    }
+                }
+
+                if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
+                    return $this->warning(
+                        'warning: %s should not be uninstalled, other installed packages depend ' .
+                        'on this package');
+                }
+
+                return $this->raiseError(
+                    '%s cannot be uninstalled, other installed packages depend on this package');
+            }
+        }
+
+        return true;
+    }
+
+    function _validatePackageUninstall($dep, $required, $dl)
+    {
+        $depname = $this->_registry->parsedPackageNameToString($dep, true);
+        $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']);
+        if (!$version) {
+            return true;
+        }
+
+        $extra = $this->_getExtraString($dep);
+        if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
+            $dep['exclude'] = array($dep['exclude']);
+        }
+
+        if (isset($dep['conflicts'])) {
+            return true; // uninstall OK - these packages conflict (probably installed with --force)
+        }
+
+        if (!isset($dep['min']) && !isset($dep['max'])) {
+            if (!$required) {
+                return $this->warning('"' . $depname . '" can be optionally used by ' .
+                        'installed package %s' . $extra);
+            }
+
+            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+                return $this->raiseError('"' . $depname . '" is required by ' .
+                    'installed package %s' . $extra);
+            }
+
+            return $this->warning('warning: "' . $depname . '" is required by ' .
+                'installed package %s' . $extra);
+        }
+
+        $fail = false;
+        if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) {
+            $fail = true;
+        }
+
+        if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) {
+            $fail = true;
+        }
+
+        // we re-use this variable, preserve the original value
+        $saverequired = $required;
+        if (!$required) {
+            return $this->warning($depname . $extra . ' can be optionally used by installed package' .
+                    ' "%s"');
+        }
+
+        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+            return $this->raiseError($depname . $extra . ' is required by installed package' .
+                ' "%s"');
+        }
+
+        return $this->raiseError('warning: ' . $depname . $extra .
+            ' is required by installed package "%s"');
+    }
+
+    /**
+     * validate a downloaded package against installed packages
+     *
+     * As of PEAR 1.4.3, this will only validate
+     *
+     * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     *              $pkg package identifier (either
+     *                   array('package' => blah, 'channel' => blah) or an array with
+     *                   index 'info' referencing an object)
+     * @param PEAR_Downloader $dl
+     * @param array $params full list of packages to install
+     * @return true|PEAR_Error
+     */
+    function validatePackage($pkg, &$dl, $params = array())
+    {
+        if (is_array($pkg) && isset($pkg['info'])) {
+            $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']);
+        } else {
+            $deps = $this->_dependencydb->getDependentPackageDependencies($pkg);
+        }
+
+        $fail = false;
+        if ($deps) {
+            if (!class_exists('PEAR_Downloader_Package')) {
+                require_once 'PEAR/Downloader/Package.php';
+            }
+
+            $dp = &new PEAR_Downloader_Package($dl);
+            if (is_object($pkg)) {
+                $dp->setPackageFile($pkg);
+            } else {
+                $dp->setDownloadURL($pkg);
+            }
+
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            foreach ($deps as $channel => $info) {
+                foreach ($info as $package => $ds) {
+                    foreach ($params as $packd) {
+                        if (strtolower($packd->getPackage()) == strtolower($package) &&
+                              $packd->getChannel() == $channel) {
+                            $dl->log(3, 'skipping installed package check of "' .
+                                        $this->_registry->parsedPackageNameToString(
+                                            array('channel' => $channel, 'package' => $package),
+                                            true) .
+                                        '", version "' . $packd->getVersion() . '" will be ' .
+                                        'downloaded and installed');
+                            continue 2; // jump to next package
+                        }
+                    }
+
+                    foreach ($ds as $d) {
+                        $checker = &new PEAR_Dependency2($this->_config, $this->_options,
+                            array('channel' => $channel, 'package' => $package), $this->_state);
+                        $dep = $d['dep'];
+                        $required = $d['type'] == 'required';
+                        $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp));
+                        if (is_array($ret)) {
+                            $dl->log(0, $ret[0]);
+                        } elseif (PEAR::isError($ret)) {
+                            $dl->log(0, $ret->getMessage());
+                            $fail = true;
+                        }
+                    }
+                }
+            }
+            PEAR::popErrorHandling();
+        }
+
+        if ($fail) {
+            return $this->raiseError(
+                '%s cannot be installed, conflicts with installed packages');
+        }
+
+        return true;
+    }
+
+    /**
+     * validate a package.xml 1.0 dependency
+     */
+    function validateDependency1($dep, $params = array())
+    {
+        if (!isset($dep['optional'])) {
+            $dep['optional'] = 'no';
+        }
+
+        list($newdep, $type) = $this->normalizeDep($dep);
+        if (!$newdep) {
+            return $this->raiseError("Invalid Dependency");
+        }
+
+        if (method_exists($this, "validate{$type}Dependency")) {
+            return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no',
+                $params, true);
+        }
+    }
+
+    /**
+     * Convert a 1.0 dep into a 2.0 dep
+     */
+    function normalizeDep($dep)
+    {
+        $types = array(
+            'pkg' => 'Package',
+            'ext' => 'Extension',
+            'os' => 'Os',
+            'php' => 'Php'
+        );
+
+        if (!isset($types[$dep['type']])) {
+            return array(false, false);
+        }
+
+        $type = $types[$dep['type']];
+
+        $newdep = array();
+        switch ($type) {
+            case 'Package' :
+                $newdep['channel'] = 'pear.php.net';
+            case 'Extension' :
+            case 'Os' :
+                $newdep['name'] = $dep['name'];
+            break;
+        }
+
+        $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']);
+        switch ($dep['rel']) {
+            case 'has' :
+                return array($newdep, $type);
+            break;
+            case 'not' :
+                $newdep['conflicts'] = true;
+            break;
+            case '>=' :
+            case '>' :
+                $newdep['min'] = $dep['version'];
+                if ($dep['rel'] == '>') {
+                    $newdep['exclude'] = $dep['version'];
+                }
+            break;
+            case '<=' :
+            case '<' :
+                $newdep['max'] = $dep['version'];
+                if ($dep['rel'] == '<') {
+                    $newdep['exclude'] = $dep['version'];
+                }
+            break;
+            case 'ne' :
+            case '!=' :
+                $newdep['min'] = '0';
+                $newdep['max'] = '100000';
+                $newdep['exclude'] = $dep['version'];
+            break;
+            case '==' :
+                $newdep['min'] = $dep['version'];
+                $newdep['max'] = $dep['version'];
+            break;
+        }
+        if ($type == 'Php') {
+            if (!isset($newdep['min'])) {
+                $newdep['min'] = '4.4.0';
+            }
+
+            if (!isset($newdep['max'])) {
+                $newdep['max'] = '6.0.0';
+            }
+        }
+        return array($newdep, $type);
+    }
+
+    /**
+     * Converts text comparing operators to them sign equivalents
+     *
+     * Example: 'ge' to '>='
+     *
+     * @access public
+     * @param  string Operator
+     * @return string Sign equivalent
+     */
+    function signOperator($operator)
+    {
+        switch($operator) {
+            case 'lt': return '<';
+            case 'le': return '<=';
+            case 'gt': return '>';
+            case 'ge': return '>=';
+            case 'eq': return '==';
+            case 'ne': return '!=';
+            default:
+                return $operator;
+        }
+    }
+
+    function raiseError($msg)
+    {
+        if (isset($this->_options['ignore-errors'])) {
+            return $this->warning($msg);
+        }
+
+        return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString(
+            $this->_currentPackage, true)));
+    }
+
+    function warning($msg)
+    {
+        return array(sprintf($msg, $this->_registry->parsedPackageNameToString(
+            $this->_currentPackage, true)));
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/DependencyDB.php b/WEB-INF/lib/pear/PEAR/DependencyDB.php
new file mode 100644 (file)
index 0000000..948f0c9
--- /dev/null
@@ -0,0 +1,769 @@
+<?php
+/**
+ * PEAR_DependencyDB, advanced installed packages dependency database
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: DependencyDB.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Config.php';
+
+$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array();
+/**
+ * Track dependency relationships between installed packages
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Tomas V.V.Cox <cox@idec.net.com>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_DependencyDB
+{
+    // {{{ properties
+
+    /**
+     * This is initialized by {@link setConfig()}
+     * @var PEAR_Config
+     * @access private
+     */
+    var $_config;
+    /**
+     * This is initialized by {@link setConfig()}
+     * @var PEAR_Registry
+     * @access private
+     */
+    var $_registry;
+    /**
+     * Filename of the dependency DB (usually .depdb)
+     * @var string
+     * @access private
+     */
+    var $_depdb = false;
+    /**
+     * File name of the lockfile (usually .depdblock)
+     * @var string
+     * @access private
+     */
+    var $_lockfile = false;
+    /**
+     * Open file resource for locking the lockfile
+     * @var resource|false
+     * @access private
+     */
+    var $_lockFp = false;
+    /**
+     * API version of this class, used to validate a file on-disk
+     * @var string
+     * @access private
+     */
+    var $_version = '1.0';
+    /**
+     * Cached dependency database file
+     * @var array|null
+     * @access private
+     */
+    var $_cache;
+
+    // }}}
+    // {{{ & singleton()
+
+    /**
+     * Get a raw dependency database.  Calls setConfig() and assertDepsDB()
+     * @param PEAR_Config
+     * @param string|false full path to the dependency database, or false to use default
+     * @return PEAR_DependencyDB|PEAR_Error
+     * @static
+     */
+    function &singleton(&$config, $depdb = false)
+    {
+        $phpdir = $config->get('php_dir', null, 'pear.php.net');
+        if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) {
+            $a = new PEAR_DependencyDB;
+            $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a;
+            $a->setConfig($config, $depdb);
+            $e = $a->assertDepsDB();
+            if (PEAR::isError($e)) {
+                return $e;
+            }
+        }
+
+        return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir];
+    }
+
+    /**
+     * Set up the registry/location of dependency DB
+     * @param PEAR_Config|false
+     * @param string|false full path to the dependency database, or false to use default
+     */
+    function setConfig(&$config, $depdb = false)
+    {
+        if (!$config) {
+            $this->_config = &PEAR_Config::singleton();
+        } else {
+            $this->_config = &$config;
+        }
+
+        $this->_registry = &$this->_config->getRegistry();
+        if (!$depdb) {
+            $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') .
+                DIRECTORY_SEPARATOR . '.depdb';
+        } else {
+            $this->_depdb = $depdb;
+        }
+
+        $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock';
+    }
+    // }}}
+
+    function hasWriteAccess()
+    {
+        if (!file_exists($this->_depdb)) {
+            $dir = $this->_depdb;
+            while ($dir && $dir != '.') {
+                $dir = dirname($dir); // cd ..
+                if ($dir != '.' && file_exists($dir)) {
+                    if (is_writeable($dir)) {
+                        return true;
+                    }
+
+                    return false;
+                }
+            }
+
+            return false;
+        }
+
+        return is_writeable($this->_depdb);
+    }
+
+    // {{{ assertDepsDB()
+
+    /**
+     * Create the dependency database, if it doesn't exist.  Error if the database is
+     * newer than the code reading it.
+     * @return void|PEAR_Error
+     */
+    function assertDepsDB()
+    {
+        if (!is_file($this->_depdb)) {
+            $this->rebuildDB();
+            return;
+        }
+
+        $depdb = $this->_getDepDB();
+        // Datatype format has been changed, rebuild the Deps DB
+        if ($depdb['_version'] < $this->_version) {
+            $this->rebuildDB();
+        }
+
+        if ($depdb['_version']{0} > $this->_version{0}) {
+            return PEAR::raiseError('Dependency database is version ' .
+                $depdb['_version'] . ', and we are version ' .
+                $this->_version . ', cannot continue');
+        }
+    }
+
+    /**
+     * Get a list of installed packages that depend on this package
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+     * @return array|false
+     */
+    function getDependentPackages(&$pkg)
+    {
+        $data = $this->_getDepDB();
+        if (is_object($pkg)) {
+            $channel = strtolower($pkg->getChannel());
+            $package = strtolower($pkg->getPackage());
+        } else {
+            $channel = strtolower($pkg['channel']);
+            $package = strtolower($pkg['package']);
+        }
+
+        if (isset($data['packages'][$channel][$package])) {
+            return $data['packages'][$channel][$package];
+        }
+
+        return false;
+    }
+
+    /**
+     * Get a list of the actual dependencies of installed packages that depend on
+     * a package.
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+     * @return array|false
+     */
+    function getDependentPackageDependencies(&$pkg)
+    {
+        $data = $this->_getDepDB();
+        if (is_object($pkg)) {
+            $channel = strtolower($pkg->getChannel());
+            $package = strtolower($pkg->getPackage());
+        } else {
+            $channel = strtolower($pkg['channel']);
+            $package = strtolower($pkg['package']);
+        }
+
+        $depend = $this->getDependentPackages($pkg);
+        if (!$depend) {
+            return false;
+        }
+
+        $dependencies = array();
+        foreach ($depend as $info) {
+            $temp = $this->getDependencies($info);
+            foreach ($temp as $dep) {
+                if (
+                    isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) &&
+                    strtolower($dep['dep']['channel']) == $channel &&
+                    strtolower($dep['dep']['name']) == $package
+                ) {
+                    if (!isset($dependencies[$info['channel']])) {
+                        $dependencies[$info['channel']] = array();
+                    }
+
+                    if (!isset($dependencies[$info['channel']][$info['package']])) {
+                        $dependencies[$info['channel']][$info['package']] = array();
+                    }
+                    $dependencies[$info['channel']][$info['package']][] = $dep;
+                }
+            }
+        }
+
+        return $dependencies;
+    }
+
+    /**
+     * Get a list of dependencies of this installed package
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+     * @return array|false
+     */
+    function getDependencies(&$pkg)
+    {
+        if (is_object($pkg)) {
+            $channel = strtolower($pkg->getChannel());
+            $package = strtolower($pkg->getPackage());
+        } else {
+            $channel = strtolower($pkg['channel']);
+            $package = strtolower($pkg['package']);
+        }
+
+        $data = $this->_getDepDB();
+        if (isset($data['dependencies'][$channel][$package])) {
+            return $data['dependencies'][$channel][$package];
+        }
+
+        return false;
+    }
+
+    /**
+     * Determine whether $parent depends on $child, near or deep
+     * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
+     * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
+     */
+    function dependsOn($parent, $child)
+    {
+        $c = array();
+        $this->_getDepDB();
+        return $this->_dependsOn($parent, $child, $c);
+    }
+
+    function _dependsOn($parent, $child, &$checked)
+    {
+        if (is_object($parent)) {
+            $channel = strtolower($parent->getChannel());
+            $package = strtolower($parent->getPackage());
+        } else {
+            $channel = strtolower($parent['channel']);
+            $package = strtolower($parent['package']);
+        }
+
+        if (is_object($child)) {
+            $depchannel = strtolower($child->getChannel());
+            $deppackage = strtolower($child->getPackage());
+        } else {
+            $depchannel = strtolower($child['channel']);
+            $deppackage = strtolower($child['package']);
+        }
+
+        if (isset($checked[$channel][$package][$depchannel][$deppackage])) {
+            return false; // avoid endless recursion
+        }
+
+        $checked[$channel][$package][$depchannel][$deppackage] = true;
+        if (!isset($this->_cache['dependencies'][$channel][$package])) {
+            return false;
+        }
+
+        foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
+            if (isset($info['dep']['uri'])) {
+                if (is_object($child)) {
+                    if ($info['dep']['uri'] == $child->getURI()) {
+                        return true;
+                    }
+                } elseif (isset($child['uri'])) {
+                    if ($info['dep']['uri'] == $child['uri']) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            if (strtolower($info['dep']['channel']) == $depchannel &&
+                  strtolower($info['dep']['name']) == $deppackage) {
+                return true;
+            }
+        }
+
+        foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
+            if (isset($info['dep']['uri'])) {
+                if ($this->_dependsOn(array(
+                        'uri' => $info['dep']['uri'],
+                        'package' => $info['dep']['name']), $child, $checked)) {
+                    return true;
+                }
+            } else {
+                if ($this->_dependsOn(array(
+                        'channel' => $info['dep']['channel'],
+                        'package' => $info['dep']['name']), $child, $checked)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Register dependencies of a package that is being installed or upgraded
+     * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2
+     */
+    function installPackage(&$package)
+    {
+        $data = $this->_getDepDB();
+        unset($this->_cache);
+        $this->_setPackageDeps($data, $package);
+        $this->_writeDepDB($data);
+    }
+
+    /**
+     * Remove dependencies of a package that is being uninstalled, or upgraded.
+     *
+     * Upgraded packages first uninstall, then install
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have
+     *        indices 'channel' and 'package'
+     */
+    function uninstallPackage(&$pkg)
+    {
+        $data = $this->_getDepDB();
+        unset($this->_cache);
+        if (is_object($pkg)) {
+            $channel = strtolower($pkg->getChannel());
+            $package = strtolower($pkg->getPackage());
+        } else {
+            $channel = strtolower($pkg['channel']);
+            $package = strtolower($pkg['package']);
+        }
+
+        if (!isset($data['dependencies'][$channel][$package])) {
+            return true;
+        }
+
+        foreach ($data['dependencies'][$channel][$package] as $dep) {
+            $found      = false;
+            $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']);
+            $depname    = strtolower($dep['dep']['name']);
+            if (isset($data['packages'][$depchannel][$depname])) {
+                foreach ($data['packages'][$depchannel][$depname] as $i => $info) {
+                    if ($info['channel'] == $channel && $info['package'] == $package) {
+                        $found = true;
+                        break;
+                    }
+                }
+            }
+
+            if ($found) {
+                unset($data['packages'][$depchannel][$depname][$i]);
+                if (!count($data['packages'][$depchannel][$depname])) {
+                    unset($data['packages'][$depchannel][$depname]);
+                    if (!count($data['packages'][$depchannel])) {
+                        unset($data['packages'][$depchannel]);
+                    }
+                } else {
+                    $data['packages'][$depchannel][$depname] =
+                        array_values($data['packages'][$depchannel][$depname]);
+                }
+            }
+        }
+
+        unset($data['dependencies'][$channel][$package]);
+        if (!count($data['dependencies'][$channel])) {
+            unset($data['dependencies'][$channel]);
+        }
+
+        if (!count($data['dependencies'])) {
+            unset($data['dependencies']);
+        }
+
+        if (!count($data['packages'])) {
+            unset($data['packages']);
+        }
+
+        $this->_writeDepDB($data);
+    }
+
+    /**
+     * Rebuild the dependency DB by reading registry entries.
+     * @return true|PEAR_Error
+     */
+    function rebuildDB()
+    {
+        $depdb = array('_version' => $this->_version);
+        if (!$this->hasWriteAccess()) {
+            // allow startup for read-only with older Registry
+            return $depdb;
+        }
+
+        $packages = $this->_registry->listAllPackages();
+        if (PEAR::isError($packages)) {
+            return $packages;
+        }
+
+        foreach ($packages as $channel => $ps) {
+            foreach ($ps as $package) {
+                $package = $this->_registry->getPackage($package, $channel);
+                if (PEAR::isError($package)) {
+                    return $package;
+                }
+                $this->_setPackageDeps($depdb, $package);
+            }
+        }
+
+        $error = $this->_writeDepDB($depdb);
+        if (PEAR::isError($error)) {
+            return $error;
+        }
+
+        $this->_cache = $depdb;
+        return true;
+    }
+
+    /**
+     * Register usage of the dependency DB to prevent race conditions
+     * @param int one of the LOCK_* constants
+     * @return true|PEAR_Error
+     * @access private
+     */
+    function _lock($mode = LOCK_EX)
+    {
+        if (stristr(php_uname(), 'Windows 9')) {
+            return true;
+        }
+
+        if ($mode != LOCK_UN && is_resource($this->_lockFp)) {
+            // XXX does not check type of lock (LOCK_SH/LOCK_EX)
+            return true;
+        }
+
+        $open_mode = 'w';
+        // XXX People reported problems with LOCK_SH and 'w'
+        if ($mode === LOCK_SH) {
+            if (!file_exists($this->_lockfile)) {
+                touch($this->_lockfile);
+            } elseif (!is_file($this->_lockfile)) {
+                return PEAR::raiseError('could not create Dependency lock file, ' .
+                    'it exists and is not a regular file');
+            }
+            $open_mode = 'r';
+        }
+
+        if (!is_resource($this->_lockFp)) {
+            $this->_lockFp = @fopen($this->_lockfile, $open_mode);
+        }
+
+        if (!is_resource($this->_lockFp)) {
+            return PEAR::raiseError("could not create Dependency lock file" .
+                                     (isset($php_errormsg) ? ": " . $php_errormsg : ""));
+        }
+
+        if (!(int)flock($this->_lockFp, $mode)) {
+            switch ($mode) {
+                case LOCK_SH: $str = 'shared';    break;
+                case LOCK_EX: $str = 'exclusive'; break;
+                case LOCK_UN: $str = 'unlock';    break;
+                default:      $str = 'unknown';   break;
+            }
+
+            return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)");
+        }
+
+        return true;
+    }
+
+    /**
+     * Release usage of dependency DB
+     * @return true|PEAR_Error
+     * @access private
+     */
+    function _unlock()
+    {
+        $ret = $this->_lock(LOCK_UN);
+        if (is_resource($this->_lockFp)) {
+            fclose($this->_lockFp);
+        }
+        $this->_lockFp = null;
+        return $ret;
+    }
+
+    /**
+     * Load the dependency database from disk, or return the cache
+     * @return array|PEAR_Error
+     */
+    function _getDepDB()
+    {
+        if (!$this->hasWriteAccess()) {
+            return array('_version' => $this->_version);
+        }
+
+        if (isset($this->_cache)) {
+            return $this->_cache;
+        }
+
+        if (!$fp = fopen($this->_depdb, 'r')) {
+            $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'");
+            return $err;
+        }
+
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        clearstatcache();
+        fclose($fp);
+        $data = unserialize(file_get_contents($this->_depdb));
+        set_magic_quotes_runtime($rt);
+        $this->_cache = $data;
+        return $data;
+    }
+
+    /**
+     * Write out the dependency database to disk
+     * @param array the database
+     * @return true|PEAR_Error
+     * @access private
+     */
+    function _writeDepDB(&$deps)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+
+        if (!$fp = fopen($this->_depdb, 'wb')) {
+            $this->_unlock();
+            return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing");
+        }
+
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        fwrite($fp, serialize($deps));
+        set_magic_quotes_runtime($rt);
+        fclose($fp);
+        $this->_unlock();
+        $this->_cache = $deps;
+        return true;
+    }
+
+    /**
+     * Register all dependencies from a package in the dependencies database, in essence
+     * "installing" the package's dependency information
+     * @param array the database
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @access private
+     */
+    function _setPackageDeps(&$data, &$pkg)
+    {
+        $pkg->setConfig($this->_config);
+        if ($pkg->getPackagexmlVersion() == '1.0') {
+            $gen = &$pkg->getDefaultGenerator();
+            $deps = $gen->dependenciesToV2();
+        } else {
+            $deps = $pkg->getDeps(true);
+        }
+
+        if (!$deps) {
+            return;
+        }
+
+        if (!is_array($data)) {
+            $data = array();
+        }
+
+        if (!isset($data['dependencies'])) {
+            $data['dependencies'] = array();
+        }
+
+        $channel = strtolower($pkg->getChannel());
+        $package = strtolower($pkg->getPackage());
+
+        if (!isset($data['dependencies'][$channel])) {
+            $data['dependencies'][$channel] = array();
+        }
+
+        $data['dependencies'][$channel][$package] = array();
+        if (isset($deps['required']['package'])) {
+            if (!isset($deps['required']['package'][0])) {
+                $deps['required']['package'] = array($deps['required']['package']);
+            }
+
+            foreach ($deps['required']['package'] as $dep) {
+                $this->_registerDep($data, $pkg, $dep, 'required');
+            }
+        }
+
+        if (isset($deps['optional']['package'])) {
+            if (!isset($deps['optional']['package'][0])) {
+                $deps['optional']['package'] = array($deps['optional']['package']);
+            }
+
+            foreach ($deps['optional']['package'] as $dep) {
+                $this->_registerDep($data, $pkg, $dep, 'optional');
+            }
+        }
+
+        if (isset($deps['required']['subpackage'])) {
+            if (!isset($deps['required']['subpackage'][0])) {
+                $deps['required']['subpackage'] = array($deps['required']['subpackage']);
+            }
+
+            foreach ($deps['required']['subpackage'] as $dep) {
+                $this->_registerDep($data, $pkg, $dep, 'required');
+            }
+        }
+
+        if (isset($deps['optional']['subpackage'])) {
+            if (!isset($deps['optional']['subpackage'][0])) {
+                $deps['optional']['subpackage'] = array($deps['optional']['subpackage']);
+            }
+
+            foreach ($deps['optional']['subpackage'] as $dep) {
+                $this->_registerDep($data, $pkg, $dep, 'optional');
+            }
+        }
+
+        if (isset($deps['group'])) {
+            if (!isset($deps['group'][0])) {
+                $deps['group'] = array($deps['group']);
+            }
+
+            foreach ($deps['group'] as $group) {
+                if (isset($group['package'])) {
+                    if (!isset($group['package'][0])) {
+                        $group['package'] = array($group['package']);
+                    }
+
+                    foreach ($group['package'] as $dep) {
+                        $this->_registerDep($data, $pkg, $dep, 'optional',
+                            $group['attribs']['name']);
+                    }
+                }
+
+                if (isset($group['subpackage'])) {
+                    if (!isset($group['subpackage'][0])) {
+                        $group['subpackage'] = array($group['subpackage']);
+                    }
+
+                    foreach ($group['subpackage'] as $dep) {
+                        $this->_registerDep($data, $pkg, $dep, 'optional',
+                            $group['attribs']['name']);
+                    }
+                }
+            }
+        }
+
+        if ($data['dependencies'][$channel][$package] == array()) {
+            unset($data['dependencies'][$channel][$package]);
+            if (!count($data['dependencies'][$channel])) {
+                unset($data['dependencies'][$channel]);
+            }
+        }
+    }
+
+    /**
+     * @param array the database
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param array the specific dependency
+     * @param required|optional whether this is a required or an optional dep
+     * @param string|false dependency group this dependency is from, or false for ordinary dep
+     */
+    function _registerDep(&$data, &$pkg, $dep, $type, $group = false)
+    {
+        $info = array(
+            'dep'   => $dep,
+            'type'  => $type,
+            'group' => $group
+        );
+
+        $dep  = array_map('strtolower', $dep);
+        $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+        if (!isset($data['dependencies'])) {
+            $data['dependencies'] = array();
+        }
+
+        $channel = strtolower($pkg->getChannel());
+        $package = strtolower($pkg->getPackage());
+
+        if (!isset($data['dependencies'][$channel])) {
+            $data['dependencies'][$channel] = array();
+        }
+
+        if (!isset($data['dependencies'][$channel][$package])) {
+            $data['dependencies'][$channel][$package] = array();
+        }
+
+        $data['dependencies'][$channel][$package][] = $info;
+        if (isset($data['packages'][$depchannel][$dep['name']])) {
+            $found = false;
+            foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) {
+                if ($p['channel'] == $channel && $p['package'] == $package) {
+                    $found = true;
+                    break;
+                }
+            }
+        } else {
+            if (!isset($data['packages'])) {
+                $data['packages'] = array();
+            }
+
+            if (!isset($data['packages'][$depchannel])) {
+                $data['packages'][$depchannel] = array();
+            }
+
+            if (!isset($data['packages'][$depchannel][$dep['name']])) {
+                $data['packages'][$depchannel][$dep['name']] = array();
+            }
+
+            $found = false;
+        }
+
+        if (!$found) {
+            $data['packages'][$depchannel][$dep['name']][] = array(
+                'channel' => $channel,
+                'package' => $package
+            );
+        }
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Downloader.php b/WEB-INF/lib/pear/PEAR/Downloader.php
new file mode 100644 (file)
index 0000000..730df0b
--- /dev/null
@@ -0,0 +1,1766 @@
+<?php
+/**
+ * PEAR_Downloader, the PEAR Installer's download utility class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Downloader.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.3.0
+ */
+
+/**
+ * Needed for constants, extending
+ */
+require_once 'PEAR/Common.php';
+
+define('PEAR_INSTALLER_OK',       1);
+define('PEAR_INSTALLER_FAILED',   0);
+define('PEAR_INSTALLER_SKIPPED', -1);
+define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
+
+/**
+ * Administration class used to download anything from the internet (PEAR Packages,
+ * static URLs, xml files)
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.3.0
+ */
+class PEAR_Downloader extends PEAR_Common
+{
+    /**
+     * @var PEAR_Registry
+     * @access private
+     */
+    var $_registry;
+
+    /**
+     * Preferred Installation State (snapshot, devel, alpha, beta, stable)
+     * @var string|null
+     * @access private
+     */
+    var $_preferredState;
+
+    /**
+     * Options from command-line passed to Install.
+     *
+     * Recognized options:<br />
+     *  - onlyreqdeps   : install all required dependencies as well
+     *  - alldeps       : install all dependencies, including optional
+     *  - installroot   : base relative path to install files in
+     *  - force         : force a download even if warnings would prevent it
+     *  - nocompress    : download uncompressed tarballs
+     * @see PEAR_Command_Install
+     * @access private
+     * @var array
+     */
+    var $_options;
+
+    /**
+     * Downloaded Packages after a call to download().
+     *
+     * Format of each entry:
+     *
+     * <code>
+     * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+     *    'info' => array() // parsed package.xml
+     * );
+     * </code>
+     * @access private
+     * @var array
+     */
+    var $_downloadedPackages = array();
+
+    /**
+     * Packages slated for download.
+     *
+     * This is used to prevent downloading a package more than once should it be a dependency
+     * for two packages to be installed.
+     * Format of each entry:
+     *
+     * <pre>
+     * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
+     * );
+     * </pre>
+     * @access private
+     * @var array
+     */
+    var $_toDownload = array();
+
+    /**
+     * Array of every package installed, with names lower-cased.
+     *
+     * Format:
+     * <code>
+     * array('package1' => 0, 'package2' => 1, );
+     * </code>
+     * @var array
+     */
+    var $_installed = array();
+
+    /**
+     * @var array
+     * @access private
+     */
+    var $_errorStack = array();
+
+    /**
+     * @var boolean
+     * @access private
+     */
+    var $_internalDownload = false;
+
+    /**
+     * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
+     * @var array
+     * @access private
+     */
+    var $_packageSortTree;
+
+    /**
+     * Temporary directory, or configuration value where downloads will occur
+     * @var string
+     */
+    var $_downloadDir;
+
+    /**
+     * @param PEAR_Frontend_*
+     * @param array
+     * @param PEAR_Config
+     */
+    function PEAR_Downloader(&$ui, $options, &$config)
+    {
+        parent::PEAR_Common();
+        $this->_options = $options;
+        $this->config = &$config;
+        $this->_preferredState = $this->config->get('preferred_state');
+        $this->ui = &$ui;
+        if (!$this->_preferredState) {
+            // don't inadvertantly use a non-set preferred_state
+            $this->_preferredState = null;
+        }
+
+        if (isset($this->_options['installroot'])) {
+            $this->config->setInstallRoot($this->_options['installroot']);
+        }
+        $this->_registry = &$config->getRegistry();
+
+        if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
+            $this->_installed = $this->_registry->listAllPackages();
+            foreach ($this->_installed as $key => $unused) {
+                if (!count($unused)) {
+                    continue;
+                }
+                $strtolower = create_function('$a','return strtolower($a);');
+                array_walk($this->_installed[$key], $strtolower);
+            }
+        }
+    }
+
+    /**
+     * Attempt to discover a channel's remote capabilities from
+     * its server name
+     * @param string
+     * @return boolean
+     */
+    function discover($channel)
+    {
+        $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
+        if (!class_exists('System')) {
+            require_once 'System.php';
+        }
+
+        $tmpdir = $this->config->get('temp_dir');
+        $tmp = System::mktemp('-d -t "' . $tmpdir . '"');
+        $a   = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
+        PEAR::popErrorHandling();
+        if (PEAR::isError($a)) {
+            // Attempt to fallback to https automatically.
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...');
+            $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
+            PEAR::popErrorHandling();
+            if (PEAR::isError($a)) {
+                return false;
+            }
+        }
+
+        list($a, $lastmodified) = $a;
+        if (!class_exists('PEAR_ChannelFile')) {
+            require_once 'PEAR/ChannelFile.php';
+        }
+
+        $b = new PEAR_ChannelFile;
+        if ($b->fromXmlFile($a)) {
+            unlink($a);
+            if ($this->config->get('auto_discover')) {
+                $this->_registry->addChannel($b, $lastmodified);
+                $alias = $b->getName();
+                if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
+                    $alias = $b->getAlias();
+                }
+
+                $this->log(1, 'Auto-discovered channel "' . $channel .
+                    '", alias "' . $alias . '", adding to registry');
+            }
+
+            return true;
+        }
+
+        unlink($a);
+        return false;
+    }
+
+    /**
+     * For simpler unit-testing
+     * @param PEAR_Downloader
+     * @return PEAR_Downloader_Package
+     */
+    function &newDownloaderPackage(&$t)
+    {
+        if (!class_exists('PEAR_Downloader_Package')) {
+            require_once 'PEAR/Downloader/Package.php';
+        }
+        $a = &new PEAR_Downloader_Package($t);
+        return $a;
+    }
+
+    /**
+     * For simpler unit-testing
+     * @param PEAR_Config
+     * @param array
+     * @param array
+     * @param int
+     */
+    function &getDependency2Object(&$c, $i, $p, $s)
+    {
+        if (!class_exists('PEAR_Dependency2')) {
+            require_once 'PEAR/Dependency2.php';
+        }
+        $z = &new PEAR_Dependency2($c, $i, $p, $s);
+        return $z;
+    }
+
+    function &download($params)
+    {
+        if (!count($params)) {
+            $a = array();
+            return $a;
+        }
+
+        if (!isset($this->_registry)) {
+            $this->_registry = &$this->config->getRegistry();
+        }
+
+        $channelschecked = array();
+        // convert all parameters into PEAR_Downloader_Package objects
+        foreach ($params as $i => $param) {
+            $params[$i] = &$this->newDownloaderPackage($this);
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $err = $params[$i]->initialize($param);
+            PEAR::staticPopErrorHandling();
+            if (!$err) {
+                // skip parameters that were missed by preferred_state
+                continue;
+            }
+
+            if (PEAR::isError($err)) {
+                if (!isset($this->_options['soft']) && $err->getMessage() !== '') {
+                    $this->log(0, $err->getMessage());
+                }
+
+                $params[$i] = false;
+                if (is_object($param)) {
+                    $param = $param->getChannel() . '/' . $param->getPackage();
+                }
+
+                if (!isset($this->_options['soft'])) {
+                    $this->log(2, 'Package "' . $param . '" is not valid');
+                }
+
+                // Message logged above in a specific verbose mode, passing null to not show up on CLI
+                $this->pushError(null, PEAR_INSTALLER_SKIPPED);
+            } else {
+                do {
+                    if ($params[$i] && $params[$i]->getType() == 'local') {
+                        // bug #7090 skip channel.xml check for local packages
+                        break;
+                    }
+
+                    if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
+                          !isset($this->_options['offline'])
+                    ) {
+                        $channelschecked[$params[$i]->getChannel()] = true;
+                        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+                        if (!class_exists('System')) {
+                            require_once 'System.php';
+                        }
+
+                        $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
+                        if (PEAR::isError($curchannel)) {
+                            PEAR::staticPopErrorHandling();
+                            return $this->raiseError($curchannel);
+                        }
+
+                        if (PEAR::isError($dir = $this->getDownloadDir())) {
+                            PEAR::staticPopErrorHandling();
+                            break;
+                        }
+
+                        $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel());
+                        $url    = 'http://' . $mirror . '/channel.xml';
+                        $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified());
+
+                        PEAR::staticPopErrorHandling();
+                        if (PEAR::isError($a) || !$a) {
+                            // Attempt fallback to https automatically
+                            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                            $a = $this->downloadHttp('https://' . $mirror .
+                                '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
+
+                            PEAR::staticPopErrorHandling();
+                            if (PEAR::isError($a) || !$a) {
+                                break;
+                            }
+                        }
+                        $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
+                            'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() .
+                            '" to update');
+                    }
+                } while (false);
+
+                if ($params[$i] && !isset($this->_options['downloadonly'])) {
+                    if (isset($this->_options['packagingroot'])) {
+                        $checkdir = $this->_prependPath(
+                            $this->config->get('php_dir', null, $params[$i]->getChannel()),
+                            $this->_options['packagingroot']);
+                    } else {
+                        $checkdir = $this->config->get('php_dir',
+                            null, $params[$i]->getChannel());
+                    }
+
+                    while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
+                        $checkdir = dirname($checkdir);
+                    }
+
+                    if ($checkdir == '.') {
+                        $checkdir = '/';
+                    }
+
+                    if (!is_writeable($checkdir)) {
+                        return PEAR::raiseError('Cannot install, php_dir for channel "' .
+                            $params[$i]->getChannel() . '" is not writeable by the current user');
+                    }
+                }
+            }
+        }
+
+        unset($channelschecked);
+        PEAR_Downloader_Package::removeDuplicates($params);
+        if (!count($params)) {
+            $a = array();
+            return $a;
+        }
+
+        if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
+            $reverify = true;
+            while ($reverify) {
+                $reverify = false;
+                foreach ($params as $i => $param) {
+                    //PHP Bug 40768 / PEAR Bug #10944
+                    //Nested foreaches fail in PHP 5.2.1
+                    key($params);
+                    $ret = $params[$i]->detectDependencies($params);
+                    if (PEAR::isError($ret)) {
+                        $reverify = true;
+                        $params[$i] = false;
+                        PEAR_Downloader_Package::removeDuplicates($params);
+                        if (!isset($this->_options['soft'])) {
+                            $this->log(0, $ret->getMessage());
+                        }
+                        continue 2;
+                    }
+                }
+            }
+        }
+
+        if (isset($this->_options['offline'])) {
+            $this->log(3, 'Skipping dependency download check, --offline specified');
+        }
+
+        if (!count($params)) {
+            $a = array();
+            return $a;
+        }
+
+        while (PEAR_Downloader_Package::mergeDependencies($params));
+        PEAR_Downloader_Package::removeDuplicates($params, true);
+        $errorparams = array();
+        if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
+            if (count($errorparams)) {
+                foreach ($errorparams as $param) {
+                    $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
+                    $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
+                }
+                $a = array();
+                return $a;
+            }
+        }
+
+        PEAR_Downloader_Package::removeInstalled($params);
+        if (!count($params)) {
+            $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
+            $a = array();
+            return $a;
+        }
+
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $this->analyzeDependencies($params);
+        PEAR::popErrorHandling();
+        if (!count($params)) {
+            $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
+            $a = array();
+            return $a;
+        }
+
+        $ret = array();
+        $newparams = array();
+        if (isset($this->_options['pretend'])) {
+            return $params;
+        }
+
+        $somefailed = false;
+        foreach ($params as $i => $package) {
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $pf = &$params[$i]->download();
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($pf)) {
+                if (!isset($this->_options['soft'])) {
+                    $this->log(1, $pf->getMessage());
+                    $this->log(0, 'Error: cannot download "' .
+                        $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
+                            true) .
+                        '"');
+                }
+                $somefailed = true;
+                continue;
+            }
+
+            $newparams[] = &$params[$i];
+            $ret[] = array(
+                'file' => $pf->getArchiveFile(),
+                'info' => &$pf,
+                'pkg'  => $pf->getPackage()
+            );
+        }
+
+        if ($somefailed) {
+            // remove params that did not download successfully
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $err = $this->analyzeDependencies($newparams, true);
+            PEAR::popErrorHandling();
+            if (!count($newparams)) {
+                $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
+                $a = array();
+                return $a;
+            }
+        }
+
+        $this->_downloadedPackages = $ret;
+        return $newparams;
+    }
+
+    /**
+     * @param array all packages to be installed
+     */
+    function analyzeDependencies(&$params, $force = false)
+    {
+        if (isset($this->_options['downloadonly'])) {
+            return;
+        }
+
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $redo  = true;
+        $reset = $hasfailed = $failed = false;
+        while ($redo) {
+            $redo = false;
+            foreach ($params as $i => $param) {
+                $deps = $param->getDeps();
+                if (!$deps) {
+                    $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
+                        $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
+                    $send = $param->getPackageFile();
+
+                    $installcheck = $depchecker->validatePackage($send, $this, $params);
+                    if (PEAR::isError($installcheck)) {
+                        if (!isset($this->_options['soft'])) {
+                            $this->log(0, $installcheck->getMessage());
+                        }
+                        $hasfailed  = true;
+                        $params[$i] = false;
+                        $reset      = true;
+                        $redo       = true;
+                        $failed     = false;
+                        PEAR_Downloader_Package::removeDuplicates($params);
+                        continue 2;
+                    }
+                    continue;
+                }
+
+                if (!$reset && $param->alreadyValidated() && !$force) {
+                    continue;
+                }
+
+                if (count($deps)) {
+                    $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
+                        $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
+                    $send = $param->getPackageFile();
+                    if ($send === null) {
+                        $send = $param->getDownloadURL();
+                    }
+
+                    $installcheck = $depchecker->validatePackage($send, $this, $params);
+                    if (PEAR::isError($installcheck)) {
+                        if (!isset($this->_options['soft'])) {
+                            $this->log(0, $installcheck->getMessage());
+                        }
+                        $hasfailed  = true;
+                        $params[$i] = false;
+                        $reset      = true;
+                        $redo       = true;
+                        $failed     = false;
+                        PEAR_Downloader_Package::removeDuplicates($params);
+                        continue 2;
+                    }
+
+                    $failed = false;
+                    if (isset($deps['required']) && is_array($deps['required'])) {
+                        foreach ($deps['required'] as $type => $dep) {
+                            // note: Dependency2 will never return a PEAR_Error if ignore-errors
+                            // is specified, so soft is needed to turn off logging
+                            if (!isset($dep[0])) {
+                                if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
+                                      true, $params))) {
+                                    $failed = true;
+                                    if (!isset($this->_options['soft'])) {
+                                        $this->log(0, $e->getMessage());
+                                    }
+                                } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                    if (!isset($this->_options['soft'])) {
+                                        $this->log(0, $e[0]);
+                                    }
+                                }
+                            } else {
+                                foreach ($dep as $d) {
+                                    if (PEAR::isError($e =
+                                          $depchecker->{"validate{$type}Dependency"}($d,
+                                          true, $params))) {
+                                        $failed = true;
+                                        if (!isset($this->_options['soft'])) {
+                                            $this->log(0, $e->getMessage());
+                                        }
+                                    } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                        if (!isset($this->_options['soft'])) {
+                                            $this->log(0, $e[0]);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        if (isset($deps['optional']) && is_array($deps['optional'])) {
+                            foreach ($deps['optional'] as $type => $dep) {
+                                if (!isset($dep[0])) {
+                                    if (PEAR::isError($e =
+                                          $depchecker->{"validate{$type}Dependency"}($dep,
+                                          false, $params))) {
+                                        $failed = true;
+                                        if (!isset($this->_options['soft'])) {
+                                            $this->log(0, $e->getMessage());
+                                        }
+                                    } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                        if (!isset($this->_options['soft'])) {
+                                            $this->log(0, $e[0]);
+                                        }
+                                    }
+                                } else {
+                                    foreach ($dep as $d) {
+                                        if (PEAR::isError($e =
+                                              $depchecker->{"validate{$type}Dependency"}($d,
+                                              false, $params))) {
+                                            $failed = true;
+                                            if (!isset($this->_options['soft'])) {
+                                                $this->log(0, $e->getMessage());
+                                            }
+                                        } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                            if (!isset($this->_options['soft'])) {
+                                                $this->log(0, $e[0]);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        $groupname = $param->getGroup();
+                        if (isset($deps['group']) && $groupname) {
+                            if (!isset($deps['group'][0])) {
+                                $deps['group'] = array($deps['group']);
+                            }
+
+                            $found = false;
+                            foreach ($deps['group'] as $group) {
+                                if ($group['attribs']['name'] == $groupname) {
+                                    $found = true;
+                                    break;
+                                }
+                            }
+
+                            if ($found) {
+                                unset($group['attribs']);
+                                foreach ($group as $type => $dep) {
+                                    if (!isset($dep[0])) {
+                                        if (PEAR::isError($e =
+                                              $depchecker->{"validate{$type}Dependency"}($dep,
+                                              false, $params))) {
+                                            $failed = true;
+                                            if (!isset($this->_options['soft'])) {
+                                                $this->log(0, $e->getMessage());
+                                            }
+                                        } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                            if (!isset($this->_options['soft'])) {
+                                                $this->log(0, $e[0]);
+                                            }
+                                        }
+                                    } else {
+                                        foreach ($dep as $d) {
+                                            if (PEAR::isError($e =
+                                                  $depchecker->{"validate{$type}Dependency"}($d,
+                                                  false, $params))) {
+                                                $failed = true;
+                                                if (!isset($this->_options['soft'])) {
+                                                    $this->log(0, $e->getMessage());
+                                                }
+                                            } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                                if (!isset($this->_options['soft'])) {
+                                                    $this->log(0, $e[0]);
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        foreach ($deps as $dep) {
+                            if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
+                                $failed = true;
+                                if (!isset($this->_options['soft'])) {
+                                    $this->log(0, $e->getMessage());
+                                }
+                            } elseif (is_array($e) && !$param->alreadyValidated()) {
+                                if (!isset($this->_options['soft'])) {
+                                    $this->log(0, $e[0]);
+                                }
+                            }
+                        }
+                    }
+                    $params[$i]->setValidated();
+                }
+
+                if ($failed) {
+                    $hasfailed  = true;
+                    $params[$i] = false;
+                    $reset      = true;
+                    $redo       = true;
+                    $failed     = false;
+                    PEAR_Downloader_Package::removeDuplicates($params);
+                    continue 2;
+                }
+            }
+        }
+
+        PEAR::staticPopErrorHandling();
+        if ($hasfailed && (isset($this->_options['ignore-errors']) ||
+              isset($this->_options['nodeps']))) {
+            // this is probably not needed, but just in case
+            if (!isset($this->_options['soft'])) {
+                $this->log(0, 'WARNING: dependencies failed');
+            }
+        }
+    }
+
+    /**
+     * Retrieve the directory that downloads will happen in
+     * @access private
+     * @return string
+     */
+    function getDownloadDir()
+    {
+        if (isset($this->_downloadDir)) {
+            return $this->_downloadDir;
+        }
+
+        $downloaddir = $this->config->get('download_dir');
+        if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
+            if  (is_dir($downloaddir) && !is_writable($downloaddir)) {
+                $this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
+                    '" is not writeable.  Change download_dir config variable to ' .
+                    'a writeable dir to avoid this warning');
+            }
+
+            if (!class_exists('System')) {
+                require_once 'System.php';
+            }
+
+            if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
+                return $downloaddir;
+            }
+            $this->log(3, '+ tmp dir created at ' . $downloaddir);
+        }
+
+        if (!is_writable($downloaddir)) {
+            if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
+                  !is_writable($downloaddir)) {
+                return PEAR::raiseError('download directory "' . $downloaddir .
+                    '" is not writeable.  Change download_dir config variable to ' .
+                    'a writeable dir');
+            }
+        }
+
+        return $this->_downloadDir = $downloaddir;
+    }
+
+    function setDownloadDir($dir)
+    {
+        if (!@is_writable($dir)) {
+            if (PEAR::isError(System::mkdir(array('-p', $dir)))) {
+                return PEAR::raiseError('download directory "' . $dir .
+                    '" is not writeable.  Change download_dir config variable to ' .
+                    'a writeable dir');
+            }
+        }
+        $this->_downloadDir = $dir;
+    }
+
+    function configSet($key, $value, $layer = 'user', $channel = false)
+    {
+        $this->config->set($key, $value, $layer, $channel);
+        $this->_preferredState = $this->config->get('preferred_state', null, $channel);
+        if (!$this->_preferredState) {
+            // don't inadvertantly use a non-set preferred_state
+            $this->_preferredState = null;
+        }
+    }
+
+    function setOptions($options)
+    {
+        $this->_options = $options;
+    }
+
+    function getOptions()
+    {
+        return $this->_options;
+    }
+
+
+    /**
+     * @param array output of {@link parsePackageName()}
+     * @access private
+     */
+    function _getPackageDownloadUrl($parr)
+    {
+        $curchannel = $this->config->get('default_channel');
+        $this->configSet('default_channel', $parr['channel']);
+        // getDownloadURL returns an array.  On error, it only contains information
+        // on the latest release as array(version, info).  On success it contains
+        // array(version, info, download url string)
+        $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
+        if (!$this->_registry->channelExists($parr['channel'])) {
+            do {
+                if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) {
+                    break;
+                }
+
+                $this->configSet('default_channel', $curchannel);
+                return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']);
+            } while (false);
+        }
+
+        $chan = &$this->_registry->getChannel($parr['channel']);
+        if (PEAR::isError($chan)) {
+            return $chan;
+        }
+
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $version   = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']);
+        $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']);
+        // package is installed - use the installed release stability level
+        if (!isset($parr['state']) && $stability !== null) {
+            $state = $stability['release'];
+        }
+        PEAR::staticPopErrorHandling();
+        $base2 = false;
+
+        $preferred_mirror = $this->config->get('preferred_mirror');
+        if (!$chan->supportsREST($preferred_mirror) ||
+              (
+               !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
+               &&
+               !($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
+              )
+        ) {
+            return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
+        }
+
+        if ($base2) {
+            $rest = &$this->config->getREST('1.3', $this->_options);
+            $base = $base2;
+        } else {
+            $rest = &$this->config->getREST('1.0', $this->_options);
+        }
+
+        $downloadVersion = false;
+        if (!isset($parr['version']) && !isset($parr['state']) && $version
+              && !PEAR::isError($version)
+              && !isset($this->_options['downloadonly'])
+        ) {
+            $downloadVersion = $version;
+        }
+
+        $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName());
+        if (PEAR::isError($url)) {
+            $this->configSet('default_channel', $curchannel);
+            return $url;
+        }
+
+        if ($parr['channel'] != $curchannel) {
+            $this->configSet('default_channel', $curchannel);
+        }
+
+        if (!is_array($url)) {
+            return $url;
+        }
+
+        $url['raw'] = false; // no checking is necessary for REST
+        if (!is_array($url['info'])) {
+            return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
+                'this should never happen');
+        }
+
+        if (!isset($this->_options['force']) &&
+              !isset($this->_options['downloadonly']) &&
+              $version &&
+              !PEAR::isError($version) &&
+              !isset($parr['group'])
+        ) {
+            if (version_compare($version, $url['version'], '=')) {
+                return PEAR::raiseError($this->_registry->parsedPackageNameToString(
+                    $parr, true) . ' is already installed and is the same as the ' .
+                    'released version ' . $url['version'], -976);
+            }
+
+            if (version_compare($version, $url['version'], '>')) {
+                return PEAR::raiseError($this->_registry->parsedPackageNameToString(
+                    $parr, true) . ' is already installed and is newer than detected ' .
+                    'released version ' . $url['version'], -976);
+            }
+        }
+
+        if (isset($url['info']['required']) || $url['compatible']) {
+            require_once 'PEAR/PackageFile/v2.php';
+            $pf = new PEAR_PackageFile_v2;
+            $pf->setRawChannel($parr['channel']);
+            if ($url['compatible']) {
+                $pf->setRawCompatible($url['compatible']);
+            }
+        } else {
+            require_once 'PEAR/PackageFile/v1.php';
+            $pf = new PEAR_PackageFile_v1;
+        }
+
+        $pf->setRawPackage($url['package']);
+        $pf->setDeps($url['info']);
+        if ($url['compatible']) {
+            $pf->setCompatible($url['compatible']);
+        }
+
+        $pf->setRawState($url['stability']);
+        $url['info'] = &$pf;
+        if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
+            $ext = '.tar';
+        } else {
+            $ext = '.tgz';
+        }
+
+        if (is_array($url) && isset($url['url'])) {
+            $url['url'] .= $ext;
+        }
+
+        return $url;
+    }
+
+    /**
+     * @param array dependency array
+     * @access private
+     */
+    function _getDepPackageDownloadUrl($dep, $parr)
+    {
+        $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
+        $curchannel = $this->config->get('default_channel');
+        if (isset($dep['uri'])) {
+            $xsdversion = '2.0';
+            $chan = &$this->_registry->getChannel('__uri');
+            if (PEAR::isError($chan)) {
+                return $chan;
+            }
+
+            $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
+            $this->configSet('default_channel', '__uri');
+        } else {
+            if (isset($dep['channel'])) {
+                $remotechannel = $dep['channel'];
+            } else {
+                $remotechannel = 'pear.php.net';
+            }
+
+            if (!$this->_registry->channelExists($remotechannel)) {
+                do {
+                    if ($this->config->get('auto_discover')) {
+                        if ($this->discover($remotechannel)) {
+                            break;
+                        }
+                    }
+                    return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
+                } while (false);
+            }
+
+            $chan = &$this->_registry->getChannel($remotechannel);
+            if (PEAR::isError($chan)) {
+                return $chan;
+            }
+
+            $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel);
+            $this->configSet('default_channel', $remotechannel);
+        }
+
+        $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
+        if (isset($parr['state']) && isset($parr['version'])) {
+            unset($parr['state']);
+        }
+
+        if (isset($dep['uri'])) {
+            $info = &$this->newDownloaderPackage($this);
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $err = $info->initialize($dep);
+            PEAR::staticPopErrorHandling();
+            if (!$err) {
+                // skip parameters that were missed by preferred_state
+                return PEAR::raiseError('Cannot initialize dependency');
+            }
+
+            if (PEAR::isError($err)) {
+                if (!isset($this->_options['soft'])) {
+                    $this->log(0, $err->getMessage());
+                }
+
+                if (is_object($info)) {
+                    $param = $info->getChannel() . '/' . $info->getPackage();
+                }
+                return PEAR::raiseError('Package "' . $param . '" is not valid');
+            }
+            return $info;
+        } elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
+              &&
+                (
+                  ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror')))
+                    ||
+                  ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')))
+                )
+        ) {
+            if ($base2) {
+                $base = $base2;
+                $rest = &$this->config->getREST('1.3', $this->_options);
+            } else {
+                $rest = &$this->config->getREST('1.0', $this->_options);
+            }
+
+            $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
+                    $state, $version, $chan->getName());
+            if (PEAR::isError($url)) {
+                return $url;
+            }
+
+            if ($parr['channel'] != $curchannel) {
+                $this->configSet('default_channel', $curchannel);
+            }
+
+            if (!is_array($url)) {
+                return $url;
+            }
+
+            $url['raw'] = false; // no checking is necessary for REST
+            if (!is_array($url['info'])) {
+                return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
+                    'this should never happen');
+            }
+
+            if (isset($url['info']['required'])) {
+                if (!class_exists('PEAR_PackageFile_v2')) {
+                    require_once 'PEAR/PackageFile/v2.php';
+                }
+                $pf = new PEAR_PackageFile_v2;
+                $pf->setRawChannel($remotechannel);
+            } else {
+                if (!class_exists('PEAR_PackageFile_v1')) {
+                    require_once 'PEAR/PackageFile/v1.php';
+                }
+                $pf = new PEAR_PackageFile_v1;
+
+            }
+            $pf->setRawPackage($url['package']);
+            $pf->setDeps($url['info']);
+            if ($url['compatible']) {
+                $pf->setCompatible($url['compatible']);
+            }
+
+            $pf->setRawState($url['stability']);
+            $url['info'] = &$pf;
+            if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
+                $ext = '.tar';
+            } else {
+                $ext = '.tgz';
+            }
+
+            if (is_array($url) && isset($url['url'])) {
+                $url['url'] .= $ext;
+            }
+
+            return $url;
+        }
+
+        return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
+    }
+
+    /**
+     * @deprecated in favor of _getPackageDownloadUrl
+     */
+    function getPackageDownloadUrl($package, $version = null, $channel = false)
+    {
+        if ($version) {
+            $package .= "-$version";
+        }
+        if ($this === null || $this->_registry === null) {
+            $package = "http://pear.php.net/get/$package";
+        } else {
+            $chan = $this->_registry->getChannel($channel);
+            if (PEAR::isError($chan)) {
+                return '';
+            }
+            $package = "http://" . $chan->getServer() . "/get/$package";
+        }
+        if (!extension_loaded("zlib")) {
+            $package .= '?uncompress=yes';
+        }
+        return $package;
+    }
+
+    /**
+     * Retrieve a list of downloaded packages after a call to {@link download()}.
+     *
+     * Also resets the list of downloaded packages.
+     * @return array
+     */
+    function getDownloadedPackages()
+    {
+        $ret = $this->_downloadedPackages;
+        $this->_downloadedPackages = array();
+        $this->_toDownload = array();
+        return $ret;
+    }
+
+    function _downloadCallback($msg, $params = null)
+    {
+        switch ($msg) {
+            case 'saveas':
+                $this->log(1, "downloading $params ...");
+                break;
+            case 'done':
+                $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
+                break;
+            case 'bytesread':
+                static $bytes;
+                if (empty($bytes)) {
+                    $bytes = 0;
+                }
+                if (!($bytes % 10240)) {
+                    $this->log(1, '.', false);
+                }
+                $bytes += $params;
+                break;
+            case 'start':
+                if($params[1] == -1) {
+                    $length = "Unknown size";
+                } else {
+                    $length = number_format($params[1], 0, '', ',')." bytes";
+                }
+                $this->log(1, "Starting to download {$params[0]} ($length)");
+                break;
+        }
+        if (method_exists($this->ui, '_downloadCallback'))
+            $this->ui->_downloadCallback($msg, $params);
+    }
+
+    function _prependPath($path, $prepend)
+    {
+        if (strlen($prepend) > 0) {
+            if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
+                if (preg_match('/^[a-z]:/i', $prepend)) {
+                    $prepend = substr($prepend, 2);
+                } elseif ($prepend{0} != '\\') {
+                    $prepend = "\\$prepend";
+                }
+                $path = substr($path, 0, 2) . $prepend . substr($path, 2);
+            } else {
+                $path = $prepend . $path;
+            }
+        }
+        return $path;
+    }
+
+    /**
+     * @param string
+     * @param integer
+     */
+    function pushError($errmsg, $code = -1)
+    {
+        array_push($this->_errorStack, array($errmsg, $code));
+    }
+
+    function getErrorMsgs()
+    {
+        $msgs = array();
+        $errs = $this->_errorStack;
+        foreach ($errs as $err) {
+            $msgs[] = $err[0];
+        }
+        $this->_errorStack = array();
+        return $msgs;
+    }
+
+    /**
+     * for BC
+     *
+     * @deprecated
+     */
+    function sortPkgDeps(&$packages, $uninstall = false)
+    {
+        $uninstall ?
+            $this->sortPackagesForUninstall($packages) :
+            $this->sortPackagesForInstall($packages);
+    }
+
+    /**
+     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+     *
+     * This uses the topological sort method from graph theory, and the
+     * Structures_Graph package to properly sort dependencies for installation.
+     * @param array an array of downloaded PEAR_Downloader_Packages
+     * @return array array of array(packagefilename, package.xml contents)
+     */
+    function sortPackagesForInstall(&$packages)
+    {
+        require_once 'Structures/Graph.php';
+        require_once 'Structures/Graph/Node.php';
+        require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
+        $depgraph = new Structures_Graph(true);
+        $nodes = array();
+        $reg = &$this->config->getRegistry();
+        foreach ($packages as $i => $package) {
+            $pname = $reg->parsedPackageNameToString(
+                array(
+                    'channel' => $package->getChannel(),
+                    'package' => strtolower($package->getPackage()),
+                ));
+            $nodes[$pname] = new Structures_Graph_Node;
+            $nodes[$pname]->setData($packages[$i]);
+            $depgraph->addNode($nodes[$pname]);
+        }
+
+        $deplinks = array();
+        foreach ($nodes as $package => $node) {
+            $pf = &$node->getData();
+            $pdeps = $pf->getDeps(true);
+            if (!$pdeps) {
+                continue;
+            }
+
+            if ($pf->getPackagexmlVersion() == '1.0') {
+                foreach ($pdeps as $dep) {
+                    if ($dep['type'] != 'pkg' ||
+                          (isset($dep['optional']) && $dep['optional'] == 'yes')) {
+                        continue;
+                    }
+
+                    $dname = $reg->parsedPackageNameToString(
+                          array(
+                              'channel' => 'pear.php.net',
+                              'package' => strtolower($dep['name']),
+                          ));
+
+                    if (isset($nodes[$dname])) {
+                        if (!isset($deplinks[$dname])) {
+                            $deplinks[$dname] = array();
+                        }
+
+                        $deplinks[$dname][$package] = 1;
+                        // dependency is in installed packages
+                        continue;
+                    }
+
+                    $dname = $reg->parsedPackageNameToString(
+                          array(
+                              'channel' => 'pecl.php.net',
+                              'package' => strtolower($dep['name']),
+                          ));
+
+                    if (isset($nodes[$dname])) {
+                        if (!isset($deplinks[$dname])) {
+                            $deplinks[$dname] = array();
+                        }
+
+                        $deplinks[$dname][$package] = 1;
+                        // dependency is in installed packages
+                        continue;
+                    }
+                }
+            } else {
+                // the only ordering we care about is:
+                // 1) subpackages must be installed before packages that depend on them
+                // 2) required deps must be installed before packages that depend on them
+                if (isset($pdeps['required']['subpackage'])) {
+                    $t = $pdeps['required']['subpackage'];
+                    if (!isset($t[0])) {
+                        $t = array($t);
+                    }
+
+                    $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+                }
+
+                if (isset($pdeps['group'])) {
+                    if (!isset($pdeps['group'][0])) {
+                        $pdeps['group'] = array($pdeps['group']);
+                    }
+
+                    foreach ($pdeps['group'] as $group) {
+                        if (isset($group['subpackage'])) {
+                            $t = $group['subpackage'];
+                            if (!isset($t[0])) {
+                                $t = array($t);
+                            }
+
+                            $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+                        }
+                    }
+                }
+
+                if (isset($pdeps['optional']['subpackage'])) {
+                    $t = $pdeps['optional']['subpackage'];
+                    if (!isset($t[0])) {
+                        $t = array($t);
+                    }
+
+                    $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+                }
+
+                if (isset($pdeps['required']['package'])) {
+                    $t = $pdeps['required']['package'];
+                    if (!isset($t[0])) {
+                        $t = array($t);
+                    }
+
+                    $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+                }
+
+                if (isset($pdeps['group'])) {
+                    if (!isset($pdeps['group'][0])) {
+                        $pdeps['group'] = array($pdeps['group']);
+                    }
+
+                    foreach ($pdeps['group'] as $group) {
+                        if (isset($group['package'])) {
+                            $t = $group['package'];
+                            if (!isset($t[0])) {
+                                $t = array($t);
+                            }
+
+                            $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+                        }
+                    }
+                }
+            }
+        }
+
+        $this->_detectDepCycle($deplinks);
+        foreach ($deplinks as $dependent => $parents) {
+            foreach ($parents as $parent => $unused) {
+                $nodes[$dependent]->connectTo($nodes[$parent]);
+            }
+        }
+
+        $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
+        $ret = array();
+        for ($i = 0, $count = count($installOrder); $i < $count; $i++) {
+            foreach ($installOrder[$i] as $index => $sortedpackage) {
+                $data = &$installOrder[$i][$index]->getData();
+                $ret[] = &$nodes[$reg->parsedPackageNameToString(
+                          array(
+                              'channel' => $data->getChannel(),
+                              'package' => strtolower($data->getPackage()),
+                          ))]->getData();
+            }
+        }
+
+        $packages = $ret;
+        return;
+    }
+
+    /**
+     * Detect recursive links between dependencies and break the cycles
+     *
+     * @param array
+     * @access private
+     */
+    function _detectDepCycle(&$deplinks)
+    {
+        do {
+            $keepgoing = false;
+            foreach ($deplinks as $dep => $parents) {
+                foreach ($parents as $parent => $unused) {
+                    // reset the parent cycle detector
+                    $this->_testCycle(null, null, null);
+                    if ($this->_testCycle($dep, $deplinks, $parent)) {
+                        $keepgoing = true;
+                        unset($deplinks[$dep][$parent]);
+                        if (count($deplinks[$dep]) == 0) {
+                            unset($deplinks[$dep]);
+                        }
+
+                        continue 3;
+                    }
+                }
+            }
+        } while ($keepgoing);
+    }
+
+    function _testCycle($test, $deplinks, $dep)
+    {
+        static $visited = array();
+        if ($test === null) {
+            $visited = array();
+            return;
+        }
+
+        // this happens when a parent has a dep cycle on another dependency
+        // but the child is not part of the cycle
+        if (isset($visited[$dep])) {
+            return false;
+        }
+
+        $visited[$dep] = 1;
+        if ($test == $dep) {
+            return true;
+        }
+
+        if (isset($deplinks[$dep])) {
+            if (in_array($test, array_keys($deplinks[$dep]), true)) {
+                return true;
+            }
+
+            foreach ($deplinks[$dep] as $parent => $unused) {
+                if ($this->_testCycle($test, $deplinks, $parent)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Set up the dependency for installation parsing
+     *
+     * @param array $t dependency information
+     * @param PEAR_Registry $reg
+     * @param array $deplinks list of dependency links already established
+     * @param array $nodes all existing package nodes
+     * @param string $package parent package name
+     * @access private
+     */
+    function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
+    {
+        foreach ($t as $dep) {
+            $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel'];
+            $dname = $reg->parsedPackageNameToString(
+                  array(
+                      'channel' => $depchannel,
+                      'package' => strtolower($dep['name']),
+                  ));
+
+            if (isset($nodes[$dname])) {
+                if (!isset($deplinks[$dname])) {
+                    $deplinks[$dname] = array();
+                }
+                $deplinks[$dname][$package] = 1;
+            }
+        }
+    }
+
+    function _dependsOn($a, $b)
+    {
+        return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b);
+    }
+
+    function _checkDepTree($channel, $package, $b, $checked = array())
+    {
+        $checked[$channel][$package] = true;
+        if (!isset($this->_depTree[$channel][$package])) {
+            return false;
+        }
+
+        if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
+              [strtolower($b->getPackage())])) {
+            return true;
+        }
+
+        foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
+            foreach ($packages as $pa => $true) {
+                if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    function _sortInstall($a, $b)
+    {
+        if (!$a->getDeps() && !$b->getDeps()) {
+            return 0; // neither package has dependencies, order is insignificant
+        }
+        if ($a->getDeps() && !$b->getDeps()) {
+            return 1; // $a must be installed after $b because $a has dependencies
+        }
+        if (!$a->getDeps() && $b->getDeps()) {
+            return -1; // $b must be installed after $a because $b has dependencies
+        }
+        // both packages have dependencies
+        if ($this->_dependsOn($a, $b)) {
+            return 1;
+        }
+        if ($this->_dependsOn($b, $a)) {
+            return -1;
+        }
+        return 0;
+    }
+
+    /**
+     * Download a file through HTTP.  Considers suggested file name in
+     * Content-disposition: header and can run a callback function for
+     * different events.  The callback will be called with two
+     * parameters: the callback type, and parameters.  The implemented
+     * callback types are:
+     *
+     *  'setup'       called at the very beginning, parameter is a UI object
+     *                that should be used for all output
+     *  'message'     the parameter is a string with an informational message
+     *  'saveas'      may be used to save with a different file name, the
+     *                parameter is the filename that is about to be used.
+     *                If a 'saveas' callback returns a non-empty string,
+     *                that file name will be used as the filename instead.
+     *                Note that $save_dir will not be affected by this, only
+     *                the basename of the file.
+     *  'start'       download is starting, parameter is number of bytes
+     *                that are expected, or -1 if unknown
+     *  'bytesread'   parameter is the number of bytes read so far
+     *  'done'        download is complete, parameter is the total number
+     *                of bytes read
+     *  'connfailed'  if the TCP/SSL connection fails, this callback is called
+     *                with array(host,port,errno,errmsg)
+     *  'writefailed' if writing to disk fails, this callback is called
+     *                with array(destfile,errmsg)
+     *
+     * If an HTTP proxy has been configured (http_proxy PEAR_Config
+     * setting), the proxy will be used.
+     *
+     * @param string  $url       the URL to download
+     * @param object  $ui        PEAR_Frontend_* instance
+     * @param object  $config    PEAR_Config instance
+     * @param string  $save_dir  directory to save file in
+     * @param mixed   $callback  function/method to call for status
+     *                           updates
+     * @param false|string|array $lastmodified header values to check against for caching
+     *                           use false to return the header values from this download
+     * @param false|array $accept Accept headers to send
+     * @param false|string $channel Channel to use for retrieving authentication
+     * @return string|array  Returns the full path of the downloaded file or a PEAR
+     *                       error on failure.  If the error is caused by
+     *                       socket-related errors, the error object will
+     *                       have the fsockopen error code available through
+     *                       getCode().  If caching is requested, then return the header
+     *                       values.
+     *
+     * @access public
+     */
+    function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
+                          $accept = false, $channel = false)
+    {
+        static $redirect = 0;
+        // always reset , so we are clean case of error
+        $wasredirect = $redirect;
+        $redirect = 0;
+        if ($callback) {
+            call_user_func($callback, 'setup', array(&$ui));
+        }
+
+        $info = parse_url($url);
+        if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
+            return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
+        }
+
+        if (!isset($info['host'])) {
+            return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
+        }
+
+        $host = isset($info['host']) ? $info['host'] : null;
+        $port = isset($info['port']) ? $info['port'] : null;
+        $path = isset($info['path']) ? $info['path'] : null;
+
+        if (isset($this)) {
+            $config = &$this->config;
+        } else {
+            $config = &PEAR_Config::singleton();
+        }
+
+        $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
+        if ($config->get('http_proxy') &&
+              $proxy = parse_url($config->get('http_proxy'))) {
+            $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
+            if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
+                $proxy_host = 'ssl://' . $proxy_host;
+            }
+            $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
+            $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
+            $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
+
+            if ($callback) {
+                call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
+            }
+        }
+
+        if (empty($port)) {
+            $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
+        }
+
+        $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
+
+        if ($proxy_host != '') {
+            $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
+            if (!$fp) {
+                if ($callback) {
+                    call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
+                                                                  $errno, $errstr));
+                }
+                return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
+            }
+
+            if ($lastmodified === false || $lastmodified) {
+                $request  = "GET $url HTTP/1.1\r\n";
+                $request .= "Host: $host\r\n";
+            } else {
+                $request  = "GET $url HTTP/1.0\r\n";
+                $request .= "Host: $host\r\n";
+            }
+        } else {
+            $network_host = $host;
+            if (isset($info['scheme']) && $info['scheme'] == 'https') {
+                $network_host = 'ssl://' . $host;
+            }
+
+            $fp = @fsockopen($network_host, $port, $errno, $errstr);
+            if (!$fp) {
+                if ($callback) {
+                    call_user_func($callback, 'connfailed', array($host, $port,
+                                                                  $errno, $errstr));
+                }
+                return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
+            }
+
+            if ($lastmodified === false || $lastmodified) {
+                $request = "GET $path HTTP/1.1\r\n";
+                $request .= "Host: $host\r\n";
+            } else {
+                $request = "GET $path HTTP/1.0\r\n";
+                $request .= "Host: $host\r\n";
+            }
+        }
+
+        $ifmodifiedsince = '';
+        if (is_array($lastmodified)) {
+            if (isset($lastmodified['Last-Modified'])) {
+                $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
+            }
+
+            if (isset($lastmodified['ETag'])) {
+                $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
+            }
+        } else {
+            $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
+        }
+
+        $request .= $ifmodifiedsince .
+            "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n";
+
+        if (isset($this)) { // only pass in authentication for non-static calls
+            $username = $config->get('username', null, $channel);
+            $password = $config->get('password', null, $channel);
+            if ($username && $password) {
+                $tmp = base64_encode("$username:$password");
+                $request .= "Authorization: Basic $tmp\r\n";
+            }
+        }
+
+        if ($proxy_host != '' && $proxy_user != '') {
+            $request .= 'Proxy-Authorization: Basic ' .
+                base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
+        }
+
+        if ($accept) {
+            $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
+        }
+
+        $request .= "Connection: close\r\n";
+        $request .= "\r\n";
+        fwrite($fp, $request);
+        $headers = array();
+        $reply = 0;
+        while (trim($line = fgets($fp, 1024))) {
+            if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
+                $headers[strtolower($matches[1])] = trim($matches[2]);
+            } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
+                $reply = (int)$matches[1];
+                if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
+                    return false;
+                }
+
+                if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
+                    return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)");
+                }
+            }
+        }
+
+        if ($reply != 200) {
+            if (!isset($headers['location'])) {
+                return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)");
+            }
+
+            if ($wasredirect > 4) {
+                return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)");
+            }
+
+            $redirect = $wasredirect + 1;
+            return $this->downloadHttp($headers['location'],
+                    $ui, $save_dir, $callback, $lastmodified, $accept);
+        }
+
+        if (isset($headers['content-disposition']) &&
+            preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
+            $save_as = basename($matches[1]);
+        } else {
+            $save_as = basename($url);
+        }
+
+        if ($callback) {
+            $tmp = call_user_func($callback, 'saveas', $save_as);
+            if ($tmp) {
+                $save_as = $tmp;
+            }
+        }
+
+        $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
+        if (is_link($dest_file)) {
+            return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack');
+        }
+
+        if (!$wp = @fopen($dest_file, 'wb')) {
+            fclose($fp);
+            if ($callback) {
+                call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
+            }
+            return PEAR::raiseError("could not open $dest_file for writing");
+        }
+
+        $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
+
+        $bytes = 0;
+        if ($callback) {
+            call_user_func($callback, 'start', array(basename($dest_file), $length));
+        }
+
+        while ($data = fread($fp, 1024)) {
+            $bytes += strlen($data);
+            if ($callback) {
+                call_user_func($callback, 'bytesread', $bytes);
+            }
+            if (!@fwrite($wp, $data)) {
+                fclose($fp);
+                if ($callback) {
+                    call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
+                }
+                return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
+            }
+        }
+
+        fclose($fp);
+        fclose($wp);
+        if ($callback) {
+            call_user_func($callback, 'done', $bytes);
+        }
+
+        if ($lastmodified === false || $lastmodified) {
+            if (isset($headers['etag'])) {
+                $lastmodified = array('ETag' => $headers['etag']);
+            }
+
+            if (isset($headers['last-modified'])) {
+                if (is_array($lastmodified)) {
+                    $lastmodified['Last-Modified'] = $headers['last-modified'];
+                } else {
+                    $lastmodified = $headers['last-modified'];
+                }
+            }
+            return array($dest_file, $lastmodified, $headers);
+        }
+        return $dest_file;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Downloader/Package.php b/WEB-INF/lib/pear/PEAR/Downloader/Package.php
new file mode 100644 (file)
index 0000000..987c965
--- /dev/null
@@ -0,0 +1,1988 @@
+<?php
+/**
+ * PEAR_Downloader_Package
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Package.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Error code when parameter initialization fails because no releases
+ * exist within preferred_state, but releases do exist
+ */
+define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003);
+/**
+ * Error code when parameter initialization fails because no releases
+ * exist that will work with the existing PHP version
+ */
+define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004);
+
+/**
+ * Coordinates download parameters and manages their dependencies
+ * prior to downloading them.
+ *
+ * Input can come from three sources:
+ *
+ * - local files (archives or package.xml)
+ * - remote files (downloadable urls)
+ * - abstract package names
+ *
+ * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires
+ * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the
+ * format returned of dependencies is slightly different from that used in package.xml.
+ *
+ * This class hides the differences between these elements, and makes automatic
+ * dependency resolution a piece of cake.  It also manages conflicts when
+ * two classes depend on incompatible dependencies, or differing versions of the same
+ * package dependency.  In addition, download will not be attempted if the php version is
+ * not supported, PEAR installer version is not supported, or non-PECL extensions are not
+ * installed.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Downloader_Package
+{
+    /**
+     * @var PEAR_Downloader
+     */
+    var $_downloader;
+    /**
+     * @var PEAR_Config
+     */
+    var $_config;
+    /**
+     * @var PEAR_Registry
+     */
+    var $_registry;
+    /**
+     * Used to implement packagingroot properly
+     * @var PEAR_Registry
+     */
+    var $_installRegistry;
+    /**
+     * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2
+     */
+    var $_packagefile;
+    /**
+     * @var array
+     */
+    var $_parsedname;
+    /**
+     * @var array
+     */
+    var $_downloadURL;
+    /**
+     * @var array
+     */
+    var $_downloadDeps = array();
+    /**
+     * @var boolean
+     */
+    var $_valid = false;
+    /**
+     * @var boolean
+     */
+    var $_analyzed = false;
+    /**
+     * if this or a parent package was invoked with Package-state, this is set to the
+     * state variable.
+     *
+     * This allows temporary reassignment of preferred_state for a parent package and all of
+     * its dependencies.
+     * @var string|false
+     */
+    var $_explicitState = false;
+    /**
+     * If this package is invoked with Package#group, this variable will be true
+     */
+    var $_explicitGroup = false;
+    /**
+     * Package type local|url
+     * @var string
+     */
+    var $_type;
+    /**
+     * Contents of package.xml, if downloaded from a remote channel
+     * @var string|false
+     * @access private
+     */
+    var $_rawpackagefile;
+    /**
+     * @var boolean
+     * @access private
+     */
+    var $_validated = false;
+
+    /**
+     * @param PEAR_Downloader
+     */
+    function PEAR_Downloader_Package(&$downloader)
+    {
+        $this->_downloader = &$downloader;
+        $this->_config = &$this->_downloader->config;
+        $this->_registry = &$this->_config->getRegistry();
+        $options = $downloader->getOptions();
+        if (isset($options['packagingroot'])) {
+            $this->_config->setInstallRoot($options['packagingroot']);
+            $this->_installRegistry = &$this->_config->getRegistry();
+            $this->_config->setInstallRoot(false);
+        } else {
+            $this->_installRegistry = &$this->_registry;
+        }
+        $this->_valid = $this->_analyzed = false;
+    }
+
+    /**
+     * Parse the input and determine whether this is a local file, a remote uri, or an
+     * abstract package name.
+     *
+     * This is the heart of the PEAR_Downloader_Package(), and is used in
+     * {@link PEAR_Downloader::download()}
+     * @param string
+     * @return bool|PEAR_Error
+     */
+    function initialize($param)
+    {
+        $origErr = $this->_fromFile($param);
+        if ($this->_valid) {
+            return true;
+        }
+
+        $options = $this->_downloader->getOptions();
+        if (isset($options['offline'])) {
+            if (PEAR::isError($origErr) && !isset($options['soft'])) {
+                foreach ($origErr->getUserInfo() as $userInfo) {
+                    if (isset($userInfo['message'])) {
+                        $this->_downloader->log(0, $userInfo['message']);
+                    }
+                }
+
+                $this->_downloader->log(0, $origErr->getMessage());
+            }
+
+            return PEAR::raiseError('Cannot download non-local package "' . $param . '"');
+        }
+
+        $err = $this->_fromUrl($param);
+        if (PEAR::isError($err) || !$this->_valid) {
+            if ($this->_type == 'url') {
+                if (PEAR::isError($err) && !isset($options['soft'])) {
+                    $this->_downloader->log(0, $err->getMessage());
+                }
+
+                return PEAR::raiseError("Invalid or missing remote package file");
+            }
+
+            $err = $this->_fromString($param);
+            if (PEAR::isError($err) || !$this->_valid) {
+                if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) {
+                    return false; // instruct the downloader to silently skip
+                }
+
+                if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) {
+                    if (is_array($origErr->getUserInfo())) {
+                        foreach ($origErr->getUserInfo() as $err) {
+                            if (is_array($err)) {
+                                $err = $err['message'];
+                            }
+
+                            if (!isset($options['soft'])) {
+                                $this->_downloader->log(0, $err);
+                            }
+                        }
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(0, $origErr->getMessage());
+                    }
+
+                    if (is_array($param)) {
+                        $param = $this->_registry->parsedPackageNameToString($param, true);
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
+                    }
+
+                    // Passing no message back - already logged above
+                    return PEAR::raiseError();
+                }
+
+                if (PEAR::isError($err) && !isset($options['soft'])) {
+                    $this->_downloader->log(0, $err->getMessage());
+                }
+
+                if (is_array($param)) {
+                    $param = $this->_registry->parsedPackageNameToString($param, true);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
+                }
+
+                // Passing no message back - already logged above
+                return PEAR::raiseError();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Retrieve any non-local packages
+     * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error
+     */
+    function &download()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile;
+        }
+
+        if (isset($this->_downloadURL['url'])) {
+            $this->_isvalid = false;
+            $info = $this->getParsedPackage();
+            foreach ($info as $i => $p) {
+                $info[$i] = strtolower($p);
+            }
+
+            $err = $this->_fromUrl($this->_downloadURL['url'],
+                $this->_registry->parsedPackageNameToString($this->_parsedname, true));
+            $newinfo = $this->getParsedPackage();
+            foreach ($newinfo as $i => $p) {
+                $newinfo[$i] = strtolower($p);
+            }
+
+            if ($info != $newinfo) {
+                do {
+                    if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') {
+                        $info['channel'] = 'pear.php.net';
+                        if ($info == $newinfo) {
+                            // skip the channel check if a pecl package says it's a PEAR package
+                            break;
+                        }
+                    }
+                    if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') {
+                        $info['channel'] = 'pecl.php.net';
+                        if ($info == $newinfo) {
+                            // skip the channel check if a pecl package says it's a PEAR package
+                            break;
+                        }
+                    }
+
+                    return PEAR::raiseError('CRITICAL ERROR: We are ' .
+                        $this->_registry->parsedPackageNameToString($info) . ', but the file ' .
+                        'downloaded claims to be ' .
+                        $this->_registry->parsedPackageNameToString($this->getParsedPackage()));
+                } while (false);
+            }
+
+            if (PEAR::isError($err) || !$this->_valid) {
+                return $err;
+            }
+        }
+
+        $this->_type = 'local';
+        return $this->_packagefile;
+    }
+
+    function &getPackageFile()
+    {
+        return $this->_packagefile;
+    }
+
+    function &getDownloader()
+    {
+        return $this->_downloader;
+    }
+
+    function getType()
+    {
+        return $this->_type;
+    }
+
+    /**
+     * Like {@link initialize()}, but operates on a dependency
+     */
+    function fromDepURL($dep)
+    {
+        $this->_downloadURL = $dep;
+        if (isset($dep['uri'])) {
+            $options = $this->_downloader->getOptions();
+            if (!extension_loaded("zlib") || isset($options['nocompress'])) {
+                $ext = '.tar';
+            } else {
+                $ext = '.tgz';
+            }
+
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $err = $this->_fromUrl($dep['uri'] . $ext);
+            PEAR::popErrorHandling();
+            if (PEAR::isError($err)) {
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(0, $err->getMessage());
+                }
+
+                return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' .
+                    'cannot download');
+            }
+        } else {
+            $this->_parsedname =
+                array(
+                    'package' => $dep['info']->getPackage(),
+                    'channel' => $dep['info']->getChannel(),
+                    'version' => $dep['version']
+                );
+            if (!isset($dep['nodefault'])) {
+                $this->_parsedname['group'] = 'default'; // download the default dependency group
+                $this->_explicitGroup = false;
+            }
+
+            $this->_rawpackagefile = $dep['raw'];
+        }
+    }
+
+    function detectDependencies($params)
+    {
+        $options = $this->_downloader->getOptions();
+        if (isset($options['downloadonly'])) {
+            return;
+        }
+
+        if (isset($options['offline'])) {
+            $this->_downloader->log(3, 'Skipping dependency download check, --offline specified');
+            return;
+        }
+
+        $pname = $this->getParsedPackage();
+        if (!$pname) {
+            return;
+        }
+
+        $deps = $this->getDeps();
+        if (!$deps) {
+            return;
+        }
+
+        if (isset($deps['required'])) { // package.xml 2.0
+            return $this->_detect2($deps, $pname, $options, $params);
+        }
+
+        return $this->_detect1($deps, $pname, $options, $params);
+    }
+
+    function setValidated()
+    {
+        $this->_validated = true;
+    }
+
+    function alreadyValidated()
+    {
+        return $this->_validated;
+    }
+
+    /**
+     * Remove packages to be downloaded that are already installed
+     * @param array of PEAR_Downloader_Package objects
+     * @static
+     */
+    function removeInstalled(&$params)
+    {
+        if (!isset($params[0])) {
+            return;
+        }
+
+        $options = $params[0]->_downloader->getOptions();
+        if (!isset($options['downloadonly'])) {
+            foreach ($params as $i => $param) {
+                $package = $param->getPackage();
+                $channel = $param->getChannel();
+                // remove self if already installed with this version
+                // this does not need any pecl magic - we only remove exact matches
+                if ($param->_installRegistry->packageExists($package, $channel)) {
+                    $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel);
+                    if (version_compare($packageVersion, $param->getVersion(), '==')) {
+                        if (!isset($options['force'])) {
+                            $info = $param->getParsedPackage();
+                            unset($info['version']);
+                            unset($info['state']);
+                            if (!isset($options['soft'])) {
+                                $param->_downloader->log(1, 'Skipping package "' .
+                                    $param->getShortName() .
+                                    '", already installed as version ' . $packageVersion);
+                            }
+                            $params[$i] = false;
+                        }
+                    } elseif (!isset($options['force']) && !isset($options['upgrade']) &&
+                          !isset($options['soft'])) {
+                        $info = $param->getParsedPackage();
+                        $param->_downloader->log(1, 'Skipping package "' .
+                            $param->getShortName() .
+                            '", already installed as version ' . $packageVersion);
+                        $params[$i] = false;
+                    }
+                }
+            }
+        }
+
+        PEAR_Downloader_Package::removeDuplicates($params);
+    }
+
+    function _detect2($deps, $pname, $options, $params)
+    {
+        $this->_downloadDeps = array();
+        $groupnotfound = false;
+        foreach (array('package', 'subpackage') as $packagetype) {
+            // get required dependency group
+            if (isset($deps['required'][$packagetype])) {
+                if (isset($deps['required'][$packagetype][0])) {
+                    foreach ($deps['required'][$packagetype] as $dep) {
+                        if (isset($dep['conflicts'])) {
+                            // skip any package that this package conflicts with
+                            continue;
+                        }
+                        $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
+                        if (is_array($ret)) {
+                            $this->_downloadDeps[] = $ret;
+                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+                            $this->_downloader->log(0, $ret->getMessage());
+                        }
+                    }
+                } else {
+                    $dep = $deps['required'][$packagetype];
+                    if (!isset($dep['conflicts'])) {
+                        // skip any package that this package conflicts with
+                        $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
+                        if (is_array($ret)) {
+                            $this->_downloadDeps[] = $ret;
+                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+                            $this->_downloader->log(0, $ret->getMessage());
+                        }
+                    }
+                }
+            }
+
+            // get optional dependency group, if any
+            if (isset($deps['optional'][$packagetype])) {
+                $skipnames = array();
+                if (!isset($deps['optional'][$packagetype][0])) {
+                    $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]);
+                }
+
+                foreach ($deps['optional'][$packagetype] as $dep) {
+                    $skip = false;
+                    if (!isset($options['alldeps'])) {
+                        $dep['package'] = $dep['name'];
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(3, 'Notice: package "' .
+                              $this->_registry->parsedPackageNameToString($this->getParsedPackage(),
+                                    true) . '" optional dependency "' .
+                                $this->_registry->parsedPackageNameToString(array('package' =>
+                                    $dep['name'], 'channel' => 'pear.php.net'), true) .
+                                '" will not be automatically downloaded');
+                        }
+                        $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true);
+                        $skip = true;
+                        unset($dep['package']);
+                    }
+
+                    $ret = $this->_detect2Dep($dep, $pname, 'optional', $params);
+                    if (PEAR::isError($ret) && !isset($options['soft'])) {
+                        $this->_downloader->log(0, $ret->getMessage());
+                    }
+
+                    if (!$ret) {
+                        $dep['package'] = $dep['name'];
+                        $skip = count($skipnames) ?
+                            $skipnames[count($skipnames) - 1] : '';
+                        if ($skip ==
+                              $this->_registry->parsedPackageNameToString($dep, true)) {
+                            array_pop($skipnames);
+                        }
+                    }
+
+                    if (!$skip && is_array($ret)) {
+                        $this->_downloadDeps[] = $ret;
+                    }
+                }
+
+                if (count($skipnames)) {
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(1, 'Did not download optional dependencies: ' .
+                            implode(', ', $skipnames) .
+                            ', use --alldeps to download automatically');
+                    }
+                }
+            }
+
+            // get requested dependency group, if any
+            $groupname = $this->getGroup();
+            $explicit  = $this->_explicitGroup;
+            if (!$groupname) {
+                if (!$this->canDefault()) {
+                    continue;
+                }
+
+                $groupname = 'default'; // try the default dependency group
+            }
+
+            if ($groupnotfound) {
+                continue;
+            }
+
+            if (isset($deps['group'])) {
+                if (isset($deps['group']['attribs'])) {
+                    if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) {
+                        $group = $deps['group'];
+                    } elseif ($explicit) {
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(0, 'Warning: package "' .
+                                $this->_registry->parsedPackageNameToString($pname, true) .
+                                '" has no dependency ' . 'group named "' . $groupname . '"');
+                        }
+
+                        $groupnotfound = true;
+                        continue;
+                    }
+                } else {
+                    $found = false;
+                    foreach ($deps['group'] as $group) {
+                        if (strtolower($group['attribs']['name']) == strtolower($groupname)) {
+                            $found = true;
+                            break;
+                        }
+                    }
+
+                    if (!$found) {
+                        if ($explicit) {
+                            if (!isset($options['soft'])) {
+                                $this->_downloader->log(0, 'Warning: package "' .
+                                    $this->_registry->parsedPackageNameToString($pname, true) .
+                                    '" has no dependency ' . 'group named "' . $groupname . '"');
+                            }
+                        }
+
+                        $groupnotfound = true;
+                        continue;
+                    }
+                }
+            }
+
+            if (isset($group) && isset($group[$packagetype])) {
+                if (isset($group[$packagetype][0])) {
+                    foreach ($group[$packagetype] as $dep) {
+                        $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' .
+                            $group['attribs']['name'] . '"', $params);
+                        if (is_array($ret)) {
+                            $this->_downloadDeps[] = $ret;
+                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+                            $this->_downloader->log(0, $ret->getMessage());
+                        }
+                    }
+                } else {
+                    $ret = $this->_detect2Dep($group[$packagetype], $pname,
+                        'dependency group "' .
+                        $group['attribs']['name'] . '"', $params);
+                    if (is_array($ret)) {
+                        $this->_downloadDeps[] = $ret;
+                    } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+                        $this->_downloader->log(0, $ret->getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    function _detect2Dep($dep, $pname, $group, $params)
+    {
+        if (isset($dep['conflicts'])) {
+            return true;
+        }
+
+        $options = $this->_downloader->getOptions();
+        if (isset($dep['uri'])) {
+            return array('uri' => $dep['uri'], 'dep' => $dep);;
+        }
+
+        $testdep = $dep;
+        $testdep['package'] = $dep['name'];
+        if (PEAR_Downloader_Package::willDownload($testdep, $params)) {
+            $dep['package'] = $dep['name'];
+            if (!isset($options['soft'])) {
+                $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group .
+                    ' dependency "' .
+                    $this->_registry->parsedPackageNameToString($dep, true) .
+                    '", will be installed');
+            }
+            return false;
+        }
+
+        $options = $this->_downloader->getOptions();
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        if ($this->_explicitState) {
+            $pname['state'] = $this->_explicitState;
+        }
+
+        $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
+        if (PEAR::isError($url)) {
+            PEAR::popErrorHandling();
+            return $url;
+        }
+
+        $dep['package'] = $dep['name'];
+        $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' &&
+            !isset($options['alldeps']), true);
+        PEAR::popErrorHandling();
+        if (PEAR::isError($ret)) {
+            if (!isset($options['soft'])) {
+                $this->_downloader->log(0, $ret->getMessage());
+            }
+
+            return false;
+        }
+
+        // check to see if a dep is already installed and is the same or newer
+        if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) {
+            $oper = 'has';
+        } else {
+            $oper = 'gt';
+        }
+
+        // do not try to move this before getDepPackageDownloadURL
+        // we can't determine whether upgrade is necessary until we know what
+        // version would be downloaded
+        if (!isset($options['force']) && $this->isInstalled($ret, $oper)) {
+            $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']);
+            $dep['package'] = $dep['name'];
+            if (!isset($options['soft'])) {
+                $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
+                    ' dependency "' .
+                $this->_registry->parsedPackageNameToString($dep, true) .
+                    '" version ' . $url['version'] . ', already installed as version ' .
+                    $version);
+            }
+
+            return false;
+        }
+
+        if (isset($dep['nodefault'])) {
+            $ret['nodefault'] = true;
+        }
+
+        return $ret;
+    }
+
+    function _detect1($deps, $pname, $options, $params)
+    {
+        $this->_downloadDeps = array();
+        $skipnames = array();
+        foreach ($deps as $dep) {
+            $nodownload = false;
+            if (isset ($dep['type']) && $dep['type'] === 'pkg') {
+                $dep['channel'] = 'pear.php.net';
+                $dep['package'] = $dep['name'];
+                switch ($dep['rel']) {
+                    case 'not' :
+                        continue 2;
+                    case 'ge' :
+                    case 'eq' :
+                    case 'gt' :
+                    case 'has' :
+                        $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+                            'required' :
+                            'optional';
+                        if (PEAR_Downloader_Package::willDownload($dep, $params)) {
+                            $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
+                                . ' dependency "' .
+                                $this->_registry->parsedPackageNameToString($dep, true) .
+                                '", will be installed');
+                            continue 2;
+                        }
+                        $fakedp = new PEAR_PackageFile_v1;
+                        $fakedp->setPackage($dep['name']);
+                        // skip internet check if we are not upgrading (bug #5810)
+                        if (!isset($options['upgrade']) && $this->isInstalled(
+                              $fakedp, $dep['rel'])) {
+                            $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
+                                . ' dependency "' .
+                                $this->_registry->parsedPackageNameToString($dep, true) .
+                                '", is already installed');
+                            continue 2;
+                        }
+                }
+
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                if ($this->_explicitState) {
+                    $pname['state'] = $this->_explicitState;
+                }
+
+                $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
+                $chan = 'pear.php.net';
+                if (PEAR::isError($url)) {
+                    // check to see if this is a pecl package that has jumped
+                    // from pear.php.net to pecl.php.net channel
+                    if (!class_exists('PEAR_Dependency2')) {
+                        require_once 'PEAR/Dependency2.php';
+                    }
+
+                    $newdep = PEAR_Dependency2::normalizeDep($dep);
+                    $newdep = $newdep[0];
+                    $newdep['channel'] = 'pecl.php.net';
+                    $chan = 'pecl.php.net';
+                    $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname);
+                    $obj = &$this->_installRegistry->getPackage($dep['name']);
+                    if (PEAR::isError($url)) {
+                        PEAR::popErrorHandling();
+                        if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) {
+                            $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+                                'required' :
+                                'optional';
+                            $dep['package'] = $dep['name'];
+                            if (!isset($options['soft'])) {
+                                $this->_downloader->log(3, $this->getShortName() .
+                                    ': Skipping ' . $group . ' dependency "' .
+                                    $this->_registry->parsedPackageNameToString($dep, true) .
+                                    '", already installed as version ' . $obj->getVersion());
+                            }
+                            $skip = count($skipnames) ?
+                                $skipnames[count($skipnames) - 1] : '';
+                            if ($skip ==
+                                  $this->_registry->parsedPackageNameToString($dep, true)) {
+                                array_pop($skipnames);
+                            }
+                            continue;
+                        } else {
+                            if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+                                $this->_downloader->log(2, $this->getShortName() .
+                                    ': Skipping optional dependency "' .
+                                    $this->_registry->parsedPackageNameToString($dep, true) .
+                                    '", no releases exist');
+                                continue;
+                            } else {
+                                return $url;
+                            }
+                        }
+                    }
+                }
+
+                PEAR::popErrorHandling();
+                if (!isset($options['alldeps'])) {
+                    if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(3, 'Notice: package "' .
+                                $this->getShortName() .
+                                '" optional dependency "' .
+                                $this->_registry->parsedPackageNameToString(
+                                    array('channel' => $chan, 'package' =>
+                                    $dep['name']), true) .
+                                '" will not be automatically downloaded');
+                        }
+                        $skipnames[] = $this->_registry->parsedPackageNameToString(
+                                array('channel' => $chan, 'package' =>
+                                $dep['name']), true);
+                        $nodownload = true;
+                    }
+                }
+
+                if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) {
+                    if (!isset($dep['optional']) || $dep['optional'] == 'no') {
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(3, 'Notice: package "' .
+                                $this->getShortName() .
+                                '" required dependency "' .
+                                $this->_registry->parsedPackageNameToString(
+                                    array('channel' => $chan, 'package' =>
+                                    $dep['name']), true) .
+                                '" will not be automatically downloaded');
+                        }
+                        $skipnames[] = $this->_registry->parsedPackageNameToString(
+                                array('channel' => $chan, 'package' =>
+                                $dep['name']), true);
+                        $nodownload = true;
+                    }
+                }
+
+                // check to see if a dep is already installed
+                // do not try to move this before getDepPackageDownloadURL
+                // we can't determine whether upgrade is necessary until we know what
+                // version would be downloaded
+                if (!isset($options['force']) && $this->isInstalled(
+                        $url, $dep['rel'])) {
+                    $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+                        'required' :
+                        'optional';
+                    $dep['package'] = $dep['name'];
+                    if (isset($newdep)) {
+                        $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']);
+                    } else {
+                        $version = $this->_installRegistry->packageInfo($dep['name'], 'version');
+                    }
+
+                    $dep['version'] = $url['version'];
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
+                            ' dependency "' .
+                            $this->_registry->parsedPackageNameToString($dep, true) .
+                            '", already installed as version ' . $version);
+                    }
+
+                    $skip = count($skipnames) ?
+                        $skipnames[count($skipnames) - 1] : '';
+                    if ($skip ==
+                          $this->_registry->parsedPackageNameToString($dep, true)) {
+                        array_pop($skipnames);
+                    }
+
+                    continue;
+                }
+
+                if ($nodownload) {
+                    continue;
+                }
+
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                if (isset($newdep)) {
+                    $dep = $newdep;
+                }
+
+                $dep['package'] = $dep['name'];
+                $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params,
+                    isset($dep['optional']) && $dep['optional'] == 'yes' &&
+                    !isset($options['alldeps']), true);
+                PEAR::popErrorHandling();
+                if (PEAR::isError($ret)) {
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(0, $ret->getMessage());
+                    }
+                    continue;
+                }
+
+                $this->_downloadDeps[] = $ret;
+            }
+        }
+
+        if (count($skipnames)) {
+            if (!isset($options['soft'])) {
+                $this->_downloader->log(1, 'Did not download dependencies: ' .
+                    implode(', ', $skipnames) .
+                    ', use --alldeps or --onlyreqdeps to download automatically');
+            }
+        }
+    }
+
+    function setDownloadURL($pkg)
+    {
+        $this->_downloadURL = $pkg;
+    }
+
+    /**
+     * Set the package.xml object for this downloaded package
+     *
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg
+     */
+    function setPackageFile(&$pkg)
+    {
+        $this->_packagefile = &$pkg;
+    }
+
+    function getShortName()
+    {
+        return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(),
+            'package' => $this->getPackage()), true);
+    }
+
+    function getParsedPackage()
+    {
+        if (isset($this->_packagefile) || isset($this->_parsedname)) {
+            return array('channel' => $this->getChannel(),
+                'package' => $this->getPackage(),
+                'version' => $this->getVersion());
+        }
+
+        return false;
+    }
+
+    function getDownloadURL()
+    {
+        return $this->_downloadURL;
+    }
+
+    function canDefault()
+    {
+        if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    function getPackage()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getPackage();
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->getPackage();
+        }
+
+        return false;
+    }
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     */
+    function isSubpackage(&$pf)
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->isSubpackage($pf);
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->isSubpackage($pf);
+        }
+
+        return false;
+    }
+
+    function getPackageType()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getPackageType();
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->getPackageType();
+        }
+
+        return false;
+    }
+
+    function isBundle()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getPackageType() == 'bundle';
+        }
+
+        return false;
+    }
+
+    function getPackageXmlVersion()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getPackagexmlVersion();
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->getPackagexmlVersion();
+        }
+
+        return '1.0';
+    }
+
+    function getChannel()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getChannel();
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->getChannel();
+        }
+
+        return false;
+    }
+
+    function getURI()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getURI();
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->getURI();
+        }
+
+        return false;
+    }
+
+    function getVersion()
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->getVersion();
+        } elseif (isset($this->_downloadURL['version'])) {
+            return $this->_downloadURL['version'];
+        }
+
+        return false;
+    }
+
+    function isCompatible($pf)
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->isCompatible($pf);
+        } elseif (isset($this->_downloadURL['info'])) {
+            return $this->_downloadURL['info']->isCompatible($pf);
+        }
+
+        return true;
+    }
+
+    function setGroup($group)
+    {
+        $this->_parsedname['group'] = $group;
+    }
+
+    function getGroup()
+    {
+        if (isset($this->_parsedname['group'])) {
+            return $this->_parsedname['group'];
+        }
+
+        return '';
+    }
+
+    function isExtension($name)
+    {
+        if (isset($this->_packagefile)) {
+            return $this->_packagefile->isExtension($name);
+        } elseif (isset($this->_downloadURL['info'])) {
+            if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') {
+                return $this->_downloadURL['info']->getProvidesExtension() == $name;
+            }
+
+            return false;
+        }
+
+        return false;
+    }
+
+    function getDeps()
+    {
+        if (isset($this->_packagefile)) {
+            $ver = $this->_packagefile->getPackagexmlVersion();
+            if (version_compare($ver, '2.0', '>=')) {
+                return $this->_packagefile->getDeps(true);
+            }
+
+            return $this->_packagefile->getDeps();
+        } elseif (isset($this->_downloadURL['info'])) {
+            $ver = $this->_downloadURL['info']->getPackagexmlVersion();
+            if (version_compare($ver, '2.0', '>=')) {
+                return $this->_downloadURL['info']->getDeps(true);
+            }
+
+            return $this->_downloadURL['info']->getDeps();
+        }
+
+        return array();
+    }
+
+    /**
+     * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency
+     *                     returned from getDepDownloadURL()
+     */
+    function isEqual($param)
+    {
+        if (is_object($param)) {
+            $channel = $param->getChannel();
+            $package = $param->getPackage();
+            if ($param->getURI()) {
+                $param = array(
+                    'channel' => $param->getChannel(),
+                    'package' => $param->getPackage(),
+                    'version' => $param->getVersion(),
+                    'uri' => $param->getURI(),
+                );
+            } else {
+                $param = array(
+                    'channel' => $param->getChannel(),
+                    'package' => $param->getPackage(),
+                    'version' => $param->getVersion(),
+                );
+            }
+        } else {
+            if (isset($param['uri'])) {
+                if ($this->getChannel() != '__uri') {
+                    return false;
+                }
+                return $param['uri'] == $this->getURI();
+            }
+
+            $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage();
+            $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel();
+            if (isset($param['rel'])) {
+                if (!class_exists('PEAR_Dependency2')) {
+                    require_once 'PEAR/Dependency2.php';
+                }
+
+                $newdep = PEAR_Dependency2::normalizeDep($param);
+                $newdep = $newdep[0];
+            } elseif (isset($param['min'])) {
+                $newdep = $param;
+            }
+        }
+
+        if (isset($newdep)) {
+            if (!isset($newdep['min'])) {
+                $newdep['min'] = '0';
+            }
+
+            if (!isset($newdep['max'])) {
+                $newdep['max'] = '100000000000000000000';
+            }
+
+            // use magic to support pecl packages suddenly jumping to the pecl channel
+            // we need to support both dependency possibilities
+            if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') {
+                if ($package == $this->getPackage()) {
+                    $channel = 'pecl.php.net';
+                }
+            }
+            if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
+                if ($package == $this->getPackage()) {
+                    $channel = 'pear.php.net';
+                }
+            }
+
+            return (strtolower($package) == strtolower($this->getPackage()) &&
+                $channel == $this->getChannel() &&
+                version_compare($newdep['min'], $this->getVersion(), '<=') &&
+                version_compare($newdep['max'], $this->getVersion(), '>='));
+        }
+
+        // use magic to support pecl packages suddenly jumping to the pecl channel
+        if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
+            if (strtolower($package) == strtolower($this->getPackage())) {
+                $channel = 'pear.php.net';
+            }
+        }
+
+        if (isset($param['version'])) {
+            return (strtolower($package) == strtolower($this->getPackage()) &&
+                $channel == $this->getChannel() &&
+                $param['version'] == $this->getVersion());
+        }
+
+        return strtolower($package) == strtolower($this->getPackage()) &&
+            $channel == $this->getChannel();
+    }
+
+    function isInstalled($dep, $oper = '==')
+    {
+        if (!$dep) {
+            return false;
+        }
+
+        if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') {
+            return false;
+        }
+
+        if (is_object($dep)) {
+            $package = $dep->getPackage();
+            $channel = $dep->getChannel();
+            if ($dep->getURI()) {
+                $dep = array(
+                    'uri' => $dep->getURI(),
+                    'version' => $dep->getVersion(),
+                );
+            } else {
+                $dep = array(
+                    'version' => $dep->getVersion(),
+                );
+            }
+        } else {
+            if (isset($dep['uri'])) {
+                $channel = '__uri';
+                $package = $dep['dep']['name'];
+            } else {
+                $channel = $dep['info']->getChannel();
+                $package = $dep['info']->getPackage();
+            }
+        }
+
+        $options = $this->_downloader->getOptions();
+        $test    = $this->_installRegistry->packageExists($package, $channel);
+        if (!$test && $channel == 'pecl.php.net') {
+            // do magic to allow upgrading from old pecl packages to new ones
+            $test = $this->_installRegistry->packageExists($package, 'pear.php.net');
+            $channel = 'pear.php.net';
+        }
+
+        if ($test) {
+            if (isset($dep['uri'])) {
+                if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) {
+                    return true;
+                }
+            }
+
+            if (isset($options['upgrade'])) {
+                $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel);
+                if (version_compare($packageVersion, $dep['version'], '>=')) {
+                    return true;
+                }
+
+                return false;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Detect duplicate package names with differing versions
+     *
+     * If a user requests to install Date 1.4.6 and Date 1.4.7,
+     * for instance, this is a logic error.  This method
+     * detects this situation.
+     *
+     * @param array $params array of PEAR_Downloader_Package objects
+     * @param array $errorparams empty array
+     * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts
+     */
+    function detectStupidDuplicates($params, &$errorparams)
+    {
+        $existing = array();
+        foreach ($params as $i => $param) {
+            $package = $param->getPackage();
+            $channel = $param->getChannel();
+            $group   = $param->getGroup();
+            if (!isset($existing[$channel . '/' . $package])) {
+                $existing[$channel . '/' . $package] = array();
+            }
+
+            if (!isset($existing[$channel . '/' . $package][$group])) {
+                $existing[$channel . '/' . $package][$group] = array();
+            }
+
+            $existing[$channel . '/' . $package][$group][] = $i;
+        }
+
+        $indices = array();
+        foreach ($existing as $package => $groups) {
+            foreach ($groups as $group => $dupes) {
+                if (count($dupes) > 1) {
+                    $indices = $indices + $dupes;
+                }
+            }
+        }
+
+        $indices = array_unique($indices);
+        foreach ($indices as $index) {
+            $errorparams[] = $params[$index];
+        }
+
+        return count($errorparams);
+    }
+
+    /**
+     * @param array
+     * @param bool ignore install groups - for final removal of dupe packages
+     * @static
+     */
+    function removeDuplicates(&$params, $ignoreGroups = false)
+    {
+        $pnames = array();
+        foreach ($params as $i => $param) {
+            if (!$param) {
+                continue;
+            }
+
+            if ($param->getPackage()) {
+                $group = $ignoreGroups ? '' : $param->getGroup();
+                $pnames[$i] = $param->getChannel() . '/' .
+                    $param->getPackage() . '-' . $param->getVersion() . '#' . $group;
+            }
+        }
+
+        $pnames = array_unique($pnames);
+        $unset  = array_diff(array_keys($params), array_keys($pnames));
+        $testp  = array_flip($pnames);
+        foreach ($params as $i => $param) {
+            if (!$param) {
+                $unset[] = $i;
+                continue;
+            }
+
+            if (!is_a($param, 'PEAR_Downloader_Package')) {
+                $unset[] = $i;
+                continue;
+            }
+
+            $group = $ignoreGroups ? '' : $param->getGroup();
+            if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' .
+                  $param->getVersion() . '#' . $group])) {
+                $unset[] = $i;
+            }
+        }
+
+        foreach ($unset as $i) {
+            unset($params[$i]);
+        }
+
+        $ret = array();
+        foreach ($params as $i => $param) {
+            $ret[] = &$params[$i];
+        }
+
+        $params = array();
+        foreach ($ret as $i => $param) {
+            $params[] = &$ret[$i];
+        }
+    }
+
+    function explicitState()
+    {
+        return $this->_explicitState;
+    }
+
+    function setExplicitState($s)
+    {
+        $this->_explicitState = $s;
+    }
+
+    /**
+     * @static
+     */
+    function mergeDependencies(&$params)
+    {
+        $bundles = $newparams = array();
+        foreach ($params as $i => $param) {
+            if (!$param->isBundle()) {
+                continue;
+            }
+
+            $bundles[] = $i;
+            $pf = &$param->getPackageFile();
+            $newdeps = array();
+            $contents = $pf->getBundledPackages();
+            if (!is_array($contents)) {
+                $contents = array($contents);
+            }
+
+            foreach ($contents as $file) {
+                $filecontents = $pf->getFileContents($file);
+                $dl = &$param->getDownloader();
+                $options = $dl->getOptions();
+                if (PEAR::isError($dir = $dl->getDownloadDir())) {
+                    return $dir;
+                }
+
+                $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb');
+                if (!$fp) {
+                    continue;
+                }
+
+                // FIXME do symlink check
+
+                fwrite($fp, $filecontents, strlen($filecontents));
+                fclose($fp);
+                if ($s = $params[$i]->explicitState()) {
+                    $obj->setExplicitState($s);
+                }
+
+                $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                if (PEAR::isError($dir = $dl->getDownloadDir())) {
+                    PEAR::popErrorHandling();
+                    return $dir;
+                }
+
+                $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file);
+                PEAR::popErrorHandling();
+                if (PEAR::isError($e)) {
+                    if (!isset($options['soft'])) {
+                        $dl->log(0, $e->getMessage());
+                    }
+                    continue;
+                }
+
+                $j = &$obj;
+                if (!PEAR_Downloader_Package::willDownload($j,
+                      array_merge($params, $newparams)) && !$param->isInstalled($j)) {
+                    $newparams[] = &$j;
+                }
+            }
+        }
+
+        foreach ($bundles as $i) {
+            unset($params[$i]); // remove bundles - only their contents matter for installation
+        }
+
+        PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices
+        if (count($newparams)) { // add in bundled packages for install
+            foreach ($newparams as $i => $unused) {
+                $params[] = &$newparams[$i];
+            }
+            $newparams = array();
+        }
+
+        foreach ($params as $i => $param) {
+            $newdeps = array();
+            foreach ($param->_downloadDeps as $dep) {
+                $merge = array_merge($params, $newparams);
+                if (!PEAR_Downloader_Package::willDownload($dep, $merge)
+                    && !$param->isInstalled($dep)
+                ) {
+                    $newdeps[] = $dep;
+                } else {
+                    //var_dump($dep);
+                    // detect versioning conflicts here
+                }
+            }
+
+            // convert the dependencies into PEAR_Downloader_Package objects for the next time around
+            $params[$i]->_downloadDeps = array();
+            foreach ($newdeps as $dep) {
+                $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
+                if ($s = $params[$i]->explicitState()) {
+                    $obj->setExplicitState($s);
+                }
+
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                $e = $obj->fromDepURL($dep);
+                PEAR::popErrorHandling();
+                if (PEAR::isError($e)) {
+                    if (!isset($options['soft'])) {
+                        $obj->_downloader->log(0, $e->getMessage());
+                    }
+                    continue;
+                }
+
+                $e = $obj->detectDependencies($params);
+                if (PEAR::isError($e)) {
+                    if (!isset($options['soft'])) {
+                        $obj->_downloader->log(0, $e->getMessage());
+                    }
+                }
+
+                $j = &$obj;
+                $newparams[] = &$j;
+            }
+        }
+
+        if (count($newparams)) {
+            foreach ($newparams as $i => $unused) {
+                $params[] = &$newparams[$i];
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * @static
+     */
+    function willDownload($param, $params)
+    {
+        if (!is_array($params)) {
+            return false;
+        }
+
+        foreach ($params as $obj) {
+            if ($obj->isEqual($param)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * For simpler unit-testing
+     * @param PEAR_Config
+     * @param int
+     * @param string
+     */
+    function &getPackagefileObject(&$c, $d)
+    {
+        $a = &new PEAR_PackageFile($c, $d);
+        return $a;
+    }
+
+    /**
+     * This will retrieve from a local file if possible, and parse out
+     * a group name as well.  The original parameter will be modified to reflect this.
+     * @param string|array can be a parsed package name as well
+     * @access private
+     */
+    function _fromFile(&$param)
+    {
+        $saveparam = $param;
+        if (is_string($param)) {
+            if (!@file_exists($param)) {
+                $test = explode('#', $param);
+                $group = array_pop($test);
+                if (@file_exists(implode('#', $test))) {
+                    $this->setGroup($group);
+                    $param = implode('#', $test);
+                    $this->_explicitGroup = true;
+                }
+            }
+
+            if (@is_file($param)) {
+                $this->_type = 'local';
+                $options = $this->_downloader->getOptions();
+                $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug);
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING);
+                PEAR::popErrorHandling();
+                if (PEAR::isError($pf)) {
+                    $this->_valid = false;
+                    $param = $saveparam;
+                    return $pf;
+                }
+                $this->_packagefile = &$pf;
+                if (!$this->getGroup()) {
+                    $this->setGroup('default'); // install the default dependency group
+                }
+                return $this->_valid = true;
+            }
+        }
+        $param = $saveparam;
+        return $this->_valid = false;
+    }
+
+    function _fromUrl($param, $saveparam = '')
+    {
+        if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) {
+            $options = $this->_downloader->getOptions();
+            $this->_type = 'url';
+            $callback = $this->_downloader->ui ?
+                array(&$this->_downloader, '_downloadCallback') : null;
+            $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN);
+            if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) {
+                $this->_downloader->popErrorHandling();
+                return $dir;
+            }
+
+            $this->_downloader->log(3, 'Downloading "' . $param . '"');
+            $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui,
+                $dir, $callback, null, false, $this->getChannel());
+            $this->_downloader->popErrorHandling();
+            if (PEAR::isError($file)) {
+                if (!empty($saveparam)) {
+                    $saveparam = ", cannot download \"$saveparam\"";
+                }
+                $err = PEAR::raiseError('Could not download from "' . $param .
+                    '"' . $saveparam . ' (' . $file->getMessage() . ')');
+                    return $err;
+            }
+
+            if ($this->_rawpackagefile) {
+                require_once 'Archive/Tar.php';
+                $tar = &new Archive_Tar($file);
+                $packagexml = $tar->extractInString('package2.xml');
+                if (!$packagexml) {
+                    $packagexml = $tar->extractInString('package.xml');
+                }
+
+                if (str_replace(array("\n", "\r"), array('',''), $packagexml) !=
+                      str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) {
+                    if ($this->getChannel() != 'pear.php.net') {
+                        return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' .
+                            'not match value returned from xml-rpc');
+                    }
+
+                    // be more lax for the existing PEAR packages that have not-ok
+                    // characters in their package.xml
+                    $this->_downloader->log(0, 'CRITICAL WARNING: The "' .
+                        $this->getPackage() . '" package has invalid characters in its ' .
+                        'package.xml.  The next version of PEAR may not be able to install ' .
+                        'this package for security reasons.  Please open a bug report at ' .
+                        'http://pear.php.net/package/' . $this->getPackage() . '/bugs');
+                }
+            }
+
+            // whew, download worked!
+            $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug);
+
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING);
+            PEAR::popErrorHandling();
+            if (PEAR::isError($pf)) {
+                if (is_array($pf->getUserInfo())) {
+                    foreach ($pf->getUserInfo() as $err) {
+                        if (is_array($err)) {
+                            $err = $err['message'];
+                        }
+
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(0, "Validation Error: $err");
+                        }
+                    }
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(0, $pf->getMessage());
+                }
+
+                ///FIXME need to pass back some error code that we can use to match with to cancel all further operations
+                /// At least stop all deps of this package from being installed
+                $out = $saveparam ? $saveparam : $param;
+                $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive');
+                $this->_valid = false;
+                return $err;
+            }
+
+            $this->_packagefile = &$pf;
+            $this->setGroup('default'); // install the default dependency group
+            return $this->_valid = true;
+        }
+
+        return $this->_valid = false;
+    }
+
+    /**
+     *
+     * @param string|array pass in an array of format
+     *                     array(
+     *                      'package' => 'pname',
+     *                     ['channel' => 'channame',]
+     *                     ['version' => 'version',]
+     *                     ['state' => 'state',])
+     *                     or a string of format [channame/]pname[-version|-state]
+     */
+    function _fromString($param)
+    {
+        $options = $this->_downloader->getOptions();
+        $channel = $this->_config->get('default_channel');
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $pname = $this->_registry->parsePackageName($param, $channel);
+        PEAR::popErrorHandling();
+        if (PEAR::isError($pname)) {
+            if ($pname->getCode() == 'invalid') {
+                $this->_valid = false;
+                return false;
+            }
+
+            if ($pname->getCode() == 'channel') {
+                $parsed = $pname->getUserInfo();
+                if ($this->_downloader->discover($parsed['channel'])) {
+                    if ($this->_config->get('auto_discover')) {
+                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                        $pname = $this->_registry->parsePackageName($param, $channel);
+                        PEAR::popErrorHandling();
+                    } else {
+                        if (!isset($options['soft'])) {
+                            $this->_downloader->log(0, 'Channel "' . $parsed['channel'] .
+                                '" is not initialized, use ' .
+                                '"pear channel-discover ' . $parsed['channel'] . '" to initialize' .
+                                'or pear config-set auto_discover 1');
+                        }
+                    }
+                }
+
+                if (PEAR::isError($pname)) {
+                    if (!isset($options['soft'])) {
+                        $this->_downloader->log(0, $pname->getMessage());
+                    }
+
+                    if (is_array($param)) {
+                        $param = $this->_registry->parsedPackageNameToString($param);
+                    }
+
+                    $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
+                    $this->_valid = false;
+                    return $err;
+                }
+            } else {
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(0, $pname->getMessage());
+                }
+
+                $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
+                $this->_valid = false;
+                return $err;
+            }
+        }
+
+        if (!isset($this->_type)) {
+            $this->_type = 'rest';
+        }
+
+        $this->_parsedname    = $pname;
+        $this->_explicitState = isset($pname['state']) ? $pname['state'] : false;
+        $this->_explicitGroup = isset($pname['group']) ? true : false;
+
+        $info = $this->_downloader->_getPackageDownloadUrl($pname);
+        if (PEAR::isError($info)) {
+            if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') {
+                // try pecl
+                $pname['channel'] = 'pecl.php.net';
+                if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) {
+                    if (!PEAR::isError($test)) {
+                        $info = PEAR::raiseError($info->getMessage() . ' - package ' .
+                            $this->_registry->parsedPackageNameToString($pname, true) .
+                            ' can be installed with "pecl install ' . $pname['package'] .
+                            '"');
+                    } else {
+                        $pname['channel'] = 'pear.php.net';
+                    }
+                } else {
+                    $pname['channel'] = 'pear.php.net';
+                }
+            }
+
+            return $info;
+        }
+
+        $this->_rawpackagefile = $info['raw'];
+        $ret = $this->_analyzeDownloadURL($info, $param, $pname);
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        if ($ret) {
+            $this->_downloadURL = $ret;
+            return $this->_valid = (bool) $ret;
+        }
+    }
+
+    /**
+     * @param array output of package.getDownloadURL
+     * @param string|array|object information for detecting packages to be downloaded, and
+     *                            for errors
+     * @param array name information of the package
+     * @param array|null packages to be downloaded
+     * @param bool is this an optional dependency?
+     * @param bool is this any kind of dependency?
+     * @access private
+     */
+    function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false,
+                                 $isdependency = false)
+    {
+        if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) {
+            return false;
+        }
+
+        if ($info === false) {
+            $saveparam = !is_string($param) ? ", cannot download \"$param\"" : '';
+
+            // no releases exist
+            return PEAR::raiseError('No releases for package "' .
+                $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam);
+        }
+
+        if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) {
+            $err = false;
+            if ($pname['channel'] == 'pecl.php.net') {
+                if ($info['info']->getChannel() != 'pear.php.net') {
+                    $err = true;
+                }
+            } elseif ($info['info']->getChannel() == 'pecl.php.net') {
+                if ($pname['channel'] != 'pear.php.net') {
+                    $err = true;
+                }
+            } else {
+                $err = true;
+            }
+
+            if ($err) {
+                return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] .
+                    '" retrieved another channel\'s name for download! ("' .
+                    $info['info']->getChannel() . '")');
+            }
+        }
+
+        $preferred_state = $this->_config->get('preferred_state');
+        if (!isset($info['url'])) {
+            $package_version = $this->_registry->packageInfo($info['info']->getPackage(),
+            'version', $info['info']->getChannel());
+            if ($this->isInstalled($info)) {
+                if ($isdependency && version_compare($info['version'], $package_version, '<=')) {
+                    // ignore bogus errors of "failed to download dependency"
+                    // if it is already installed and the one that would be
+                    // downloaded is older or the same version (Bug #7219)
+                    return false;
+                }
+            }
+
+            if ($info['version'] === $package_version) {
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+                        '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' .
+                        ' (' . $package_version . ') is the same as the locally installed one.');
+                }
+
+                return false;
+            }
+
+            if (version_compare($info['version'], $package_version, '<=')) {
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+                        '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' .
+                        ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').');
+                }
+
+                return false;
+            }
+
+            $instead =  ', will instead download version ' . $info['version'] .
+                        ', stability "' . $info['info']->getState() . '"';
+            // releases exist, but we failed to get any
+            if (isset($this->_downloader->_options['force'])) {
+                if (isset($pname['version'])) {
+                    $vs = ', version "' . $pname['version'] . '"';
+                } elseif (isset($pname['state'])) {
+                    $vs = ', stability "' . $pname['state'] . '"';
+                } elseif ($param == 'dependency') {
+                    if (!class_exists('PEAR_Common')) {
+                        require_once 'PEAR/Common.php';
+                    }
+
+                    if (!in_array($info['info']->getState(),
+                          PEAR_Common::betterStates($preferred_state, true))) {
+                        if ($optional) {
+                            // don't spit out confusing error message
+                            return $this->_downloader->_getPackageDownloadUrl(
+                                array('package' => $pname['package'],
+                                      'channel' => $pname['channel'],
+                                      'version' => $info['version']));
+                        }
+                        $vs = ' within preferred state "' . $preferred_state .
+                            '"';
+                    } else {
+                        if (!class_exists('PEAR_Dependency2')) {
+                            require_once 'PEAR/Dependency2.php';
+                        }
+
+                        if ($optional) {
+                            // don't spit out confusing error message
+                            return $this->_downloader->_getPackageDownloadUrl(
+                                array('package' => $pname['package'],
+                                      'channel' => $pname['channel'],
+                                      'version' => $info['version']));
+                        }
+                        $vs = PEAR_Dependency2::_getExtraString($pname);
+                        $instead = '';
+                    }
+                } else {
+                    $vs = ' within preferred state "' . $preferred_state . '"';
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+                        '/' . $pname['package'] . $vs . $instead);
+                }
+
+                // download the latest release
+                return $this->_downloader->_getPackageDownloadUrl(
+                    array('package' => $pname['package'],
+                          'channel' => $pname['channel'],
+                          'version' => $info['version']));
+            } else {
+                if (isset($info['php']) && $info['php']) {
+                    $err = PEAR::raiseError('Failed to download ' .
+                        $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'],
+                                  'package' => $pname['package']),
+                                true) .
+                        ', latest release is version ' . $info['php']['v'] .
+                        ', but it requires PHP version "' .
+                        $info['php']['m'] . '", use "' .
+                        $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'], 'package' => $pname['package'],
+                            'version' => $info['php']['v'])) . '" to install',
+                            PEAR_DOWNLOADER_PACKAGE_PHPVERSION);
+                    return $err;
+                }
+
+                // construct helpful error message
+                if (isset($pname['version'])) {
+                    $vs = ', version "' . $pname['version'] . '"';
+                } elseif (isset($pname['state'])) {
+                    $vs = ', stability "' . $pname['state'] . '"';
+                } elseif ($param == 'dependency') {
+                    if (!class_exists('PEAR_Common')) {
+                        require_once 'PEAR/Common.php';
+                    }
+
+                    if (!in_array($info['info']->getState(),
+                          PEAR_Common::betterStates($preferred_state, true))) {
+                        if ($optional) {
+                            // don't spit out confusing error message, and don't die on
+                            // optional dep failure!
+                            return $this->_downloader->_getPackageDownloadUrl(
+                                array('package' => $pname['package'],
+                                      'channel' => $pname['channel'],
+                                      'version' => $info['version']));
+                        }
+                        $vs = ' within preferred state "' . $preferred_state . '"';
+                    } else {
+                        if (!class_exists('PEAR_Dependency2')) {
+                            require_once 'PEAR/Dependency2.php';
+                        }
+
+                        if ($optional) {
+                            // don't spit out confusing error message, and don't die on
+                            // optional dep failure!
+                            return $this->_downloader->_getPackageDownloadUrl(
+                                array('package' => $pname['package'],
+                                      'channel' => $pname['channel'],
+                                      'version' => $info['version']));
+                        }
+                        $vs = PEAR_Dependency2::_getExtraString($pname);
+                    }
+                } else {
+                    $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"';
+                }
+
+                $options = $this->_downloader->getOptions();
+                // this is only set by the "download-all" command
+                if (isset($options['ignorepreferred_state'])) {
+                    $err = PEAR::raiseError(
+                        'Failed to download ' . $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'], 'package' => $pname['package']),
+                                true)
+                         . $vs .
+                        ', latest release is version ' . $info['version'] .
+                        ', stability "' . $info['info']->getState() . '", use "' .
+                        $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'], 'package' => $pname['package'],
+                            'version' => $info['version'])) . '" to install',
+                            PEAR_DOWNLOADER_PACKAGE_STATE);
+                    return $err;
+                }
+
+                // Checks if the user has a package installed already and checks the release against
+                // the state against the installed package, this allows upgrades for packages
+                // with lower stability than the preferred_state
+                $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']);
+                if (!$this->isInstalled($info)
+                    || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true))
+                ) {
+                    $err = PEAR::raiseError(
+                        'Failed to download ' . $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'], 'package' => $pname['package']),
+                                true)
+                         . $vs .
+                        ', latest release is version ' . $info['version'] .
+                        ', stability "' . $info['info']->getState() . '", use "' .
+                        $this->_registry->parsedPackageNameToString(
+                            array('channel' => $pname['channel'], 'package' => $pname['package'],
+                            'version' => $info['version'])) . '" to install');
+                    return $err;
+                }
+            }
+        }
+
+        if (isset($info['deprecated']) && $info['deprecated']) {
+            $this->_downloader->log(0,
+                'WARNING: "' .
+                    $this->_registry->parsedPackageNameToString(
+                            array('channel' => $info['info']->getChannel(),
+                                  'package' => $info['info']->getPackage()), true) .
+                '" is deprecated in favor of "' .
+                    $this->_registry->parsedPackageNameToString($info['deprecated'], true) .
+                '"');
+        }
+
+        return $info;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/ErrorStack.php b/WEB-INF/lib/pear/PEAR/ErrorStack.php
new file mode 100644 (file)
index 0000000..0303f52
--- /dev/null
@@ -0,0 +1,985 @@
+<?php
+/**
+ * Error Stack Implementation
+ * 
+ * This is an incredibly simple implementation of a very complex error handling
+ * facility.  It contains the ability
+ * to track multiple errors from multiple packages simultaneously.  In addition,
+ * it can track errors of many levels, save data along with the error, context
+ * information such as the exact file, line number, class and function that
+ * generated the error, and if necessary, it can raise a traditional PEAR_Error.
+ * It has built-in support for PEAR::Log, to log errors as they occur
+ * 
+ * Since version 0.2alpha, it is also possible to selectively ignore errors,
+ * through the use of an error callback, see {@link pushCallback()}
+ * 
+ * Since version 0.3alpha, it is possible to specify the exception class
+ * returned from {@link push()}
+ *
+ * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class.  This can
+ * still be done quite handily in an error callback or by manipulating the returned array
+ * @category   Debugging
+ * @package    PEAR_ErrorStack
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2004-2008 Greg Beaver
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR_ErrorStack
+ */
+
+/**
+ * Singleton storage
+ * 
+ * Format:
+ * <pre>
+ * array(
+ *  'package1' => PEAR_ErrorStack object,
+ *  'package2' => PEAR_ErrorStack object,
+ *  ...
+ * )
+ * </pre>
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
+
+/**
+ * Global error callback (default)
+ * 
+ * This is only used if set to non-false.  * is the default callback for
+ * all packages, whereas specific packages may set a default callback
+ * for all instances, regardless of whether they are a singleton or not.
+ *
+ * To exclude non-singletons, only set the local callback for the singleton
+ * @see PEAR_ErrorStack::setDefaultCallback()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
+    '*' => false,
+);
+
+/**
+ * Global Log object (default)
+ * 
+ * This is only used if set to non-false.  Use to set a default log object for
+ * all stacks, regardless of instantiation order or location
+ * @see PEAR_ErrorStack::setDefaultLogger()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
+
+/**
+ * Global Overriding Callback
+ * 
+ * This callback will override any error callbacks that specific loggers have set.
+ * Use with EXTREME caution
+ * @see PEAR_ErrorStack::staticPushCallback()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
+
+/**#@+
+ * One of four possible return values from the error Callback
+ * @see PEAR_ErrorStack::_errorCallback()
+ */
+/**
+ * If this is returned, then the error will be both pushed onto the stack
+ * and logged.
+ */
+define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
+/**
+ * If this is returned, then the error will only be pushed onto the stack,
+ * and not logged.
+ */
+define('PEAR_ERRORSTACK_PUSH', 2);
+/**
+ * If this is returned, then the error will only be logged, but not pushed
+ * onto the error stack.
+ */
+define('PEAR_ERRORSTACK_LOG', 3);
+/**
+ * If this is returned, then the error is completely ignored.
+ */
+define('PEAR_ERRORSTACK_IGNORE', 4);
+/**
+ * If this is returned, then the error is logged and die() is called.
+ */
+define('PEAR_ERRORSTACK_DIE', 5);
+/**#@-*/
+
+/**
+ * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
+ * the singleton method.
+ */
+define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
+
+/**
+ * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
+ * that has no __toString() method
+ */
+define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
+/**
+ * Error Stack Implementation
+ *
+ * Usage:
+ * <code>
+ * // global error stack
+ * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
+ * // local error stack
+ * $local_stack = new PEAR_ErrorStack('MyPackage');
+ * </code>
+ * @author     Greg Beaver <cellog@php.net>
+ * @version    1.9.4
+ * @package    PEAR_ErrorStack
+ * @category   Debugging
+ * @copyright  2004-2008 Greg Beaver
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR_ErrorStack
+ */
+class PEAR_ErrorStack {
+    /**
+     * Errors are stored in the order that they are pushed on the stack.
+     * @since 0.4alpha Errors are no longer organized by error level.
+     * This renders pop() nearly unusable, and levels could be more easily
+     * handled in a callback anyway
+     * @var array
+     * @access private
+     */
+    var $_errors = array();
+
+    /**
+     * Storage of errors by level.
+     *
+     * Allows easy retrieval and deletion of only errors from a particular level
+     * @since PEAR 1.4.0dev
+     * @var array
+     * @access private
+     */
+    var $_errorsByLevel = array();
+
+    /**
+     * Package name this error stack represents
+     * @var string
+     * @access protected
+     */
+    var $_package;
+    
+    /**
+     * Determines whether a PEAR_Error is thrown upon every error addition
+     * @var boolean
+     * @access private
+     */
+    var $_compat = false;
+    
+    /**
+     * If set to a valid callback, this will be used to generate the error
+     * message from the error code, otherwise the message passed in will be
+     * used
+     * @var false|string|array
+     * @access private
+     */
+    var $_msgCallback = false;
+    
+    /**
+     * If set to a valid callback, this will be used to generate the error
+     * context for an error.  For PHP-related errors, this will be a file
+     * and line number as retrieved from debug_backtrace(), but can be
+     * customized for other purposes.  The error might actually be in a separate
+     * configuration file, or in a database query.
+     * @var false|string|array
+     * @access protected
+     */
+    var $_contextCallback = false;
+    
+    /**
+     * If set to a valid callback, this will be called every time an error
+     * is pushed onto the stack.  The return value will be used to determine
+     * whether to allow an error to be pushed or logged.
+     * 
+     * The return value must be one an PEAR_ERRORSTACK_* constant
+     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+     * @var false|string|array
+     * @access protected
+     */
+    var $_errorCallback = array();
+    
+    /**
+     * PEAR::Log object for logging errors
+     * @var false|Log
+     * @access protected
+     */
+    var $_logger = false;
+    
+    /**
+     * Error messages - designed to be overridden
+     * @var array
+     * @abstract
+     */
+    var $_errorMsgs = array();
+    
+    /**
+     * Set up a new error stack
+     * 
+     * @param string   $package name of the package this error stack represents
+     * @param callback $msgCallback callback used for error message generation
+     * @param callback $contextCallback callback used for context generation,
+     *                 defaults to {@link getFileLine()}
+     * @param boolean  $throwPEAR_Error
+     */
+    function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
+                         $throwPEAR_Error = false)
+    {
+        $this->_package = $package;
+        $this->setMessageCallback($msgCallback);
+        $this->setContextCallback($contextCallback);
+        $this->_compat = $throwPEAR_Error;
+    }
+    
+    /**
+     * Return a single error stack for this package.
+     * 
+     * Note that all parameters are ignored if the stack for package $package
+     * has already been instantiated
+     * @param string   $package name of the package this error stack represents
+     * @param callback $msgCallback callback used for error message generation
+     * @param callback $contextCallback callback used for context generation,
+     *                 defaults to {@link getFileLine()}
+     * @param boolean  $throwPEAR_Error
+     * @param string   $stackClass class to instantiate
+     * @static
+     * @return PEAR_ErrorStack
+     */
+    function &singleton($package, $msgCallback = false, $contextCallback = false,
+                         $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
+    {
+        if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
+        }
+        if (!class_exists($stackClass)) {
+            if (function_exists('debug_backtrace')) {
+                $trace = debug_backtrace();
+            }
+            PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
+                'exception', array('stackclass' => $stackClass),
+                'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
+                false, $trace);
+        }
+        $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
+            new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
+
+        return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
+    }
+
+    /**
+     * Internal error handler for PEAR_ErrorStack class
+     * 
+     * Dies if the error is an exception (and would have died anyway)
+     * @access private
+     */
+    function _handleError($err)
+    {
+        if ($err['level'] == 'exception') {
+            $message = $err['message'];
+            if (isset($_SERVER['REQUEST_URI'])) {
+                echo '<br />';
+            } else {
+                echo "\n";
+            }
+            var_dump($err['context']);
+            die($message);
+        }
+    }
+    
+    /**
+     * Set up a PEAR::Log object for all error stacks that don't have one
+     * @param Log $log 
+     * @static
+     */
+    function setDefaultLogger(&$log)
+    {
+        if (is_object($log) && method_exists($log, 'log') ) {
+            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
+        } elseif (is_callable($log)) {
+            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
+       }
+    }
+    
+    /**
+     * Set up a PEAR::Log object for this error stack
+     * @param Log $log 
+     */
+    function setLogger(&$log)
+    {
+        if (is_object($log) && method_exists($log, 'log') ) {
+            $this->_logger = &$log;
+        } elseif (is_callable($log)) {
+            $this->_logger = &$log;
+        }
+    }
+    
+    /**
+     * Set an error code => error message mapping callback
+     * 
+     * This method sets the callback that can be used to generate error
+     * messages for any instance
+     * @param array|string Callback function/method
+     */
+    function setMessageCallback($msgCallback)
+    {
+        if (!$msgCallback) {
+            $this->_msgCallback = array(&$this, 'getErrorMessage');
+        } else {
+            if (is_callable($msgCallback)) {
+                $this->_msgCallback = $msgCallback;
+            }
+        }
+    }
+    
+    /**
+     * Get an error code => error message mapping callback
+     * 
+     * This method returns the current callback that can be used to generate error
+     * messages
+     * @return array|string|false Callback function/method or false if none
+     */
+    function getMessageCallback()
+    {
+        return $this->_msgCallback;
+    }
+    
+    /**
+     * Sets a default callback to be used by all error stacks
+     * 
+     * This method sets the callback that can be used to generate error
+     * messages for a singleton
+     * @param array|string Callback function/method
+     * @param string Package name, or false for all packages
+     * @static
+     */
+    function setDefaultCallback($callback = false, $package = false)
+    {
+        if (!is_callable($callback)) {
+            $callback = false;
+        }
+        $package = $package ? $package : '*';
+        $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
+    }
+    
+    /**
+     * Set a callback that generates context information (location of error) for an error stack
+     * 
+     * This method sets the callback that can be used to generate context
+     * information for an error.  Passing in NULL will disable context generation
+     * and remove the expensive call to debug_backtrace()
+     * @param array|string|null Callback function/method
+     */
+    function setContextCallback($contextCallback)
+    {
+        if ($contextCallback === null) {
+            return $this->_contextCallback = false;
+        }
+        if (!$contextCallback) {
+            $this->_contextCallback = array(&$this, 'getFileLine');
+        } else {
+            if (is_callable($contextCallback)) {
+                $this->_contextCallback = $contextCallback;
+            }
+        }
+    }
+    
+    /**
+     * Set an error Callback
+     * If set to a valid callback, this will be called every time an error
+     * is pushed onto the stack.  The return value will be used to determine
+     * whether to allow an error to be pushed or logged.
+     * 
+     * The return value must be one of the ERRORSTACK_* constants.
+     * 
+     * This functionality can be used to emulate PEAR's pushErrorHandling, and
+     * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
+     * the error stack or logging
+     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+     * @see popCallback()
+     * @param string|array $cb
+     */
+    function pushCallback($cb)
+    {
+        array_push($this->_errorCallback, $cb);
+    }
+    
+    /**
+     * Remove a callback from the error callback stack
+     * @see pushCallback()
+     * @return array|string|false
+     */
+    function popCallback()
+    {
+        if (!count($this->_errorCallback)) {
+            return false;
+        }
+        return array_pop($this->_errorCallback);
+    }
+    
+    /**
+     * Set a temporary overriding error callback for every package error stack
+     *
+     * Use this to temporarily disable all existing callbacks (can be used
+     * to emulate the @ operator, for instance)
+     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+     * @see staticPopCallback(), pushCallback()
+     * @param string|array $cb
+     * @static
+     */
+    function staticPushCallback($cb)
+    {
+        array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
+    }
+    
+    /**
+     * Remove a temporary overriding error callback
+     * @see staticPushCallback()
+     * @return array|string|false
+     * @static
+     */
+    function staticPopCallback()
+    {
+        $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
+        if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
+            $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
+        }
+        return $ret;
+    }
+    
+    /**
+     * Add an error to the stack
+     * 
+     * If the message generator exists, it is called with 2 parameters.
+     *  - the current Error Stack object
+     *  - an array that is in the same format as an error.  Available indices
+     *    are 'code', 'package', 'time', 'params', 'level', and 'context'
+     * 
+     * Next, if the error should contain context information, this is
+     * handled by the context grabbing method.
+     * Finally, the error is pushed onto the proper error stack
+     * @param int    $code      Package-specific error code
+     * @param string $level     Error level.  This is NOT spell-checked
+     * @param array  $params    associative array of error parameters
+     * @param string $msg       Error message, or a portion of it if the message
+     *                          is to be generated
+     * @param array  $repackage If this error re-packages an error pushed by
+     *                          another package, place the array returned from
+     *                          {@link pop()} in this parameter
+     * @param array  $backtrace Protected parameter: use this to pass in the
+     *                          {@link debug_backtrace()} that should be used
+     *                          to find error context
+     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
+     * thrown.  If a PEAR_Error is returned, the userinfo
+     * property is set to the following array:
+     * 
+     * <code>
+     * array(
+     *    'code' => $code,
+     *    'params' => $params,
+     *    'package' => $this->_package,
+     *    'level' => $level,
+     *    'time' => time(),
+     *    'context' => $context,
+     *    'message' => $msg,
+     * //['repackage' => $err] repackaged error array/Exception class
+     * );
+     * </code>
+     * 
+     * Normally, the previous array is returned.
+     */
+    function push($code, $level = 'error', $params = array(), $msg = false,
+                  $repackage = false, $backtrace = false)
+    {
+        $context = false;
+        // grab error context
+        if ($this->_contextCallback) {
+            if (!$backtrace) {
+                $backtrace = debug_backtrace();
+            }
+            $context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
+        }
+        
+        // save error
+        $time = explode(' ', microtime());
+        $time = $time[1] + $time[0];
+        $err = array(
+                'code' => $code,
+                'params' => $params,
+                'package' => $this->_package,
+                'level' => $level,
+                'time' => $time,
+                'context' => $context,
+                'message' => $msg,
+               );
+
+        if ($repackage) {
+            $err['repackage'] = $repackage;
+        }
+
+        // set up the error message, if necessary
+        if ($this->_msgCallback) {
+            $msg = call_user_func_array($this->_msgCallback,
+                                        array(&$this, $err));
+            $err['message'] = $msg;
+        }        
+        $push = $log = true;
+        $die = false;
+        // try the overriding callback first
+        $callback = $this->staticPopCallback();
+        if ($callback) {
+            $this->staticPushCallback($callback);
+        }
+        if (!is_callable($callback)) {
+            // try the local callback next
+            $callback = $this->popCallback();
+            if (is_callable($callback)) {
+                $this->pushCallback($callback);
+            } else {
+                // try the default callback
+                $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
+                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
+                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
+            }
+        }
+        if (is_callable($callback)) {
+            switch(call_user_func($callback, $err)){
+               case PEAR_ERRORSTACK_IGNORE: 
+                       return $err;
+                       break;
+               case PEAR_ERRORSTACK_PUSH: 
+                       $log = false;
+                       break;
+               case PEAR_ERRORSTACK_LOG: 
+                       $push = false;
+                       break;
+               case PEAR_ERRORSTACK_DIE: 
+                       $die = true;
+                       break;
+                // anything else returned has the same effect as pushandlog
+            }
+        }
+        if ($push) {
+            array_unshift($this->_errors, $err);
+            if (!isset($this->_errorsByLevel[$err['level']])) {
+                $this->_errorsByLevel[$err['level']] = array();
+            }
+            $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
+        }
+        if ($log) {
+            if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
+                $this->_log($err);
+            }
+        }
+        if ($die) {
+            die();
+        }
+        if ($this->_compat && $push) {
+            return $this->raiseError($msg, $code, null, null, $err);
+        }
+        return $err;
+    }
+    
+    /**
+     * Static version of {@link push()}
+     * 
+     * @param string $package   Package name this error belongs to
+     * @param int    $code      Package-specific error code
+     * @param string $level     Error level.  This is NOT spell-checked
+     * @param array  $params    associative array of error parameters
+     * @param string $msg       Error message, or a portion of it if the message
+     *                          is to be generated
+     * @param array  $repackage If this error re-packages an error pushed by
+     *                          another package, place the array returned from
+     *                          {@link pop()} in this parameter
+     * @param array  $backtrace Protected parameter: use this to pass in the
+     *                          {@link debug_backtrace()} that should be used
+     *                          to find error context
+     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
+     *                          thrown.  see docs for {@link push()}
+     * @static
+     */
+    function staticPush($package, $code, $level = 'error', $params = array(),
+                        $msg = false, $repackage = false, $backtrace = false)
+    {
+        $s = &PEAR_ErrorStack::singleton($package);
+        if ($s->_contextCallback) {
+            if (!$backtrace) {
+                if (function_exists('debug_backtrace')) {
+                    $backtrace = debug_backtrace();
+                }
+            }
+        }
+        return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
+    }
+    
+    /**
+     * Log an error using PEAR::Log
+     * @param array $err Error array
+     * @param array $levels Error level => Log constant map
+     * @access protected
+     */
+    function _log($err)
+    {
+        if ($this->_logger) {
+            $logger = &$this->_logger;
+        } else {
+            $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
+        }
+        if (is_a($logger, 'Log')) {
+            $levels = array(
+                'exception' => PEAR_LOG_CRIT,
+                'alert' => PEAR_LOG_ALERT,
+                'critical' => PEAR_LOG_CRIT,
+                'error' => PEAR_LOG_ERR,
+                'warning' => PEAR_LOG_WARNING,
+                'notice' => PEAR_LOG_NOTICE,
+                'info' => PEAR_LOG_INFO,
+                'debug' => PEAR_LOG_DEBUG);
+            if (isset($levels[$err['level']])) {
+                $level = $levels[$err['level']];
+            } else {
+                $level = PEAR_LOG_INFO;
+            }
+            $logger->log($err['message'], $level, $err);
+        } else { // support non-standard logs
+            call_user_func($logger, $err);
+        }
+    }
+
+    
+    /**
+     * Pop an error off of the error stack
+     * 
+     * @return false|array
+     * @since 0.4alpha it is no longer possible to specify a specific error
+     * level to return - the last error pushed will be returned, instead
+     */
+    function pop()
+    {
+        $err = @array_shift($this->_errors);
+        if (!is_null($err)) {
+            @array_pop($this->_errorsByLevel[$err['level']]);
+            if (!count($this->_errorsByLevel[$err['level']])) {
+                unset($this->_errorsByLevel[$err['level']]);
+            }
+        }
+        return $err;
+    }
+
+    /**
+     * Pop an error off of the error stack, static method
+     *
+     * @param string package name
+     * @return boolean
+     * @since PEAR1.5.0a1
+     */
+    function staticPop($package)
+    {
+        if ($package) {
+            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+                return false;
+            }
+            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
+        }
+    }
+
+    /**
+     * Determine whether there are any errors on the stack
+     * @param string|array Level name.  Use to determine if any errors
+     * of level (string), or levels (array) have been pushed
+     * @return boolean
+     */
+    function hasErrors($level = false)
+    {
+        if ($level) {
+            return isset($this->_errorsByLevel[$level]);
+        }
+        return count($this->_errors);
+    }
+    
+    /**
+     * Retrieve all errors since last purge
+     * 
+     * @param boolean set in order to empty the error stack
+     * @param string level name, to return only errors of a particular severity
+     * @return array
+     */
+    function getErrors($purge = false, $level = false)
+    {
+        if (!$purge) {
+            if ($level) {
+                if (!isset($this->_errorsByLevel[$level])) {
+                    return array();
+                } else {
+                    return $this->_errorsByLevel[$level];
+                }
+            } else {
+                return $this->_errors;
+            }
+        }
+        if ($level) {
+            $ret = $this->_errorsByLevel[$level];
+            foreach ($this->_errorsByLevel[$level] as $i => $unused) {
+                // entries are references to the $_errors array
+                $this->_errorsByLevel[$level][$i] = false;
+            }
+            // array_filter removes all entries === false
+            $this->_errors = array_filter($this->_errors);
+            unset($this->_errorsByLevel[$level]);
+            return $ret;
+        }
+        $ret = $this->_errors;
+        $this->_errors = array();
+        $this->_errorsByLevel = array();
+        return $ret;
+    }
+    
+    /**
+     * Determine whether there are any errors on a single error stack, or on any error stack
+     *
+     * The optional parameter can be used to test the existence of any errors without the need of
+     * singleton instantiation
+     * @param string|false Package name to check for errors
+     * @param string Level name to check for a particular severity
+     * @return boolean
+     * @static
+     */
+    function staticHasErrors($package = false, $level = false)
+    {
+        if ($package) {
+            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+                return false;
+            }
+            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
+        }
+        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
+            if ($obj->hasErrors($level)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Get a list of all errors since last purge, organized by package
+     * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
+     * @param boolean $purge Set to purge the error stack of existing errors
+     * @param string  $level Set to a level name in order to retrieve only errors of a particular level
+     * @param boolean $merge Set to return a flat array, not organized by package
+     * @param array   $sortfunc Function used to sort a merged array - default
+     *        sorts by time, and should be good for most cases
+     * @static
+     * @return array 
+     */
+    function staticGetErrors($purge = false, $level = false, $merge = false,
+                             $sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
+    {
+        $ret = array();
+        if (!is_callable($sortfunc)) {
+            $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
+        }
+        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
+            $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
+            if ($test) {
+                if ($merge) {
+                    $ret = array_merge($ret, $test);
+                } else {
+                    $ret[$package] = $test;
+                }
+            }
+        }
+        if ($merge) {
+            usort($ret, $sortfunc);
+        }
+        return $ret;
+    }
+    
+    /**
+     * Error sorting function, sorts by time
+     * @access private
+     */
+    function _sortErrors($a, $b)
+    {
+        if ($a['time'] == $b['time']) {
+            return 0;
+        }
+        if ($a['time'] < $b['time']) {
+            return 1;
+        }
+        return -1;
+    }
+
+    /**
+     * Standard file/line number/function/class context callback
+     *
+     * This function uses a backtrace generated from {@link debug_backtrace()}
+     * and so will not work at all in PHP < 4.3.0.  The frame should
+     * reference the frame that contains the source of the error.
+     * @return array|false either array('file' => file, 'line' => line,
+     *         'function' => function name, 'class' => class name) or
+     *         if this doesn't work, then false
+     * @param unused
+     * @param integer backtrace frame.
+     * @param array Results of debug_backtrace()
+     * @static
+     */
+    function getFileLine($code, $params, $backtrace = null)
+    {
+        if ($backtrace === null) {
+            return false;
+        }
+        $frame = 0;
+        $functionframe = 1;
+        if (!isset($backtrace[1])) {
+            $functionframe = 0;
+        } else {
+            while (isset($backtrace[$functionframe]['function']) &&
+                  $backtrace[$functionframe]['function'] == 'eval' &&
+                  isset($backtrace[$functionframe + 1])) {
+                $functionframe++;
+            }
+        }
+        if (isset($backtrace[$frame])) {
+            if (!isset($backtrace[$frame]['file'])) {
+                $frame++;
+            }
+            $funcbacktrace = $backtrace[$functionframe];
+            $filebacktrace = $backtrace[$frame];
+            $ret = array('file' => $filebacktrace['file'],
+                         'line' => $filebacktrace['line']);
+            // rearrange for eval'd code or create function errors
+            if (strpos($filebacktrace['file'], '(') && 
+                 preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
+                  $matches)) {
+                $ret['file'] = $matches[1];
+                $ret['line'] = $matches[2] + 0;
+            }
+            if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
+                if ($funcbacktrace['function'] != 'eval') {
+                    if ($funcbacktrace['function'] == '__lambda_func') {
+                        $ret['function'] = 'create_function() code';
+                    } else {
+                        $ret['function'] = $funcbacktrace['function'];
+                    }
+                }
+            }
+            if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
+                $ret['class'] = $funcbacktrace['class'];
+            }
+            return $ret;
+        }
+        return false;
+    }
+    
+    /**
+     * Standard error message generation callback
+     * 
+     * This method may also be called by a custom error message generator
+     * to fill in template values from the params array, simply
+     * set the third parameter to the error message template string to use
+     * 
+     * The special variable %__msg% is reserved: use it only to specify
+     * where a message passed in by the user should be placed in the template,
+     * like so:
+     * 
+     * Error message: %msg% - internal error
+     * 
+     * If the message passed like so:
+     * 
+     * <code>
+     * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
+     * </code>
+     * 
+     * The returned error message will be "Error message: server error 500 -
+     * internal error"
+     * @param PEAR_ErrorStack
+     * @param array
+     * @param string|false Pre-generated error message template
+     * @static
+     * @return string
+     */
+    function getErrorMessage(&$stack, $err, $template = false)
+    {
+        if ($template) {
+            $mainmsg = $template;
+        } else {
+            $mainmsg = $stack->getErrorMessageTemplate($err['code']);
+        }
+        $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
+        if (is_array($err['params']) && count($err['params'])) {
+            foreach ($err['params'] as $name => $val) {
+                if (is_array($val)) {
+                    // @ is needed in case $val is a multi-dimensional array
+                    $val = @implode(', ', $val);
+                }
+                if (is_object($val)) {
+                    if (method_exists($val, '__toString')) {
+                        $val = $val->__toString();
+                    } else {
+                        PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
+                            'warning', array('obj' => get_class($val)),
+                            'object %obj% passed into getErrorMessage, but has no __toString() method');
+                        $val = 'Object';
+                    }
+                }
+                $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
+            }
+        }
+        return $mainmsg;
+    }
+    
+    /**
+     * Standard Error Message Template generator from code
+     * @return string
+     */
+    function getErrorMessageTemplate($code)
+    {
+        if (!isset($this->_errorMsgs[$code])) {
+            return '%__msg%';
+        }
+        return $this->_errorMsgs[$code];
+    }
+    
+    /**
+     * Set the Error Message Template array
+     * 
+     * The array format must be:
+     * <pre>
+     * array(error code => 'message template',...)
+     * </pre>
+     * 
+     * Error message parameters passed into {@link push()} will be used as input
+     * for the error message.  If the template is 'message %foo% was %bar%', and the
+     * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
+     * be 'message one was six'
+     * @return string
+     */
+    function setErrorMessageTemplate($template)
+    {
+        $this->_errorMsgs = $template;
+    }
+    
+    
+    /**
+     * emulate PEAR::raiseError()
+     * 
+     * @return PEAR_Error
+     */
+    function raiseError()
+    {
+        require_once 'PEAR.php';
+        $args = func_get_args();
+        return call_user_func_array(array('PEAR', 'raiseError'), $args);
+    }
+}
+$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
+$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
+?>
diff --git a/WEB-INF/lib/pear/PEAR/Exception.php b/WEB-INF/lib/pear/PEAR/Exception.php
new file mode 100644 (file)
index 0000000..4a0e7b8
--- /dev/null
@@ -0,0 +1,389 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+/**
+ * PEAR_Exception
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Hans Lellelid <hans@velum.net>
+ * @author     Bertrand Mansion <bmansion@mamasam.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Exception.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.3.3
+ */
+
+
+/**
+ * Base PEAR_Exception Class
+ *
+ * 1) Features:
+ *
+ * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
+ * - Definable triggers, shot when exceptions occur
+ * - Pretty and informative error messages
+ * - Added more context info available (like class, method or cause)
+ * - cause can be a PEAR_Exception or an array of mixed
+ *   PEAR_Exceptions/PEAR_ErrorStack warnings
+ * - callbacks for specific exception classes and their children
+ *
+ * 2) Ideas:
+ *
+ * - Maybe a way to define a 'template' for the output
+ *
+ * 3) Inherited properties from PHP Exception Class:
+ *
+ * protected $message
+ * protected $code
+ * protected $line
+ * protected $file
+ * private   $trace
+ *
+ * 4) Inherited methods from PHP Exception Class:
+ *
+ * __clone
+ * __construct
+ * getMessage
+ * getCode
+ * getFile
+ * getLine
+ * getTraceSafe
+ * getTraceSafeAsString
+ * __toString
+ *
+ * 5) Usage example
+ *
+ * <code>
+ *  require_once 'PEAR/Exception.php';
+ *
+ *  class Test {
+ *     function foo() {
+ *         throw new PEAR_Exception('Error Message', ERROR_CODE);
+ *     }
+ *  }
+ *
+ *  function myLogger($pear_exception) {
+ *     echo $pear_exception->getMessage();
+ *  }
+ *  // each time a exception is thrown the 'myLogger' will be called
+ *  // (its use is completely optional)
+ *  PEAR_Exception::addObserver('myLogger');
+ *  $test = new Test;
+ *  try {
+ *     $test->foo();
+ *  } catch (PEAR_Exception $e) {
+ *     print $e;
+ *  }
+ * </code>
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Hans Lellelid <hans@velum.net>
+ * @author     Bertrand Mansion <bmansion@mamasam.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.3.3
+ *
+ */
+class PEAR_Exception extends Exception
+{
+    const OBSERVER_PRINT = -2;
+    const OBSERVER_TRIGGER = -4;
+    const OBSERVER_DIE = -8;
+    protected $cause;
+    private static $_observers = array();
+    private static $_uniqueid = 0;
+    private $_trace;
+
+    /**
+     * Supported signatures:
+     *  - PEAR_Exception(string $message);
+     *  - PEAR_Exception(string $message, int $code);
+     *  - PEAR_Exception(string $message, Exception $cause);
+     *  - PEAR_Exception(string $message, Exception $cause, int $code);
+     *  - PEAR_Exception(string $message, PEAR_Error $cause);
+     *  - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
+     *  - PEAR_Exception(string $message, array $causes);
+     *  - PEAR_Exception(string $message, array $causes, int $code);
+     * @param string exception message
+     * @param int|Exception|PEAR_Error|array|null exception cause
+     * @param int|null exception code or null
+     */
+    public function __construct($message, $p2 = null, $p3 = null)
+    {
+        if (is_int($p2)) {
+            $code = $p2;
+            $this->cause = null;
+        } elseif (is_object($p2) || is_array($p2)) {
+            // using is_object allows both Exception and PEAR_Error
+            if (is_object($p2) && !($p2 instanceof Exception)) {
+                if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
+                    throw new PEAR_Exception('exception cause must be Exception, ' .
+                        'array, or PEAR_Error');
+                }
+            }
+            $code = $p3;
+            if (is_array($p2) && isset($p2['message'])) {
+                // fix potential problem of passing in a single warning
+                $p2 = array($p2);
+            }
+            $this->cause = $p2;
+        } else {
+            $code = null;
+            $this->cause = null;
+        }
+        parent::__construct($message, $code);
+        $this->signal();
+    }
+
+    /**
+     * @param mixed $callback  - A valid php callback, see php func is_callable()
+     *                         - A PEAR_Exception::OBSERVER_* constant
+     *                         - An array(const PEAR_Exception::OBSERVER_*,
+     *                           mixed $options)
+     * @param string $label    The name of the observer. Use this if you want
+     *                         to remove it later with removeObserver()
+     */
+    public static function addObserver($callback, $label = 'default')
+    {
+        self::$_observers[$label] = $callback;
+    }
+
+    public static function removeObserver($label = 'default')
+    {
+        unset(self::$_observers[$label]);
+    }
+
+    /**
+     * @return int unique identifier for an observer
+     */
+    public static function getUniqueId()
+    {
+        return self::$_uniqueid++;
+    }
+
+    private function signal()
+    {
+        foreach (self::$_observers as $func) {
+            if (is_callable($func)) {
+                call_user_func($func, $this);
+                continue;
+            }
+            settype($func, 'array');
+            switch ($func[0]) {
+                case self::OBSERVER_PRINT :
+                    $f = (isset($func[1])) ? $func[1] : '%s';
+                    printf($f, $this->getMessage());
+                    break;
+                case self::OBSERVER_TRIGGER :
+                    $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
+                    trigger_error($this->getMessage(), $f);
+                    break;
+                case self::OBSERVER_DIE :
+                    $f = (isset($func[1])) ? $func[1] : '%s';
+                    die(printf($f, $this->getMessage()));
+                    break;
+                default:
+                    trigger_error('invalid observer type', E_USER_WARNING);
+            }
+        }
+    }
+
+    /**
+     * Return specific error information that can be used for more detailed
+     * error messages or translation.
+     *
+     * This method may be overridden in child exception classes in order
+     * to add functionality not present in PEAR_Exception and is a placeholder
+     * to define API
+     *
+     * The returned array must be an associative array of parameter => value like so:
+     * <pre>
+     * array('name' => $name, 'context' => array(...))
+     * </pre>
+     * @return array
+     */
+    public function getErrorData()
+    {
+        return array();
+    }
+
+    /**
+     * Returns the exception that caused this exception to be thrown
+     * @access public
+     * @return Exception|array The context of the exception
+     */
+    public function getCause()
+    {
+        return $this->cause;
+    }
+
+    /**
+     * Function must be public to call on caused exceptions
+     * @param array
+     */
+    public function getCauseMessage(&$causes)
+    {
+        $trace = $this->getTraceSafe();
+        $cause = array('class'   => get_class($this),
+                       'message' => $this->message,
+                       'file' => 'unknown',
+                       'line' => 'unknown');
+        if (isset($trace[0])) {
+            if (isset($trace[0]['file'])) {
+                $cause['file'] = $trace[0]['file'];
+                $cause['line'] = $trace[0]['line'];
+            }
+        }
+        $causes[] = $cause;
+        if ($this->cause instanceof PEAR_Exception) {
+            $this->cause->getCauseMessage($causes);
+        } elseif ($this->cause instanceof Exception) {
+            $causes[] = array('class'   => get_class($this->cause),
+                              'message' => $this->cause->getMessage(),
+                              'file' => $this->cause->getFile(),
+                              'line' => $this->cause->getLine());
+        } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
+            $causes[] = array('class' => get_class($this->cause),
+                              'message' => $this->cause->getMessage(),
+                              'file' => 'unknown',
+                              'line' => 'unknown');
+        } elseif (is_array($this->cause)) {
+            foreach ($this->cause as $cause) {
+                if ($cause instanceof PEAR_Exception) {
+                    $cause->getCauseMessage($causes);
+                } elseif ($cause instanceof Exception) {
+                    $causes[] = array('class'   => get_class($cause),
+                                   'message' => $cause->getMessage(),
+                                   'file' => $cause->getFile(),
+                                   'line' => $cause->getLine());
+                } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
+                    $causes[] = array('class' => get_class($cause),
+                                      'message' => $cause->getMessage(),
+                                      'file' => 'unknown',
+                                      'line' => 'unknown');
+                } elseif (is_array($cause) && isset($cause['message'])) {
+                    // PEAR_ErrorStack warning
+                    $causes[] = array(
+                        'class' => $cause['package'],
+                        'message' => $cause['message'],
+                        'file' => isset($cause['context']['file']) ?
+                                            $cause['context']['file'] :
+                                            'unknown',
+                        'line' => isset($cause['context']['line']) ?
+                                            $cause['context']['line'] :
+                                            'unknown',
+                    );
+                }
+            }
+        }
+    }
+
+    public function getTraceSafe()
+    {
+        if (!isset($this->_trace)) {
+            $this->_trace = $this->getTrace();
+            if (empty($this->_trace)) {
+                $backtrace = debug_backtrace();
+                $this->_trace = array($backtrace[count($backtrace)-1]);
+            }
+        }
+        return $this->_trace;
+    }
+
+    public function getErrorClass()
+    {
+        $trace = $this->getTraceSafe();
+        return $trace[0]['class'];
+    }
+
+    public function getErrorMethod()
+    {
+        $trace = $this->getTraceSafe();
+        return $trace[0]['function'];
+    }
+
+    public function __toString()
+    {
+        if (isset($_SERVER['REQUEST_URI'])) {
+            return $this->toHtml();
+        }
+        return $this->toText();
+    }
+
+    public function toHtml()
+    {
+        $trace = $this->getTraceSafe();
+        $causes = array();
+        $this->getCauseMessage($causes);
+        $html =  '<table style="border: 1px" cellspacing="0">' . "\n";
+        foreach ($causes as $i => $cause) {
+            $html .= '<tr><td colspan="3" style="background: #ff9999">'
+               . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
+               . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
+               . 'on line <b>' . $cause['line'] . '</b>'
+               . "</td></tr>\n";
+        }
+        $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
+               . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
+               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
+               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
+
+        foreach ($trace as $k => $v) {
+            $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
+                   . '<td>';
+            if (!empty($v['class'])) {
+                $html .= $v['class'] . $v['type'];
+            }
+            $html .= $v['function'];
+            $args = array();
+            if (!empty($v['args'])) {
+                foreach ($v['args'] as $arg) {
+                    if (is_null($arg)) $args[] = 'null';
+                    elseif (is_array($arg)) $args[] = 'Array';
+                    elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
+                    elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
+                    elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
+                    else {
+                        $arg = (string)$arg;
+                        $str = htmlspecialchars(substr($arg, 0, 16));
+                        if (strlen($arg) > 16) $str .= '&hellip;';
+                        $args[] = "'" . $str . "'";
+                    }
+                }
+            }
+            $html .= '(' . implode(', ',$args) . ')'
+                   . '</td>'
+                   . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
+                   . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
+                   . '</td></tr>' . "\n";
+        }
+        $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
+               . '<td>{main}</td>'
+               . '<td>&nbsp;</td></tr>' . "\n"
+               . '</table>';
+        return $html;
+    }
+
+    public function toText()
+    {
+        $causes = array();
+        $this->getCauseMessage($causes);
+        $causeMsg = '';
+        foreach ($causes as $i => $cause) {
+            $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
+                   . $cause['message'] . ' in ' . $cause['file']
+                   . ' on line ' . $cause['line'] . "\n";
+        }
+        return $causeMsg . $this->getTraceAsString();
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php b/WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php
new file mode 100644 (file)
index 0000000..be5dc3c
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+if ($skipmsg) {
+    $a = &new $ec($code, $mode, $options, $userinfo);
+} else {
+    $a = &new $ec($message, $code, $mode, $options, $userinfo);
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Frontend.php b/WEB-INF/lib/pear/PEAR/Frontend.php
new file mode 100644 (file)
index 0000000..531e541
--- /dev/null
@@ -0,0 +1,228 @@
+<?php
+/**
+ * PEAR_Frontend, the singleton-based frontend for user input/output
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Frontend.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Include error handling
+ */
+//require_once 'PEAR.php';
+
+/**
+ * Which user interface class is being used.
+ * @var string class name
+ */
+$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI';
+
+/**
+ * Instance of $_PEAR_Command_uiclass.
+ * @var object
+ */
+$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null;
+
+/**
+ * Singleton-based frontend for PEAR user input/output
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Frontend extends PEAR
+{
+    /**
+     * Retrieve the frontend object
+     * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk
+     * @static
+     */
+    function &singleton($type = null)
+    {
+        if ($type === null) {
+            if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) {
+                $a = false;
+                return $a;
+            }
+            return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+        }
+
+        $a = PEAR_Frontend::setFrontendClass($type);
+        return $a;
+    }
+
+    /**
+     * Set the frontend class that will be used by calls to {@link singleton()}
+     *
+     * Frontends are expected to conform to the PEAR naming standard of
+     * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php)
+     * @param string $uiclass full class name
+     * @return PEAR_Frontend
+     * @static
+     */
+    function &setFrontendClass($uiclass)
+    {
+        if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) &&
+              is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) {
+            return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+        }
+
+        if (!class_exists($uiclass)) {
+            $file = str_replace('_', '/', $uiclass) . '.php';
+            if (PEAR_Frontend::isIncludeable($file)) {
+                include_once $file;
+            }
+        }
+
+        if (class_exists($uiclass)) {
+            $obj = &new $uiclass;
+            // quick test to see if this class implements a few of the most
+            // important frontend methods
+            if (is_a($obj, 'PEAR_Frontend')) {
+                $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj;
+                $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass;
+                return $obj;
+            }
+
+            $err = PEAR::raiseError("not a frontend class: $uiclass");
+            return $err;
+        }
+
+        $err = PEAR::raiseError("no such class: $uiclass");
+        return $err;
+    }
+
+    /**
+     * Set the frontend class that will be used by calls to {@link singleton()}
+     *
+     * Frontends are expected to be a descendant of PEAR_Frontend
+     * @param PEAR_Frontend
+     * @return PEAR_Frontend
+     * @static
+     */
+    function &setFrontendObject($uiobject)
+    {
+        if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) &&
+              is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) {
+            return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+        }
+
+        if (!is_a($uiobject, 'PEAR_Frontend')) {
+            $err = PEAR::raiseError('not a valid frontend class: (' .
+                get_class($uiobject) . ')');
+            return $err;
+        }
+
+        $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject;
+        $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject);
+        return $uiobject;
+    }
+
+    /**
+     * @param string $path relative or absolute include path
+     * @return boolean
+     * @static
+     */
+    function isIncludeable($path)
+    {
+        if (file_exists($path) && is_readable($path)) {
+            return true;
+        }
+
+        $fp = @fopen($path, 'r', true);
+        if ($fp) {
+            fclose($fp);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param PEAR_Config
+     */
+    function setConfig(&$config)
+    {
+    }
+
+    /**
+     * This can be overridden to allow session-based temporary file management
+     *
+     * By default, all files are deleted at the end of a session.  The web installer
+     * needs to be able to sustain a list over many sessions in order to support
+     * user interaction with install scripts
+     */
+    function addTempFile($file)
+    {
+        $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
+    }
+
+    /**
+     * Log an action
+     *
+     * @param string $msg the message to log
+     * @param boolean $append_crlf
+     * @return boolean true
+     * @abstract
+     */
+    function log($msg, $append_crlf = true)
+    {
+    }
+
+    /**
+     * Run a post-installation script
+     *
+     * @param array $scripts array of post-install scripts
+     * @abstract
+     */
+    function runPostinstallScripts(&$scripts)
+    {
+    }
+
+    /**
+     * Display human-friendly output formatted depending on the
+     * $command parameter.
+     *
+     * This should be able to handle basic output data with no command
+     * @param mixed  $data    data structure containing the information to display
+     * @param string $command command from which this method was called
+     * @abstract
+     */
+    function outputData($data, $command = '_default')
+    {
+    }
+
+    /**
+     * Display a modal form dialog and return the given input
+     *
+     * A frontend that requires multiple requests to retrieve and process
+     * data must take these needs into account, and implement the request
+     * handling code.
+     * @param string $command  command from which this method was called
+     * @param array  $prompts  associative array. keys are the input field names
+     *                         and values are the description
+     * @param array  $types    array of input field types (text, password,
+     *                         etc.) keys have to be the same like in $prompts
+     * @param array  $defaults array of default values. again keys have
+     *                         to be the same like in $prompts.  Do not depend
+     *                         on a default value being set.
+     * @return array input sent by the user
+     * @abstract
+     */
+    function userDialog($command, $prompts, $types = array(), $defaults = array())
+    {
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Frontend/CLI.php b/WEB-INF/lib/pear/PEAR/Frontend/CLI.php
new file mode 100644 (file)
index 0000000..340b99b
--- /dev/null
@@ -0,0 +1,751 @@
+<?php
+/**
+ * PEAR_Frontend_CLI
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: CLI.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+/**
+ * base class
+ */
+require_once 'PEAR/Frontend.php';
+
+/**
+ * Command-line Frontend for the PEAR Installer
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Frontend_CLI extends PEAR_Frontend
+{
+    /**
+     * What type of user interface this frontend is for.
+     * @var string
+     * @access public
+     */
+    var $type = 'CLI';
+    var $lp = ''; // line prefix
+
+    var $params = array();
+    var $term = array(
+        'bold'   => '',
+        'normal' => '',
+    );
+
+    function PEAR_Frontend_CLI()
+    {
+        parent::PEAR();
+        $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1
+        if (function_exists('posix_isatty') && !posix_isatty(1)) {
+            // output is being redirected to a file or through a pipe
+        } elseif ($term) {
+            if (preg_match('/^(xterm|vt220|linux)/', $term)) {
+                $this->term['bold']   = sprintf("%c%c%c%c", 27, 91, 49, 109);
+                $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109);
+            } elseif (preg_match('/^vt100/', $term)) {
+                $this->term['bold']   = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0);
+                $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0);
+            }
+        } elseif (OS_WINDOWS) {
+            // XXX add ANSI codes here
+        }
+    }
+
+    /**
+     * @param object PEAR_Error object
+     */
+    function displayError($e)
+    {
+        return $this->_displayLine($e->getMessage());
+    }
+
+    /**
+     * @param object PEAR_Error object
+     */
+    function displayFatalError($eobj)
+    {
+        $this->displayError($eobj);
+        if (class_exists('PEAR_Config')) {
+            $config = &PEAR_Config::singleton();
+            if ($config->get('verbose') > 5) {
+                if (function_exists('debug_print_backtrace')) {
+                    debug_print_backtrace();
+                    exit(1);
+                }
+
+                $raised = false;
+                foreach (debug_backtrace() as $i => $frame) {
+                    if (!$raised) {
+                        if (isset($frame['class'])
+                            && strtolower($frame['class']) == 'pear'
+                            && strtolower($frame['function']) == 'raiseerror'
+                        ) {
+                            $raised = true;
+                        } else {
+                            continue;
+                        }
+                    }
+
+                    $frame['class']    = !isset($frame['class'])    ? '' : $frame['class'];
+                    $frame['type']     = !isset($frame['type'])     ? '' : $frame['type'];
+                    $frame['function'] = !isset($frame['function']) ? '' : $frame['function'];
+                    $frame['line']     = !isset($frame['line'])     ? '' : $frame['line'];
+                    $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]");
+                }
+            }
+        }
+
+        exit(1);
+    }
+
+    /**
+     * Instruct the runInstallScript method to skip a paramgroup that matches the
+     * id value passed in.
+     *
+     * This method is useful for dynamically configuring which sections of a post-install script
+     * will be run based on the user's setup, which is very useful for making flexible
+     * post-install scripts without losing the cross-Frontend ability to retrieve user input
+     * @param string
+     */
+    function skipParamgroup($id)
+    {
+        $this->_skipSections[$id] = true;
+    }
+
+    function runPostinstallScripts(&$scripts)
+    {
+        foreach ($scripts as $i => $script) {
+            $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj);
+        }
+    }
+
+    /**
+     * @param array $xml contents of postinstallscript tag
+     * @param object $script post-installation script
+     * @param string install|upgrade
+     */
+    function runInstallScript($xml, &$script)
+    {
+        $this->_skipSections = array();
+        if (!is_array($xml) || !isset($xml['paramgroup'])) {
+            $script->run(array(), '_default');
+            return;
+        }
+
+        $completedPhases = array();
+        if (!isset($xml['paramgroup'][0])) {
+            $xml['paramgroup'] = array($xml['paramgroup']);
+        }
+
+        foreach ($xml['paramgroup'] as $group) {
+            if (isset($this->_skipSections[$group['id']])) {
+                // the post-install script chose to skip this section dynamically
+                continue;
+            }
+
+            if (isset($group['name'])) {
+                $paramname = explode('::', $group['name']);
+                if ($lastgroup['id'] != $paramname[0]) {
+                    continue;
+                }
+
+                $group['name'] = $paramname[1];
+                if (!isset($answers)) {
+                    return;
+                }
+
+                if (isset($answers[$group['name']])) {
+                    switch ($group['conditiontype']) {
+                        case '=' :
+                            if ($answers[$group['name']] != $group['value']) {
+                                continue 2;
+                            }
+                        break;
+                        case '!=' :
+                            if ($answers[$group['name']] == $group['value']) {
+                                continue 2;
+                            }
+                        break;
+                        case 'preg_match' :
+                            if (!@preg_match('/' . $group['value'] . '/',
+                                  $answers[$group['name']])) {
+                                continue 2;
+                            }
+                        break;
+                        default :
+                        return;
+                    }
+                }
+            }
+
+            $lastgroup = $group;
+            if (isset($group['instructions'])) {
+                $this->_display($group['instructions']);
+            }
+
+            if (!isset($group['param'][0])) {
+                $group['param'] = array($group['param']);
+            }
+
+            if (isset($group['param'])) {
+                if (method_exists($script, 'postProcessPrompts')) {
+                    $prompts = $script->postProcessPrompts($group['param'], $group['id']);
+                    if (!is_array($prompts) || count($prompts) != count($group['param'])) {
+                        $this->outputData('postinstall', 'Error: post-install script did not ' .
+                            'return proper post-processed prompts');
+                        $prompts = $group['param'];
+                    } else {
+                        foreach ($prompts as $i => $var) {
+                            if (!is_array($var) || !isset($var['prompt']) ||
+                                  !isset($var['name']) ||
+                                  ($var['name'] != $group['param'][$i]['name']) ||
+                                  ($var['type'] != $group['param'][$i]['type'])
+                            ) {
+                                $this->outputData('postinstall', 'Error: post-install script ' .
+                                    'modified the variables or prompts, severe security risk. ' .
+                                    'Will instead use the defaults from the package.xml');
+                                $prompts = $group['param'];
+                            }
+                        }
+                    }
+
+                    $answers = $this->confirmDialog($prompts);
+                } else {
+                    $answers = $this->confirmDialog($group['param']);
+                }
+            }
+
+            if ((isset($answers) && $answers) || !isset($group['param'])) {
+                if (!isset($answers)) {
+                    $answers = array();
+                }
+
+                array_unshift($completedPhases, $group['id']);
+                if (!$script->run($answers, $group['id'])) {
+                    $script->run($completedPhases, '_undoOnError');
+                    return;
+                }
+            } else {
+                $script->run($completedPhases, '_undoOnError');
+                return;
+            }
+        }
+    }
+
+    /**
+     * Ask for user input, confirm the answers and continue until the user is satisfied
+     * @param array an array of arrays, format array('name' => 'paramname', 'prompt' =>
+     *              'text to display', 'type' => 'string'[, default => 'default value'])
+     * @return array
+     */
+    function confirmDialog($params)
+    {
+        $answers = $prompts = $types = array();
+        foreach ($params as $param) {
+            $prompts[$param['name']] = $param['prompt'];
+            $types[$param['name']]   = $param['type'];
+            $answers[$param['name']] = isset($param['default']) ? $param['default'] : '';
+        }
+
+        $tried = false;
+        do {
+            if ($tried) {
+                $i = 1;
+                foreach ($answers as $var => $value) {
+                    if (!strlen($value)) {
+                        echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n");
+                    }
+                    $i++;
+                }
+            }
+
+            $answers = $this->userDialog('', $prompts, $types, $answers);
+            $tried   = true;
+        } while (is_array($answers) && count(array_filter($answers)) != count($prompts));
+
+        return $answers;
+    }
+
+    function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20)
+    {
+        if (!is_array($prompts)) {
+            return array();
+        }
+
+        $testprompts = array_keys($prompts);
+        $result      = $defaults;
+
+        reset($prompts);
+        if (count($prompts) === 1) {
+            foreach ($prompts as $key => $prompt) {
+                $type    = $types[$key];
+                $default = @$defaults[$key];
+                print "$prompt ";
+                if ($default) {
+                    print "[$default] ";
+                }
+                print ": ";
+
+                $line         = fgets(STDIN, 2048);
+                $result[$key] =  ($default && trim($line) == '') ? $default : trim($line);
+            }
+
+            return $result;
+        }
+
+        $first_run = true;
+        while (true) {
+            $descLength = max(array_map('strlen', $prompts));
+            $descFormat = "%-{$descLength}s";
+            $last       = count($prompts);
+
+            $i = 0;
+            foreach ($prompts as $n => $var) {
+                $res = isset($result[$n]) ? $result[$n] : null;
+                printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res);
+            }
+            print "\n1-$last, 'all', 'abort', or Enter to continue: ";
+
+            $tmp = trim(fgets(STDIN, 1024));
+            if (empty($tmp)) {
+                break;
+            }
+
+            if ($tmp == 'abort') {
+                return false;
+            }
+
+            if (isset($testprompts[(int)$tmp - 1])) {
+                $var     = $testprompts[(int)$tmp - 1];
+                $desc    = $prompts[$var];
+                $current = @$result[$var];
+                print "$desc [$current] : ";
+                $tmp = trim(fgets(STDIN, 1024));
+                if ($tmp !== '') {
+                    $result[$var] = $tmp;
+                }
+            } elseif ($tmp == 'all') {
+                foreach ($prompts as $var => $desc) {
+                    $current = $result[$var];
+                    print "$desc [$current] : ";
+                    $tmp = trim(fgets(STDIN, 1024));
+                    if (trim($tmp) !== '') {
+                        $result[$var] = trim($tmp);
+                    }
+                }
+            }
+
+            $first_run = false;
+        }
+
+        return $result;
+    }
+
+    function userConfirm($prompt, $default = 'yes')
+    {
+        trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR);
+        static $positives = array('y', 'yes', 'on', '1');
+        static $negatives = array('n', 'no', 'off', '0');
+        print "$this->lp$prompt [$default] : ";
+        $fp = fopen("php://stdin", "r");
+        $line = fgets($fp, 2048);
+        fclose($fp);
+        $answer = strtolower(trim($line));
+        if (empty($answer)) {
+            $answer = $default;
+        }
+        if (in_array($answer, $positives)) {
+            return true;
+        }
+        if (in_array($answer, $negatives)) {
+            return false;
+        }
+        if (in_array($default, $positives)) {
+            return true;
+        }
+        return false;
+    }
+
+    function outputData($data, $command = '_default')
+    {
+        switch ($command) {
+            case 'channel-info':
+                foreach ($data as $type => $section) {
+                    if ($type == 'main') {
+                        $section['data'] = array_values($section['data']);
+                    }
+
+                    $this->outputData($section);
+                }
+                break;
+            case 'install':
+            case 'upgrade':
+            case 'upgrade-all':
+                if (is_array($data) && isset($data['release_warnings'])) {
+                    $this->_displayLine('');
+                    $this->_startTable(array(
+                        'border' => false,
+                        'caption' => 'Release Warnings'
+                    ));
+                    $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55)));
+                    $this->_endTable();
+                    $this->_displayLine('');
+                }
+
+                $this->_displayLine(is_array($data) ? $data['data'] : $data);
+                break;
+            case 'search':
+                $this->_startTable($data);
+                if (isset($data['headline']) && is_array($data['headline'])) {
+                    $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
+                }
+
+                $packages = array();
+                foreach($data['data'] as $category) {
+                    foreach($category as $name => $pkg) {
+                        $packages[$pkg[0]] = $pkg;
+                    }
+                }
+
+                $p = array_keys($packages);
+                natcasesort($p);
+                foreach ($p as $name) {
+                    $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55)));
+                }
+
+                $this->_endTable();
+                break;
+            case 'list-all':
+                if (!isset($data['data'])) {
+                      $this->_displayLine('No packages in channel');
+                      break;
+                }
+
+                $this->_startTable($data);
+                if (isset($data['headline']) && is_array($data['headline'])) {
+                    $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
+                }
+
+                $packages = array();
+                foreach($data['data'] as $category) {
+                    foreach($category as $name => $pkg) {
+                        $packages[$pkg[0]] = $pkg;
+                    }
+                }
+
+                $p = array_keys($packages);
+                natcasesort($p);
+                foreach ($p as $name) {
+                    $pkg = $packages[$name];
+                    unset($pkg[4], $pkg[5]);
+                    $this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
+                }
+
+                $this->_endTable();
+                break;
+            case 'config-show':
+                $data['border'] = false;
+                $opts = array(
+                    0 => array('wrap' => 30),
+                    1 => array('wrap' => 20),
+                    2 => array('wrap' => 35)
+                );
+
+                $this->_startTable($data);
+                if (isset($data['headline']) && is_array($data['headline'])) {
+                    $this->_tableRow($data['headline'], array('bold' => true), $opts);
+                }
+
+                foreach ($data['data'] as $group) {
+                    foreach ($group as $value) {
+                        if ($value[2] == '') {
+                            $value[2] = "<not set>";
+                        }
+
+                        $this->_tableRow($value, null, $opts);
+                    }
+                }
+
+                $this->_endTable();
+                break;
+            case 'remote-info':
+                $d = $data;
+                $data = array(
+                    'caption' => 'Package details:',
+                    'border'  => false,
+                    'data'    => array(
+                        array("Latest",      $data['stable']),
+                        array("Installed",   $data['installed']),
+                        array("Package",     $data['name']),
+                        array("License",     $data['license']),
+                        array("Category",    $data['category']),
+                        array("Summary",     $data['summary']),
+                        array("Description", $data['description']),
+                    ),
+                );
+
+                if (isset($d['deprecated']) && $d['deprecated']) {
+                    $conf = &PEAR_Config::singleton();
+                    $reg = $conf->getRegistry();
+                    $name = $reg->parsedPackageNameToString($d['deprecated'], true);
+                    $data['data'][] = array('Deprecated! use', $name);
+                }
+            default: {
+                if (is_array($data)) {
+                    $this->_startTable($data);
+                    $count = count($data['data'][0]);
+                    if ($count == 2) {
+                        $opts = array(0 => array('wrap' => 25),
+                                      1 => array('wrap' => 48)
+                        );
+                    } elseif ($count == 3) {
+                        $opts = array(0 => array('wrap' => 30),
+                                      1 => array('wrap' => 20),
+                                      2 => array('wrap' => 35)
+                        );
+                    } else {
+                        $opts = null;
+                    }
+                    if (isset($data['headline']) && is_array($data['headline'])) {
+                        $this->_tableRow($data['headline'],
+                                         array('bold' => true),
+                                         $opts);
+                    }
+
+                    if (is_array($data['data'])) {
+                        foreach($data['data'] as $row) {
+                            $this->_tableRow($row, null, $opts);
+                        }
+                    } else {
+                        $this->_tableRow(array($data['data']), null, $opts);
+                     }
+                    $this->_endTable();
+                } else {
+                    $this->_displayLine($data);
+                }
+            }
+        }
+    }
+
+    function log($text, $append_crlf = true)
+    {
+        if ($append_crlf) {
+            return $this->_displayLine($text);
+        }
+
+        return $this->_display($text);
+    }
+
+    function bold($text)
+    {
+        if (empty($this->term['bold'])) {
+            return strtoupper($text);
+        }
+
+        return $this->term['bold'] . $text . $this->term['normal'];
+    }
+
+    function _displayHeading($title)
+    {
+        print $this->lp.$this->bold($title)."\n";
+        print $this->lp.str_repeat("=", strlen($title))."\n";
+    }
+
+    function _startTable($params = array())
+    {
+        $params['table_data'] = array();
+        $params['widest']     = array();  // indexed by column
+        $params['highest']    = array(); // indexed by row
+        $params['ncols']      = 0;
+        $this->params         = $params;
+    }
+
+    function _tableRow($columns, $rowparams = array(), $colparams = array())
+    {
+        $highest = 1;
+        for ($i = 0; $i < count($columns); $i++) {
+            $col = &$columns[$i];
+            if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
+                $col = wordwrap($col, $colparams[$i]['wrap']);
+            }
+
+            if (strpos($col, "\n") !== false) {
+                $multiline = explode("\n", $col);
+                $w = 0;
+                foreach ($multiline as $n => $line) {
+                    $len = strlen($line);
+                    if ($len > $w) {
+                        $w = $len;
+                    }
+                }
+                $lines = count($multiline);
+            } else {
+                $w = strlen($col);
+            }
+
+            if (isset($this->params['widest'][$i])) {
+                if ($w > $this->params['widest'][$i]) {
+                    $this->params['widest'][$i] = $w;
+                }
+            } else {
+                $this->params['widest'][$i] = $w;
+            }
+
+            $tmp = count_chars($columns[$i], 1);
+            // handle unix, mac and windows formats
+            $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1;
+            if ($lines > $highest) {
+                $highest = $lines;
+            }
+        }
+
+        if (count($columns) > $this->params['ncols']) {
+            $this->params['ncols'] = count($columns);
+        }
+
+        $new_row = array(
+            'data'      => $columns,
+            'height'    => $highest,
+            'rowparams' => $rowparams,
+            'colparams' => $colparams,
+        );
+        $this->params['table_data'][] = $new_row;
+    }
+
+    function _endTable()
+    {
+        extract($this->params);
+        if (!empty($caption)) {
+            $this->_displayHeading($caption);
+        }
+
+        if (count($table_data) === 0) {
+            return;
+        }
+
+        if (!isset($width)) {
+            $width = $widest;
+        } else {
+            for ($i = 0; $i < $ncols; $i++) {
+                if (!isset($width[$i])) {
+                    $width[$i] = $widest[$i];
+                }
+            }
+        }
+
+        $border = false;
+        if (empty($border)) {
+            $cellstart  = '';
+            $cellend    = ' ';
+            $rowend     = '';
+            $padrowend  = false;
+            $borderline = '';
+        } else {
+            $cellstart  = '| ';
+            $cellend    = ' ';
+            $rowend     = '|';
+            $padrowend  = true;
+            $borderline = '+';
+            foreach ($width as $w) {
+                $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1);
+                $borderline .= '+';
+            }
+        }
+
+        if ($borderline) {
+            $this->_displayLine($borderline);
+        }
+
+        for ($i = 0; $i < count($table_data); $i++) {
+            extract($table_data[$i]);
+            if (!is_array($rowparams)) {
+                $rowparams = array();
+            }
+
+            if (!is_array($colparams)) {
+                $colparams = array();
+            }
+
+            $rowlines = array();
+            if ($height > 1) {
+                for ($c = 0; $c < count($data); $c++) {
+                    $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
+                    if (count($rowlines[$c]) < $height) {
+                        $rowlines[$c] = array_pad($rowlines[$c], $height, '');
+                    }
+                }
+            } else {
+                for ($c = 0; $c < count($data); $c++) {
+                    $rowlines[$c] = array($data[$c]);
+                }
+            }
+
+            for ($r = 0; $r < $height; $r++) {
+                $rowtext = '';
+                for ($c = 0; $c < count($data); $c++) {
+                    if (isset($colparams[$c])) {
+                        $attribs = array_merge($rowparams, $colparams);
+                    } else {
+                        $attribs = $rowparams;
+                    }
+
+                    $w = isset($width[$c]) ? $width[$c] : 0;
+                    //$cell = $data[$c];
+                    $cell = $rowlines[$c][$r];
+                    $l = strlen($cell);
+                    if ($l > $w) {
+                        $cell = substr($cell, 0, $w);
+                    }
+
+                    if (isset($attribs['bold'])) {
+                        $cell = $this->bold($cell);
+                    }
+
+                    if ($l < $w) {
+                        // not using str_pad here because we may
+                        // add bold escape characters to $cell
+                        $cell .= str_repeat(' ', $w - $l);
+                    }
+
+                    $rowtext .= $cellstart . $cell . $cellend;
+                }
+
+                if (!$border) {
+                    $rowtext = rtrim($rowtext);
+                }
+
+                $rowtext .= $rowend;
+                $this->_displayLine($rowtext);
+            }
+        }
+
+        if ($borderline) {
+            $this->_displayLine($borderline);
+        }
+    }
+
+    function _displayLine($text)
+    {
+        print "$this->lp$text\n";
+    }
+
+    function _display($text)
+    {
+        print $text;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer.php b/WEB-INF/lib/pear/PEAR/Installer.php
new file mode 100644 (file)
index 0000000..eb17ca7
--- /dev/null
@@ -0,0 +1,1823 @@
+<?php
+/**
+ * PEAR_Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * Used for installation groups in package.xml 2.0 and platform exceptions
+ */
+require_once 'OS/Guess.php';
+require_once 'PEAR/Downloader.php';
+
+define('PEAR_INSTALLER_NOBINARY', -240);
+/**
+ * Administration class used to install PEAR packages and maintain the
+ * installed package database.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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 <file> tag in package.xml
+     * @param string path to install the file in
+     * @param array options from command-line
+     * @access private
+     */
+    function _installFile($file, $atts, $tmp_path, $options)
+    {
+        // {{{ return if this file is meant for another platform
+        static $os;
+        if (!isset($this->_registry)) {
+            $this->_registry = &$this->config->getRegistry();
+        }
+
+        if (isset($atts['platform'])) {
+            if (empty($os)) {
+                $os = new OS_Guess();
+            }
+
+            if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
+                $negate   = true;
+                $platform = substr($atts['platform'], 1);
+            } else {
+                $negate    = false;
+                $platform = $atts['platform'];
+            }
+
+            if ((bool) $os->matchSignature($platform) === $negate) {
+                $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
+                return PEAR_INSTALLER_SKIPPED;
+            }
+        }
+        // }}}
+
+        $channel = $this->pkginfo->getChannel();
+        // {{{ assemble the destination paths
+        switch ($atts['role']) {
+            case 'src':
+            case 'extsrc':
+                $this->source_files++;
+                return;
+            case 'doc':
+            case 'data':
+            case 'test':
+                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
+                            DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
+                unset($atts['baseinstalldir']);
+                break;
+            case 'ext':
+            case 'php':
+                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
+                break;
+            case 'script':
+                $dest_dir = $this->config->get('bin_dir', null, $channel);
+                break;
+            default:
+                return $this->raiseError("Invalid role `$atts[role]' for file $file");
+        }
+
+        $save_destdir = $dest_dir;
+        if (!empty($atts['baseinstalldir'])) {
+            $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+        }
+
+        if (dirname($file) != '.' && empty($atts['install-as'])) {
+            $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
+        }
+
+        if (empty($atts['install-as'])) {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
+        } else {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
+        }
+        $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
+
+        // Clean up the DIRECTORY_SEPARATOR mess
+        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+        list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+                                                    array(DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR),
+                                                    array($dest_file, $orig_file));
+        $final_dest_file = $installed_as = $dest_file;
+        if (isset($this->_options['packagingroot'])) {
+            $installedas_dest_dir  = dirname($final_dest_file);
+            $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+            $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
+        } else {
+            $installedas_dest_dir  = dirname($final_dest_file);
+            $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        }
+
+        $dest_dir  = dirname($final_dest_file);
+        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+        }
+        // }}}
+
+        if (empty($this->_options['register-only']) &&
+              (!file_exists($dest_dir) || !is_dir($dest_dir))) {
+            if (!$this->mkDirHier($dest_dir)) {
+                return $this->raiseError("failed to mkdir $dest_dir",
+                                         PEAR_INSTALLER_FAILED);
+            }
+            $this->log(3, "+ mkdir $dest_dir");
+        }
+
+        // pretty much nothing happens if we are only registering the install
+        if (empty($this->_options['register-only'])) {
+            if (empty($atts['replacements'])) {
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (!@copy($orig_file, $dest_file)) {
+                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $orig_file $dest_file");
+                if (isset($atts['md5sum'])) {
+                    $md5sum = md5_file($dest_file);
+                }
+            } else {
+                // {{{ file with replacements
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $contents = file_get_contents($orig_file);
+                if ($contents === false) {
+                    $contents = '';
+                }
+
+                if (isset($atts['md5sum'])) {
+                    $md5sum = md5($contents);
+                }
+
+                $subst_from = $subst_to = array();
+                foreach ($atts['replacements'] as $a) {
+                    $to = '';
+                    if ($a['type'] == 'php-const') {
+                        if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
+                            eval("\$to = $a[to];");
+                        } else {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid php-const replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    } elseif ($a['type'] == 'pear-config') {
+                        if ($a['to'] == 'master_server') {
+                            $chan = $this->_registry->getChannel($channel);
+                            if (!PEAR::isError($chan)) {
+                                $to = $chan->getServer();
+                            } else {
+                                $to = $this->config->get($a['to'], null, $channel);
+                            }
+                        } else {
+                            $to = $this->config->get($a['to'], null, $channel);
+                        }
+                        if (is_null($to)) {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid pear-config replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    } elseif ($a['type'] == 'package-info') {
+                        if ($t = $this->pkginfo->packageInfo($a['to'])) {
+                            $to = $t;
+                        } else {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid package-info replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    }
+                    if (!is_null($to)) {
+                        $subst_from[] = $a['from'];
+                        $subst_to[] = $to;
+                    }
+                }
+
+                $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
+                if (sizeof($subst_from)) {
+                    $contents = str_replace($subst_from, $subst_to, $contents);
+                }
+
+                $wp = @fopen($dest_file, "wb");
+                if (!is_resource($wp)) {
+                    return $this->raiseError("failed to create $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (@fwrite($wp, $contents) === false) {
+                    return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                fclose($wp);
+                // }}}
+            }
+
+            // {{{ check the md5
+            if (isset($md5sum)) {
+                if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
+                    $this->log(2, "md5sum ok: $final_dest_file");
+                } else {
+                    if (empty($options['force'])) {
+                        // delete the file
+                        if (file_exists($dest_file)) {
+                            unlink($dest_file);
+                        }
+
+                        if (!isset($options['ignore-errors'])) {
+                            return $this->raiseError("bad md5sum for file $final_dest_file",
+                                                 PEAR_INSTALLER_FAILED);
+                        }
+
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    } else {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    }
+                }
+            }
+            // }}}
+            // {{{ set file permissions
+            if (!OS_WINDOWS) {
+                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'));
+                }
+
+                if ($atts['role'] != 'src') {
+                    $this->addFileOperation("chmod", array($mode, $dest_file));
+                    if (!@chmod($dest_file, $mode)) {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+                        }
+                    }
+                }
+            }
+            // }}}
+
+            if ($atts['role'] == 'src') {
+                rename($dest_file, $final_dest_file);
+                $this->log(2, "renamed source file $dest_file to $final_dest_file");
+            } else {
+                $this->addFileOperation("rename", array($dest_file, $final_dest_file,
+                    $atts['role'] == 'ext'));
+            }
+        }
+
+        // Store the full path where the file was installed for easy unistall
+        if ($atts['role'] != 'script') {
+            $loc = $this->config->get($atts['role'] . '_dir');
+        } else {
+            $loc = $this->config->get('bin_dir');
+        }
+
+        if ($atts['role'] != 'src') {
+            $this->addFileOperation("installed_as", array($file, $installed_as,
+                                    $loc,
+                                    dirname(substr($installedas_dest_file, strlen($loc)))));
+        }
+
+        //$this->log(2, "installed: $dest_file");
+        return PEAR_INSTALLER_OK;
+    }
+
+    // }}}
+    // {{{ _installFile2()
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param string filename
+     * @param array attributes from <file> tag in package.xml
+     * @param string path to install the file in
+     * @param array options from command-line
+     * @access private
+     */
+    function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
+    {
+        $atts = $real_atts;
+        if (!isset($this->_registry)) {
+            $this->_registry = &$this->config->getRegistry();
+        }
+
+        $channel = $pkg->getChannel();
+        // {{{ assemble the destination paths
+        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;
+        }
+
+        list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
+        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+        }
+
+        $final_dest_file = $installed_as = $dest_file;
+        if (isset($this->_options['packagingroot'])) {
+            $final_dest_file = $this->_prependPath($final_dest_file,
+                $this->_options['packagingroot']);
+        }
+
+        $dest_dir  = dirname($final_dest_file);
+        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        // }}}
+
+        if (empty($this->_options['register-only'])) {
+            if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
+                if (!$this->mkDirHier($dest_dir)) {
+                    return $this->raiseError("failed to mkdir $dest_dir",
+                                             PEAR_INSTALLER_FAILED);
+                }
+                $this->log(3, "+ mkdir $dest_dir");
+            }
+        }
+
+        $attribs = $atts['attribs'];
+        unset($atts['attribs']);
+        // pretty much nothing happens if we are only registering the install
+        if (empty($this->_options['register-only'])) {
+            if (!count($atts)) { // no tasks
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (!@copy($orig_file, $dest_file)) {
+                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $orig_file $dest_file");
+                if (isset($attribs['md5sum'])) {
+                    $md5sum = md5_file($dest_file);
+                }
+            } else { // file with tasks
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $contents = file_get_contents($orig_file);
+                if ($contents === false) {
+                    $contents = '';
+                }
+
+                if (isset($attribs['md5sum'])) {
+                    $md5sum = md5($contents);
+                }
+
+                foreach ($atts as $tag => $raw) {
+                    $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
+                    $task = "PEAR_Task_$tag";
+                    $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
+                    if (!$task->isScript()) { // scripts are only handled after installation
+                        $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
+                        $res = $task->startSession($pkg, $contents, $final_dest_file);
+                        if ($res === false) {
+                            continue; // skip this file
+                        }
+
+                        if (PEAR::isError($res)) {
+                            return $res;
+                        }
+
+                        $contents = $res; // save changes
+                    }
+
+                    $wp = @fopen($dest_file, "wb");
+                    if (!is_resource($wp)) {
+                        return $this->raiseError("failed to create $dest_file: $php_errormsg",
+                                                 PEAR_INSTALLER_FAILED);
+                    }
+
+                    if (fwrite($wp, $contents) === false) {
+                        return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+                                                 PEAR_INSTALLER_FAILED);
+                    }
+
+                    fclose($wp);
+                }
+            }
+
+            // {{{ check the md5
+            if (isset($md5sum)) {
+                // Make sure the original md5 sum matches with expected
+                if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
+                    $this->log(2, "md5sum ok: $final_dest_file");
+
+                    if (isset($contents)) {
+                        // set md5 sum based on $content in case any tasks were run.
+                        $real_atts['attribs']['md5sum'] = md5($contents);
+                    }
+                } else {
+                    if (empty($options['force'])) {
+                        // delete the file
+                        if (file_exists($dest_file)) {
+                            unlink($dest_file);
+                        }
+
+                        if (!isset($options['ignore-errors'])) {
+                            return $this->raiseError("bad md5sum for file $final_dest_file",
+                                                     PEAR_INSTALLER_FAILED);
+                        }
+
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    } else {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    }
+                }
+            } else {
+                $real_atts['attribs']['md5sum'] = md5_file($dest_file);
+            }
+
+            // }}}
+            // {{{ set file permissions
+            if (!OS_WINDOWS) {
+                if ($role->isExecutable()) {
+                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
+                    $this->log(3, "+ chmod +x $dest_file");
+                } else {
+                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+                }
+
+                if ($attribs['role'] != 'src') {
+                    $this->addFileOperation("chmod", array($mode, $dest_file));
+                    if (!@chmod($dest_file, $mode)) {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+                        }
+                    }
+                }
+            }
+            // }}}
+
+            if ($attribs['role'] == 'src') {
+                rename($dest_file, $final_dest_file);
+                $this->log(2, "renamed source file $dest_file to $final_dest_file");
+            } else {
+                $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
+            }
+        }
+
+        // Store the full path where the file was installed for easy uninstall
+        if ($attribs['role'] != 'src') {
+            $loc = $this->config->get($role->getLocationConfig(), null, $channel);
+            $this->addFileOperation('installed_as', array($file, $installed_as,
+                                $loc,
+                                dirname(substr($installed_as, strlen($loc)))));
+        }
+
+        //$this->log(2, "installed: $dest_file");
+        return PEAR_INSTALLER_OK;
+    }
+
+    // }}}
+    // {{{ 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()
+    {
+        // {{{ first, check permissions and such manually
+        $errors = array();
+        foreach ($this->file_operations as $key => $tr) {
+            list($type, $data) = $tr;
+            switch ($type) {
+                case 'rename':
+                    if (!file_exists($data[0])) {
+                        $errors[] = "cannot rename file $data[0], doesn't exist";
+                    }
+
+                    // check that dest dir. is writable
+                    if (!is_writable(dirname($data[1]))) {
+                        $errors[] = "permission denied ($type): $data[1]";
+                    }
+                    break;
+                case 'chmod':
+                    // check that file is writable
+                    if (!is_writable($data[1])) {
+                        $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
+                    }
+                    break;
+                case 'delete':
+                    if (!file_exists($data[0])) {
+                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
+                    }
+                    // check that directory is writable
+                    if (file_exists($data[0])) {
+                        if (!is_writable(dirname($data[0]))) {
+                            $errors[] = "permission denied ($type): $data[0]";
+                        } else {
+                            // make sure the file to be deleted can be opened for writing
+                            $fp = false;
+                            if (!is_dir($data[0]) &&
+                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
+                                $errors[] = "permission denied ($type): $data[0]";
+                            } elseif ($fp) {
+                                fclose($fp);
+                            }
+                        }
+
+                        /* Verify we are not deleting a file owned by another package
+                         * This can happen when a file moves from package A to B in
+                         * an upgrade ala http://pear.php.net/17986
+                         */
+                        $info = array(
+                            'package' => strtolower($this->pkginfo->getName()),
+                            'channel' => strtolower($this->pkginfo->getChannel()),
+                        );
+                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
+                        if (is_array($result)) {
+                            $res = array_diff($result, $info);
+                            if (!empty($res)) {
+                                $new = $this->_registry->getPackage($result[1], $result[0]);
+                                $this->file_operations[$key] = false;
+                                $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled.");
+                            }
+                        }
+                    }
+                    break;
+            }
+
+        }
+        // }}}
+
+        $n = count($this->file_operations);
+        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
+
+        $m = count($errors);
+        if ($m > 0) {
+            foreach ($errors as $error) {
+                if (!isset($this->_options['soft'])) {
+                    $this->log(1, $error);
+                }
+            }
+
+            if (!isset($this->_options['ignore-errors'])) {
+                return false;
+            }
+        }
+
+        $this->_dirtree = array();
+        // {{{ really commit the transaction
+        foreach ($this->file_operations as $i => $tr) {
+            if (!$tr) {
+                // support removal of non-existing backups
+                continue;
+            }
+
+            list($type, $data) = $tr;
+            switch ($type) {
+                case 'backup':
+                    if (!file_exists($data[0])) {
+                        $this->file_operations[$i] = false;
+                        break;
+                    }
+
+                    if (!@copy($data[0], $data[0] . '.bak')) {
+                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
+                            '.bak ' . $php_errormsg);
+                        return false;
+                    }
+                    $this->log(3, "+ backup $data[0] to $data[0].bak");
+                    break;
+                case 'removebackup':
+                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+                        unlink($data[0] . '.bak');
+                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+                    }
+                    break;
+                case 'rename':
+                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
+                    if (!$test && file_exists($data[1])) {
+                        if ($data[2]) {
+                            $extra = ', this extension must be installed manually.  Rename to "' .
+                                basename($data[1]) . '"';
+                        } else {
+                            $extra = '';
+                        }
+
+                        if (!isset($this->_options['soft'])) {
+                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
+                                $data[0] . $extra);
+                        }
+
+                        if (!isset($this->_options['ignore-errors'])) {
+                            return false;
+                        }
+                    }
+
+                    // permissions issues with rename - copy() is far superior
+                    $perms = @fileperms($data[0]);
+                    if (!@copy($data[0], $data[1])) {
+                        $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
+                            ' ' . $php_errormsg);
+                        return false;
+                    }
+
+                    // copy over permissions, otherwise they are lost
+                    @chmod($data[1], $perms);
+                    @unlink($data[0]);
+                    $this->log(3, "+ mv $data[0] $data[1]");
+                    break;
+                case 'chmod':
+                    if (!@chmod($data[1], $data[0])) {
+                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
+                            decoct($data[0]) . ' ' . $php_errormsg);
+                        return false;
+                    }
+
+                    $octmode = decoct($data[0]);
+                    $this->log(3, "+ chmod $octmode $data[1]");
+                    break;
+                case 'delete':
+                    if (file_exists($data[0])) {
+                        if (!@unlink($data[0])) {
+                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
+                                $php_errormsg);
+                            return false;
+                        }
+                        $this->log(3, "+ rm $data[0]");
+                    }
+                    break;
+                case 'rmdir':
+                    if (file_exists($data[0])) {
+                        do {
+                            $testme = opendir($data[0]);
+                            while (false !== ($entry = readdir($testme))) {
+                                if ($entry == '.' || $entry == '..') {
+                                    continue;
+                                }
+                                closedir($testme);
+                                break 2; // this directory is not empty and can't be
+                                         // deleted
+                            }
+
+                            closedir($testme);
+                            if (!@rmdir($data[0])) {
+                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
+                                    $php_errormsg);
+                                return false;
+                            }
+                            $this->log(3, "+ rmdir $data[0]");
+                        } while (false);
+                    }
+                    break;
+                case 'installed_as':
+                    $this->pkginfo->setInstalledAs($data[0], $data[1]);
+                    if (!isset($this->_dirtree[dirname($data[1])])) {
+                        $this->_dirtree[dirname($data[1])] = true;
+                        $this->pkginfo->setDirtree(dirname($data[1]));
+
+                        while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
+                                $data[3] != '/' && $data[3] != '\\') {
+                            $this->pkginfo->setDirtree($pp =
+                                $this->_prependPath($data[3], $data[2]));
+                            $this->_dirtree[$pp] = true;
+                            $data[3] = dirname($data[3]);
+                        }
+                    }
+                    break;
+            }
+        }
+        // }}}
+        $this->log(2, "successfully committed $n file operations");
+        $this->file_operations = array();
+        return true;
+    }
+
+    // }}}
+    // {{{ 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')) {
+                        if (file_exists($data[0] && is_writable($data[0]))) {
+                            unlink($data[0]);
+                        }
+                        @copy($data[0] . '.bak', $data[0]);
+                        $this->log(3, "+ restore $data[0] from $data[0].bak");
+                    }
+                    break;
+                case 'removebackup':
+                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+                        unlink($data[0] . '.bak');
+                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+                    }
+                    break;
+                case 'rename':
+                    @unlink($data[0]);
+                    $this->log(3, "+ rm $data[0]");
+                    break;
+                case 'mkdir':
+                    @rmdir($data[0]);
+                    $this->log(3, "+ rmdir $data[0]");
+                    break;
+                case 'chmod':
+                    break;
+                case 'delete':
+                    break;
+                case 'installed_as':
+                    $this->pkginfo->setInstalledAs($data[0], false);
+                    break;
+            }
+        }
+        $this->pkginfo->resetDirtree();
+        $this->file_operations = array();
+    }
+
+    // }}}
+    // {{{ 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:
+     *
+     * <code>
+     * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+     *    'info' => array() // parsed package.xml
+     * );
+     * </code>
+     * @param array this will be populated with any error messages
+     * @param false private recursion variable
+     * @param false private recursion variable
+     * @param false private recursion variable
+     * @deprecated in favor of PEAR_Downloader
+     */
+    function download($packages, $options, &$config, &$installpackages,
+                      &$errors, $installed = false, $willinstall = false, $state = false)
+    {
+        // trickiness: initialize here
+        parent::PEAR_Downloader($this->ui, $options, $config);
+        $ret             = parent::download($packages);
+        $errors          = $this->getErrorMsgs();
+        $installpackages = $this->getDownloadedPackages();
+        trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
+                      "in favor of PEAR_Downloader class", E_USER_WARNING);
+        return $ret;
+    }
+
+    // }}}
+    // {{{ _parsePackageXml()
+
+    function _parsePackageXml(&$descfile)
+    {
+        // Parse xml file -----------------------------------------------
+        $pkg = new PEAR_PackageFile($this->config, $this->debug);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($p)) {
+            if (is_array($p->getUserInfo())) {
+                foreach ($p->getUserInfo() as $err) {
+                    $loglevel = $err['level'] == 'error' ? 0 : 1;
+                    if (!isset($this->_options['soft'])) {
+                        $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
+                    }
+                }
+            }
+            return $this->raiseError('Installation failed: invalid package file');
+        }
+
+        $descfile = $p->getPackageFile();
+        return $p;
+    }
+
+    // }}}
+    /**
+     * Set the list of PEAR_Downloader_Package objects to allow more sane
+     * dependency validation
+     * @param array
+     */
+    function setDownloadedPackages(&$pkgs)
+    {
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $this->analyzeDependencies($pkgs);
+        PEAR::popErrorHandling();
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+        $this->_downloadedPackages = &$pkgs;
+    }
+
+    /**
+     * Set the list of PEAR_Downloader_Package objects to allow more sane
+     * dependency validation
+     * @param array
+     */
+    function setUninstallPackages(&$pkgs)
+    {
+        $this->_downloadedPackages = &$pkgs;
+    }
+
+    function getInstallPackages()
+    {
+        return $this->_downloadedPackages;
+    }
+
+    // {{{ 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();
+        } else {
+            $descfile = $pkgfile;
+            $pkg      = $this->_parsePackageXml($descfile);
+            if (PEAR::isError($pkg)) {
+                return $pkg;
+            }
+        }
+
+        $tmpdir = dirname($descfile);
+        if (realpath($descfile) != realpath($pkgfile)) {
+            // Use the temp_dir since $descfile can contain the download dir path
+            $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
+            $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
+
+            $tar = new Archive_Tar($pkgfile);
+            if (!$tar->extract($tmpdir)) {
+                return $this->raiseError("unable to unpack $pkgfile");
+            }
+        }
+
+        $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);
+                if (!$installregistry->channelExists($channel, true)) {
+                    // we need to fake a channel-discover of this channel
+                    $chanobj = $this->_registry->getChannel($channel, true);
+                    $installregistry->addChannel($chanobj);
+                }
+                $php_dir = $packrootphp_dir;
+            } else {
+                $installregistry = &$this->_registry;
+                $php_dir = $this->config->get('php_dir', null, $channel);
+            }
+            $this->installroot = '';
+        }
+
+        // {{{ checks to do when not in "force" mode
+        if (empty($options['force']) &&
+              (file_exists($this->config->get('php_dir')) &&
+               is_dir($this->config->get('php_dir')))) {
+            $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
+            $instfilelist = $pkg->getInstallationFileList(true);
+            if (PEAR::isError($instfilelist)) {
+                return $instfilelist;
+            }
+
+            // ensure we have the most accurate registry
+            $installregistry->flushFileMap();
+            $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
+            if (PEAR::isError($test)) {
+                return $test;
+            }
+
+            if (sizeof($test)) {
+                $pkgs = $this->getInstallPackages();
+                $found = false;
+                foreach ($pkgs as $param) {
+                    if ($pkg->isSubpackageOf($param)) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if ($found) {
+                    // subpackages can conflict with earlier versions of parent packages
+                    $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
+                    $tmp = $test;
+                    foreach ($tmp as $file => $info) {
+                        if (is_array($info)) {
+                            if (strtolower($info[1]) == strtolower($param->getPackage()) &&
+                                  strtolower($info[0]) == strtolower($param->getChannel())
+                            ) {
+                                if (isset($parentreg['filelist'][$file])) {
+                                    unset($parentreg['filelist'][$file]);
+                                } else{
+                                    $pos     = strpos($file, '/');
+                                    $basedir = substr($file, 0, $pos);
+                                    $file2   = substr($file, $pos + 1);
+                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+                                    ) {
+                                        unset($parentreg['filelist'][$file2]);
+                                    }
+                                }
+
+                                unset($test[$file]);
+                            }
+                        } else {
+                            if (strtolower($param->getChannel()) != 'pear.php.net') {
+                                continue;
+                            }
+
+                            if (strtolower($info) == strtolower($param->getPackage())) {
+                                if (isset($parentreg['filelist'][$file])) {
+                                    unset($parentreg['filelist'][$file]);
+                                } else{
+                                    $pos     = strpos($file, '/');
+                                    $basedir = substr($file, 0, $pos);
+                                    $file2   = substr($file, $pos + 1);
+                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+                                    ) {
+                                        unset($parentreg['filelist'][$file2]);
+                                    }
+                                }
+
+                                unset($test[$file]);
+                            }
+                        }
+                    }
+
+                    $pfk = &new PEAR_PackageFile($this->config);
+                    $parentpkg = &$pfk->fromArray($parentreg);
+                    $installregistry->updatePackage2($parentpkg);
+                }
+
+                if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
+                    $tmp = $test;
+                    foreach ($tmp as $file => $info) {
+                        if (is_string($info)) {
+                            // pear.php.net packages are always stored as strings
+                            if (strtolower($info) == strtolower($param->getPackage())) {
+                                // upgrading existing package
+                                unset($test[$file]);
+                            }
+                        }
+                    }
+                }
+
+                if (count($test)) {
+                    $msg = "$channel/$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);
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->log(0, "WARNING: $msg");
+                    }
+                }
+            }
+        }
+        // }}}
+
+        $this->startFileTransaction();
+
+        $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['upgrade']) && empty($options['soft'])) {
+            // checks to do only when installing new packages
+            if (empty($options['force']) && $test) {
+                return $this->raiseError("$channel/$pkgname is already installed");
+            }
+        } else {
+            // Upgrade
+            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)");
+                }
+            }
+        }
+
+        // Do cleanups for upgrade and install, remove old release's files first
+        if ($test && 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);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: ' . $err->getMessage());
+                }
+            } else {
+                $backedup = $err;
+            }
+        }
+
+        // {{{ Copy files to dest dir ---------------------------------------
+
+        // info from the package it self we want to access from _installFile
+        $this->pkginfo = &$pkg;
+        // used to determine whether we should build any C code
+        $this->source_files = 0;
+
+        $savechannel = $this->config->get('default_channel');
+        if (empty($options['register-only']) && !is_dir($php_dir)) {
+            if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
+                return $this->raiseError("no installation destination directory '$php_dir'\n");
+            }
+        }
+
+        if (substr($pkgfile, -4) != '.xml') {
+            $tmpdir .= DIRECTORY_SEPARATOR . $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;
+        }
+
+        $p = &$installregistry->getPackage($pkgname, $channel);
+        $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
+
+        $pkg->resetFilelist();
+        $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
+            'version', $pkg->getChannel()));
+        foreach ($filelist as $file => $atts) {
+            $this->expectError(PEAR_INSTALLER_FAILED);
+            if ($pkg->getPackagexmlVersion() == '1.0') {
+                $res = $this->_installFile($file, $atts, $tmpdir, $options);
+            } else {
+                $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
+            }
+            $this->popExpect();
+
+            if (PEAR::isError($res)) {
+                if (empty($options['ignore-errors'])) {
+                    $this->rollbackFileTransaction();
+                    if ($res->getMessage() == "file does not exist") {
+                        $this->raiseError("file $file in package.xml does not exist");
+                    }
+
+                    return $this->raiseError($res);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, "Warning: " . $res->getMessage());
+                }
+            }
+
+            $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
+            if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
+                // Register files that were installed
+                $pkg->installedFile($file, $atts);
+            }
+        }
+        // }}}
+
+        // {{{ compile and install source files
+        if ($this->source_files > 0 && empty($options['nobuild'])) {
+            if (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 {
+            if ($dirtree) {
+                $this->startFileTransaction();
+                // attempt to delete empty directories
+                uksort($dirtree, array($this, '_sortDirs'));
+                foreach($dirtree as $dir => $notused) {
+                    $this->addFileOperation('rmdir', array($dir));
+                }
+                $this->commitFileTransaction();
+            }
+
+            $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);
+            $extra  = $copyto != $dest ? " as '$copyto'" : '';
+            $this->log(1, "Installing '$dest'$extra");
+
+            $copydir = dirname($copyto);
+            // pretty much nothing happens if we are only registering the install
+            if (empty($this->_options['register-only'])) {
+                if (!file_exists($copydir) || !is_dir($copydir)) {
+                    if (!$this->mkDirHier($copydir)) {
+                        return $this->raiseError("failed to mkdir $copydir",
+                            PEAR_INSTALLER_FAILED);
+                    }
+
+                    $this->log(3, "+ mkdir $copydir");
+                }
+
+                if (!@copy($ext['file'], $copyto)) {
+                    return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $ext[file] $copyto");
+                $this->addFileOperation('rename', array($ext['file'], $copyto));
+                if (!OS_WINDOWS) {
+                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+                    $this->addFileOperation('chmod', array($mode, $copyto));
+                    if (!@chmod($copyto, $mode)) {
+                        $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
+                    }
+                }
+            }
+
+
+            $data = array(
+                'role'         => $role,
+                'name'         => $bn,
+                'installed_as' => $dest,
+                'php_api'      => $ext['php_api'],
+                'zend_mod_api' => $ext['zend_mod_api'],
+                'zend_ext_api' => $ext['zend_ext_api'],
+            );
+
+            if ($filelist->getPackageXmlVersion() == '1.0') {
+                $filelist->installedFile($bn, $data);
+            } else {
+                $filelist->installedFile($bn, array('attribs' => $data));
+            }
+        }
+    }
+
+    // }}}
+    function &getUninstallPackages()
+    {
+        return $this->_downloadedPackages;
+    }
+    // {{{ uninstall()
+
+    /**
+     * Uninstall a package
+     *
+     * This method removes all files installed by the application, and then
+     * removes any empty directories.
+     * @param string package name
+     * @param array Command-line options.  Possibilities include:
+     *
+     *              - installroot: base installation dir, if not the default
+     *              - register-only : update registry but don't remove files
+     *              - nodeps: do not process dependencies of other packages to ensure
+     *                        uninstallation does not break things
+     */
+    function uninstall($package, $options = array())
+    {
+        $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
+        $this->config->setInstallRoot($installRoot);
+
+        $this->installroot = '';
+        $this->_registry = &$this->config->getRegistry();
+        if (is_object($package)) {
+            $channel = $package->getChannel();
+            $pkg     = $package;
+            $package = $pkg->getPackage();
+        } else {
+            $pkg = false;
+            $info = $this->_registry->parsePackageName($package,
+                $this->config->get('default_channel'));
+            $channel = $info['channel'];
+            $package = $info['package'];
+        }
+
+        $savechannel = $this->config->get('default_channel');
+        $this->configSet('default_channel', $channel);
+        if (!is_object($pkg)) {
+            $pkg = $this->_registry->getPackage($package, $channel);
+        }
+
+        if (!$pkg) {
+            $this->configSet('default_channel', $savechannel);
+            return $this->raiseError($this->_registry->parsedPackageNameToString(
+                array(
+                    'channel' => $channel,
+                    'package' => $package
+                ), true) . ' not installed');
+        }
+
+        if ($pkg->getInstalledBinary()) {
+            // this is just an alias for a binary package
+            return $this->_registry->deletePackage($package, $channel);
+        }
+
+        $filelist = $pkg->getFilelist();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        if (!class_exists('PEAR_Dependency2')) {
+            require_once 'PEAR/Dependency2.php';
+        }
+
+        $depchecker = &new PEAR_Dependency2($this->config, $options,
+            array('channel' => $channel, 'package' => $package),
+            PEAR_VALIDATE_UNINSTALLING);
+        $e = $depchecker->validatePackageUninstall($this);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($e)) {
+            if (!isset($options['ignore-errors'])) {
+                return $this->raiseError($e);
+            }
+
+            if (!isset($options['soft'])) {
+                $this->log(0, 'WARNING: ' . $e->getMessage());
+            }
+        } elseif (is_array($e)) {
+            if (!isset($options['soft'])) {
+                $this->log(0, $e[0]);
+            }
+        }
+
+        $this->pkginfo = &$pkg;
+        // pretty much nothing happens if we are only registering the uninstall
+        if (empty($options['register-only'])) {
+            // {{{ Delete the files
+            $this->startFileTransaction();
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
+                PEAR::popErrorHandling();
+                $this->rollbackFileTransaction();
+                $this->configSet('default_channel', $savechannel);
+                if (!isset($options['ignore-errors'])) {
+                    return $this->raiseError($err);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: ' . $err->getMessage());
+                }
+            } else {
+                PEAR::popErrorHandling();
+            }
+
+            if (!$this->commitFileTransaction()) {
+                $this->rollbackFileTransaction();
+                if (!isset($options['ignore-errors'])) {
+                    return $this->raiseError("uninstall failed");
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: uninstall failed');
+                }
+            } else {
+                $this->startFileTransaction();
+                $dirtree = $pkg->getDirTree();
+                if ($dirtree === false) {
+                    $this->configSet('default_channel', $savechannel);
+                    return $this->_registry->deletePackage($package, $channel);
+                }
+
+                // attempt to delete empty directories
+                uksort($dirtree, array($this, '_sortDirs'));
+                foreach($dirtree as $dir => $notused) {
+                    $this->addFileOperation('rmdir', array($dir));
+                }
+
+                if (!$this->commitFileTransaction()) {
+                    $this->rollbackFileTransaction();
+                    if (!isset($options['ignore-errors'])) {
+                        return $this->raiseError("uninstall failed");
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->log(0, 'WARNING: uninstall failed');
+                    }
+                }
+            }
+            // }}}
+        }
+
+        $this->configSet('default_channel', $savechannel);
+        // Register that the package is no longer installed
+        return $this->_registry->deletePackage($package, $channel);
+    }
+
+    /**
+     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+     *
+     * It also removes duplicate dependencies
+     * @param array an array of PEAR_PackageFile_v[1/2] objects
+     * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
+     */
+    function sortPackagesForUninstall(&$packages)
+    {
+        $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
+        if (PEAR::isError($this->_dependencyDB)) {
+            return $this->_dependencyDB;
+        }
+        usort($packages, array(&$this, '_sortUninstall'));
+    }
+
+    function _sortUninstall($a, $b)
+    {
+        if (!$a->getDeps() && !$b->getDeps()) {
+            return 0; // neither package has dependencies, order is insignificant
+        }
+        if ($a->getDeps() && !$b->getDeps()) {
+            return -1; // $a must be installed after $b because $a has dependencies
+        }
+        if (!$a->getDeps() && $b->getDeps()) {
+            return 1; // $b must be installed after $a because $b has dependencies
+        }
+        // both packages have dependencies
+        if ($this->_dependencyDB->dependsOn($a, $b)) {
+            return -1;
+        }
+        if ($this->_dependencyDB->dependsOn($b, $a)) {
+            return 1;
+        }
+        return 0;
+    }
+
+    // }}}
+    // {{{ _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');
+        }
+    }
+
+    // }}}
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role.php b/WEB-INF/lib/pear/PEAR/Installer/Role.php
new file mode 100644 (file)
index 0000000..0c50fa7
--- /dev/null
@@ -0,0 +1,276 @@
+<?php
+/**
+ * PEAR_Installer_Role
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Role.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * base class for installer roles
+ */
+require_once 'PEAR/Installer/Role/Common.php';
+require_once 'PEAR/XMLParser.php';
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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($class, $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)) {
+            return $ret;
+        }
+
+        $ret = array();
+        foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+            if ($okreleases['installable']) {
+                $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Return an array of roles that are affected by the baseinstalldir attribute
+     *
+     * Most roles ignore this attribute, and instead install directly into:
+     * PackageName/filepath
+     * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php
+     * @param bool clear cache
+     * @return array
+     * @static
+     */
+    function getBaseinstallRoles($clear = false)
+    {
+        if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+            PEAR_Installer_Role::registerRoles();
+        }
+
+        static $ret;
+        if ($clear) {
+            unset($ret);
+        }
+
+        if (isset($ret)) {
+            return $ret;
+        }
+
+        $ret = array();
+        foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+            if ($okreleases['honorsbaseinstall']) {
+                $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Return an array of file roles that should be analyzed for PHP content at package time,
+     * like the "php" role.
+     * @param bool clear cache
+     * @return array
+     * @static
+     */
+    function getPhpRoles($clear = false)
+    {
+        if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+            PEAR_Installer_Role::registerRoles();
+        }
+
+        static $ret;
+        if ($clear) {
+            unset($ret);
+        }
+
+        if (isset($ret)) {
+            return $ret;
+        }
+
+        $ret = array();
+        foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+            if ($okreleases['phpfile']) {
+                $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Scan through the Command directory looking for classes
+     * and see what commands they implement.
+     * @param string which directory to look for classes, defaults to
+     *               the Installer/Roles subdirectory of
+     *               the directory from where this file (__FILE__) is
+     *               included.
+     *
+     * @return bool TRUE on success, a PEAR error on failure
+     * @access public
+     * @static
+     */
+    function registerRoles($dir = null)
+    {
+        $GLOBALS['_PEAR_INSTALLER_ROLES'] = array();
+        $parser = new PEAR_XMLParser;
+        if ($dir === null) {
+            $dir = dirname(__FILE__) . '/Role';
+        }
+
+        if (!file_exists($dir) || !is_dir($dir)) {
+            return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory");
+        }
+
+        $dp = @opendir($dir);
+        if (empty($dp)) {
+            return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg");
+        }
+
+        while ($entry = readdir($dp)) {
+            if ($entry{0} == '.' || substr($entry, -4) != '.xml') {
+                continue;
+            }
+
+            $class = "PEAR_Installer_Role_".substr($entry, 0, -4);
+            // List of roles
+            if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) {
+                $file = "$dir/$entry";
+                $parser->parse(file_get_contents($file));
+                $data = $parser->getData();
+                if (!is_array($data['releasetypes'])) {
+                    $data['releasetypes'] = array($data['releasetypes']);
+                }
+
+                $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data;
+            }
+        }
+
+        closedir($dp);
+        ksort($GLOBALS['_PEAR_INSTALLER_ROLES']);
+        PEAR_Installer_Role::getBaseinstallRoles(true);
+        PEAR_Installer_Role::getInstallableRoles(true);
+        PEAR_Installer_Role::getPhpRoles(true);
+        PEAR_Installer_Role::getValidRoles('****', true);
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php
new file mode 100644 (file)
index 0000000..7620122
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * PEAR_Installer_Role_Cfg
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2007-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Cfg.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.7.0
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2007-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.7.0
+ */
+class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common
+{
+    /**
+     * @var PEAR_Installer
+     */
+    var $installer;
+
+    /**
+     * the md5 of the original file
+     *
+     * @var unknown_type
+     */
+    var $md5 = null;
+
+    /**
+     * Do any unusual setup here
+     * @param PEAR_Installer
+     * @param PEAR_PackageFile_v2
+     * @param array file attributes
+     * @param string file name
+     */
+    function setup(&$installer, $pkg, $atts, $file)
+    {
+        $this->installer = &$installer;
+        $reg = &$this->installer->config->getRegistry();
+        $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel());
+        if ($package) {
+            $filelist = $package->getFilelist();
+            if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) {
+                $this->md5 = $filelist[$file]['md5sum'];
+            }
+        }
+    }
+
+    function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null)
+    {
+        $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer);
+        if (@file_exists($test[2]) && @file_exists($test[3])) {
+            $md5 = md5_file($test[2]);
+            // configuration has already been installed, check for mods
+            if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) {
+                // configuration has been modified, so save our version as
+                // configfile-version
+                $old = $test[2];
+                $test[2] .= '.new-' . $pkg->getVersion();
+                // backup original and re-install it
+                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                $tmpcfg = $this->config->get('temp_dir');
+                $newloc = System::mkdir(array('-p', $tmpcfg));
+                if (!$newloc) {
+                    // try temp_dir
+                    $newloc = System::mktemp(array('-d'));
+                    if (!$newloc || PEAR::isError($newloc)) {
+                        PEAR::popErrorHandling();
+                        return PEAR::raiseError('Could not save existing configuration file '.
+                            $old . ', unable to install.  Please set temp_dir ' .
+                            'configuration variable to a writeable location and try again');
+                    }
+                } else {
+                    $newloc = $tmpcfg;
+                }
+
+                $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile');
+                if (!@copy($old, $temp_file)) {
+                    PEAR::popErrorHandling();
+                    return PEAR::raiseError('Could not save existing configuration file '.
+                        $old . ', unable to install.  Please set temp_dir ' .
+                        'configuration variable to a writeable location and try again');
+                }
+
+                PEAR::popErrorHandling();
+                $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file");
+                $this->installer->addFileOperation('rename', array($temp_file, $old, false));
+                $this->installer->addFileOperation('delete', array($temp_file));
+            }
+        }
+
+        return $test;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.xml
new file mode 100644 (file)
index 0000000..7a415dc
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>cfg_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall>1</unusualbaseinstall>
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php
new file mode 100644 (file)
index 0000000..23e7348
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Base class for all installation roles.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * Base class for all installation roles.
+ *
+ * This class allows extensibility of file roles.  Packages with complex
+ * customization can now provide custom file roles along with the possibility of
+ * adding configuration values to match.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Common
+{
+    /**
+     * @var PEAR_Config
+     * @access protected
+     */
+    var $config;
+
+    /**
+     * @param PEAR_Config
+     */
+    function PEAR_Installer_Role_Common(&$config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * Retrieve configuration information about a file role from its XML info
+     *
+     * @param string $role Role Classname, as in "PEAR_Installer_Role_Data"
+     * @return array
+     */
+    function getInfo($role)
+    {
+        if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) {
+            return PEAR::raiseError('Unknown Role class: "' . $role . '"');
+        }
+        return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role];
+    }
+
+    /**
+     * This is called for each file to set up the directories and files
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param array attributes from the <file> tag
+     * @param string file name
+     * @return array an array consisting of:
+     *
+     *    1 the original, pre-baseinstalldir installation directory
+     *    2 the final installation directory
+     *    3 the full path to the final location of the file
+     *    4 the location of the pre-installation file
+     */
+    function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null)
+    {
+        $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'],
+                    $layer, $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'],
+                    $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage();
+        }
+        if (dirname($file) != '.' && empty($atts['install-as'])) {
+            $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
+        }
+        if (empty($atts['install-as'])) {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
+        } else {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
+        }
+        $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
+
+        // Clean up the DIRECTORY_SEPARATOR mess
+        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+        
+        list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+                                                    array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR),
+                                                    array($dest_dir, $dest_file, $orig_file));
+        return array($save_destdir, $dest_dir, $dest_file, $orig_file);
+    }
+
+    /**
+     * Get the name of the configuration variable that specifies the location of this file
+     * @return string|false
+     */
+    function getLocationConfig()
+    {
+        $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/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php
new file mode 100644 (file)
index 0000000..e3b7fa2
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Data
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Data.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Data.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Data.xml
new file mode 100644 (file)
index 0000000..eae6372
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>data_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php
new file mode 100644 (file)
index 0000000..d592fff
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Doc
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Doc.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.xml
new file mode 100644 (file)
index 0000000..173afba
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>doc_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php
new file mode 100644 (file)
index 0000000..eceb027
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Ext
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Ext.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.xml
new file mode 100644 (file)
index 0000000..e2940fe
--- /dev/null
@@ -0,0 +1,12 @@
+<role version="1.0">
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>ext_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension>1</phpextension>
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php
new file mode 100644 (file)
index 0000000..e2abf44
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Php
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Php.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Php.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Php.xml
new file mode 100644 (file)
index 0000000..6b9a0e6
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>php_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile>1</phpfile>
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php
new file mode 100644 (file)
index 0000000..b31469e
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Script
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Script.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Script.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Script.xml
new file mode 100644 (file)
index 0000000..e732cf2
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>bin_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable>1</executable>
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php
new file mode 100644 (file)
index 0000000..5037053
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * PEAR_Installer_Role_Src
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Src.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Src.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Src.xml
new file mode 100644 (file)
index 0000000..1034834
--- /dev/null
@@ -0,0 +1,12 @@
+<role version="1.0">
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <installable>1</installable>
+ <locationconfig>temp_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php
new file mode 100644 (file)
index 0000000..14c0e60
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Test
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @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/WEB-INF/lib/pear/PEAR/Installer/Role/Test.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Test.xml
new file mode 100644 (file)
index 0000000..51d5b89
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>test_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php
new file mode 100644 (file)
index 0000000..11adeff
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Www
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2007-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Www.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.7.0
+ */
+
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  2007-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.7.0
+ */
+class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Www.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Www.xml
new file mode 100644 (file)
index 0000000..7598be3
--- /dev/null
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>www_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile.php b/WEB-INF/lib/pear/PEAR/PackageFile.php
new file mode 100644 (file)
index 0000000..7ae3362
--- /dev/null
@@ -0,0 +1,492 @@
+<?php
+/**
+ * PEAR_PackageFile, package.xml parsing utility class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: PackageFile.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * needed for PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+/**
+ * Error code if the package.xml <package> tag does not contain a valid version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1);
+/**
+ * Error code if the package.xml <package> tag version is not supported (version 1.0 and 1.1 are the only supported versions,
+ * currently
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2);
+/**
+ * Abstraction for the package.xml package description file
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile
+{
+    /**
+     * @var PEAR_Config
+     */
+    var $_config;
+    var $_debug;
+
+    var $_logger = false;
+    /**
+     * @var boolean
+     */
+    var $_rawReturn = false;
+
+    /**
+     * helper for extracting Archive_Tar errors
+     * @var array
+     * @access private
+     */
+    var $_extractErrors = array();
+
+    /**
+     *
+     * @param   PEAR_Config $config
+     * @param   ?   $debug
+     * @param   string @tmpdir Optional temporary directory for uncompressing
+     *          files
+     */
+    function PEAR_PackageFile(&$config, $debug = false)
+    {
+        $this->_config = $config;
+        $this->_debug = $debug;
+    }
+
+    /**
+     * Turn off validation - return a parsed package.xml without checking it
+     *
+     * This is used by the package-validate command
+     */
+    function rawReturn()
+    {
+        $this->_rawReturn = true;
+    }
+
+    function setLogger(&$l)
+    {
+        $this->_logger = &$l;
+    }
+
+    /**
+     * Create a PEAR_PackageFile_Parser_v* of a given version.
+     * @param   int $version
+     * @return  PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1
+     */
+    function &parserFactory($version)
+    {
+        if (!in_array($version{0}, array('1', '2'))) {
+            $a = false;
+            return $a;
+        }
+
+        include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php';
+        $version = $version{0};
+        $class = "PEAR_PackageFile_Parser_v$version";
+        $a = new $class;
+        return $a;
+    }
+
+    /**
+     * For simpler unit-testing
+     * @return string
+     */
+    function getClassPrefix()
+    {
+        return 'PEAR_PackageFile_v';
+    }
+
+    /**
+     * Create a PEAR_PackageFile_v* of a given version.
+     * @param   int $version
+     * @return  PEAR_PackageFile_v1|PEAR_PackageFile_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;
+        }
+
+        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('/<package[^>]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) {
+            if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) {
+                return PEAR::raiseError('package.xml version "' . $packageversion[1] .
+                    '" is not supported, only 1.0, 2.0, and 2.1 are supported.');
+            }
+
+            $object = &$this->parserFactory($packageversion[1]);
+            if ($this->_logger) {
+                $object->setLogger($this->_logger);
+            }
+
+            $object->setConfig($this->_config);
+            $pf = $object->parse($data, $file, $archive);
+            if (PEAR::isError($pf)) {
+                return $pf;
+            }
+
+            if ($this->_rawReturn) {
+                return $pf;
+            }
+
+            if (!$pf->validate($state)) {;
+                if ($this->_config->get('verbose') > 0
+                    && $this->_logger && $pf->getValidationWarnings(false)
+                ) {
+                    foreach ($pf->getValidationWarnings(false) as $warning) {
+                        $this->_logger->log(0, 'ERROR: ' . $warning['message']);
+                    }
+                }
+
+                $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
+                    2, null, null, $pf->getValidationWarnings());
+                return $a;
+            }
+
+            if ($this->_logger && $pf->getValidationWarnings(false)) {
+                foreach ($pf->getValidationWarnings() as $warning) {
+                    $this->_logger->log(0, 'WARNING: ' . $warning['message']);
+                }
+            }
+
+            if (method_exists($pf, 'flattenFilelist')) {
+                $pf->flattenFilelist(); // for v2
+            }
+
+            return $pf;
+        } elseif (preg_match('/<package[^>]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) {
+            $a = PEAR::raiseError('package.xml file "' . $file .
+                '" has unsupported package.xml <package> version "' . $packageversion[1] . '"');
+            return $a;
+        } else {
+            if (!class_exists('PEAR_ErrorStack')) {
+                require_once 'PEAR/ErrorStack.php';
+            }
+
+            PEAR_ErrorStack::staticPush('PEAR_PackageFile',
+                PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION,
+                'warning', array('xml' => $data), 'package.xml "' . $file .
+                    '" has no package.xml <package> version');
+            $object = &$this->parserFactory('1.0');
+            $object->setConfig($this->_config);
+            $pf = $object->parse($data, $file, $archive);
+            if (PEAR::isError($pf)) {
+                return $pf;
+            }
+
+            if ($this->_rawReturn) {
+                return $pf;
+            }
+
+            if (!$pf->validate($state)) {
+                $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
+                    2, null, null, $pf->getValidationWarnings());
+                return $a;
+            }
+
+            if ($this->_logger && $pf->getValidationWarnings(false)) {
+                foreach ($pf->getValidationWarnings() as $warning) {
+                    $this->_logger->log(0, 'WARNING: ' . $warning['message']);
+                }
+            }
+
+            if (method_exists($pf, 'flattenFilelist')) {
+                $pf->flattenFilelist(); // for v2
+            }
+
+            return $pf;
+        }
+    }
+
+    /**
+     * Register a temporary file or directory.  When the destructor is
+     * executed, all registered temporary files and directories are
+     * removed.
+     *
+     * @param string  $file  name of file or directory
+     * @return  void
+     */
+    function addTempFile($file)
+    {
+        $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
+    }
+
+    /**
+     * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file.
+     * @access  public
+     * @param string contents of package.xml file
+     * @param int package state (one of PEAR_VALIDATE_* constants)
+     * @return  PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @using   Archive_Tar to extract the files
+     * @using   fromPackageFile() to load the package after the package.xml
+     *          file is extracted.
+     */
+    function &fromTgzFile($file, $state)
+    {
+        if (!class_exists('Archive_Tar')) {
+            require_once 'Archive/Tar.php';
+        }
+
+        $tar = new Archive_Tar($file);
+        if ($this->_debug <= 1) {
+            $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+        }
+
+        $content = $tar->listContent();
+        if ($this->_debug <= 1) {
+            $tar->popErrorHandling();
+        }
+
+        if (!is_array($content)) {
+            if (is_string($file) && strlen($file < 255) &&
+                  (!file_exists($file) || !@is_file($file))) {
+                $ret = PEAR::raiseError("could not open file \"$file\"");
+                return $ret;
+            }
+
+            $file = realpath($file);
+            $ret = PEAR::raiseError("Could not get contents of package \"$file\"".
+                                     '. Invalid tgz file.');
+            return $ret;
+        }
+
+        if (!count($content) && !@is_file($file)) {
+            $ret = PEAR::raiseError("could not open file \"$file\"");
+            return $ret;
+        }
+
+        $xml      = null;
+        $origfile = $file;
+        foreach ($content as $file) {
+            $name = $file['filename'];
+            if ($name == 'package2.xml') { // allow a .tgz to distribute both versions
+                $xml = $name;
+                break;
+            }
+
+            if ($name == 'package.xml') {
+                $xml = $name;
+                break;
+            } elseif (preg_match('/package.xml$/', $name, $match)) {
+                $xml = $name;
+                break;
+            }
+        }
+
+        $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear');
+        if ($tmpdir === false) {
+            $ret = PEAR::raiseError("there was a problem with getting the configured temp directory");
+            return $ret;
+        }
+
+        PEAR_PackageFile::addTempFile($tmpdir);
+
+        $this->_extractErrors();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors'));
+
+        if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
+            $extra = implode("\n", $this->_extractErrors());
+            if ($extra) {
+                $extra = ' ' . $extra;
+            }
+
+            PEAR::staticPopErrorHandling();
+            $ret = PEAR::raiseError('could not extract the package.xml file from "' .
+                $origfile . '"' . $extra);
+            return $ret;
+        }
+
+        PEAR::staticPopErrorHandling();
+        $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile);
+        return $ret;
+    }
+
+    /**
+     * helper callback for extracting Archive_Tar errors
+     *
+     * @param PEAR_Error|null $err
+     * @return array
+     * @access private
+     */
+    function _extractErrors($err = null)
+    {
+        static $errors = array();
+        if ($err === null) {
+            $e = $errors;
+            $errors = array();
+            return $e;
+        }
+        $errors[] = $err->getMessage();
+    }
+
+    /**
+     * Create a PEAR_PackageFile_v* from a package.xml file.
+     *
+     * @access public
+     * @param   string  $descfile  name of package xml file
+     * @param   int     $state package state (one of PEAR_VALIDATE_* constants)
+     * @param   string|false $archive name of the archive this package.xml came
+     *          from, if any
+     * @return  PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @uses    PEAR_PackageFile::fromXmlString to create the oject after the
+     *          XML is loaded from the package.xml file.
+     */
+    function &fromPackageFile($descfile, $state, $archive = false)
+    {
+        $fp = false;
+        if (is_string($descfile) && strlen($descfile) < 255 &&
+             (
+              !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile)
+              || (!$fp = @fopen($descfile, 'r'))
+             )
+        ) {
+            $a = PEAR::raiseError("Unable to open $descfile");
+            return $a;
+        }
+
+        // read the whole thing so we only get one cdata callback
+        // for each block of cdata
+        fclose($fp);
+        $data = file_get_contents($descfile);
+        $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive);
+        return $ret;
+    }
+
+    /**
+     * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file.
+     *
+     * This method is able to extract information about a package from a .tgz
+     * archive or from a XML package definition file.
+     *
+     * @access public
+     * @param   string  $info file name
+     * @param   int     $state package state (one of PEAR_VALIDATE_* constants)
+     * @return  PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @uses    fromPackageFile() if the file appears to be XML
+     * @uses    fromTgzFile() to load all non-XML files
+     */
+    function &fromAnyFile($info, $state)
+    {
+        if (is_dir($info)) {
+            $dir_name = realpath($info);
+            if (file_exists($dir_name . '/package.xml')) {
+                $info = PEAR_PackageFile::fromPackageFile($dir_name .  '/package.xml', $state);
+            } elseif (file_exists($dir_name .  '/package2.xml')) {
+                $info = PEAR_PackageFile::fromPackageFile($dir_name .  '/package2.xml', $state);
+            } else {
+                $info = PEAR::raiseError("No package definition found in '$info' directory");
+            }
+
+            return $info;
+        }
+
+        $fp = false;
+        if (is_string($info) && strlen($info) < 255 &&
+             (file_exists($info) || ($fp = @fopen($info, 'r')))
+        ) {
+
+            if ($fp) {
+                fclose($fp);
+            }
+
+            $tmp = substr($info, -4);
+            if ($tmp == '.xml') {
+                $info = &PEAR_PackageFile::fromPackageFile($info, $state);
+            } elseif ($tmp == '.tar' || $tmp == '.tgz') {
+                $info = &PEAR_PackageFile::fromTgzFile($info, $state);
+            } else {
+                $fp   = fopen($info, 'r');
+                $test = fread($fp, 5);
+                fclose($fp);
+                if ($test == '<?xml') {
+                    $info = &PEAR_PackageFile::fromPackageFile($info, $state);
+                } else {
+                    $info = &PEAR_PackageFile::fromTgzFile($info, $state);
+                }
+            }
+
+            return $info;
+        }
+
+        $info = PEAR::raiseError("Cannot open '$info' for parsing");
+        return $info;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php
new file mode 100644 (file)
index 0000000..2f42f17
--- /dev/null
@@ -0,0 +1,1284 @@
+<?php
+/**
+ * package.xml generation class, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * needed for PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+require_once 'System.php';
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * This class converts a PEAR_PackageFile_v1 object into any output format.
+ *
+ * Supported output formats include array, XML string, and a PEAR_PackageFile_v2
+ * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Generator_v1
+{
+    /**
+     * @var PEAR_PackageFile_v1
+     */
+    var $_packagefile;
+    function PEAR_PackageFile_Generator_v1(&$packagefile)
+    {
+        $this->_packagefile = &$packagefile;
+    }
+
+    function getPackagerVersion()
+    {
+        return '1.9.4';
+    }
+
+    /**
+     * @param PEAR_Packager
+     * @param bool if true, a .tgz is written, otherwise a .tar is written
+     * @param string|null directory in which to save the .tgz
+     * @return string|PEAR_Error location of package or error object
+     */
+    function toTgz(&$packager, $compress = true, $where = null)
+    {
+        require_once 'Archive/Tar.php';
+        if ($where === null) {
+            if (!($where = System::mktemp(array('-d')))) {
+                return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed');
+            }
+        } elseif (!@System::mkDir(array('-p', $where))) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' .
+                ' not be created');
+        }
+        if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') &&
+              !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' .
+                ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"');
+        }
+        if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file');
+        }
+        $pkginfo = $this->_packagefile->getArray();
+        $ext = $compress ? '.tgz' : '.tar';
+        $pkgver = $pkginfo['package'] . '-' . $pkginfo['version'];
+        $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
+        if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) &&
+              !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' .
+                getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"');
+        }
+        if ($pkgfile = $this->_packagefile->getPackageFile()) {
+            $pkgdir = dirname(realpath($pkgfile));
+            $pkgfile = basename($pkgfile);
+        } else {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' .
+                'be created from a real file');
+        }
+        // {{{ Create the package file list
+        $filelist = array();
+        $i = 0;
+
+        foreach ($this->_packagefile->getFilelist() as $fname => $atts) {
+            $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+            if (!file_exists($file)) {
+                return PEAR::raiseError("File does not exist: $fname");
+            } else {
+                $filelist[$i++] = $file;
+                if (!isset($atts['md5sum'])) {
+                    $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file));
+                }
+                $packager->log(2, "Adding file $fname");
+            }
+        }
+        // }}}
+        $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true);
+        if ($packagexml) {
+            $tar =& new Archive_Tar($dest_package, $compress);
+            $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
+            // ----- Creates with the package.xml file
+            $ok = $tar->createModify(array($packagexml), '', $where);
+            if (PEAR::isError($ok)) {
+                return $ok;
+            } elseif (!$ok) {
+                return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed');
+            }
+            // ----- Add the content of the package
+            if (!$tar->addModify($filelist, $pkgver, $pkgdir)) {
+                return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed');
+            }
+            return $dest_package;
+        }
+    }
+
+    /**
+     * @param string|null directory to place the package.xml in, or null for a temporary dir
+     * @param int one of the PEAR_VALIDATE_* constants
+     * @param string name of the generated file
+     * @param bool if true, then no analysis will be performed on role="php" files
+     * @return string|PEAR_Error path to the created file on success
+     */
+    function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml',
+                           $nofilechecking = false)
+    {
+        if (!$this->_packagefile->validate($state, $nofilechecking)) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml',
+                null, null, null, $this->_packagefile->getValidationWarnings());
+        }
+        if ($where === null) {
+            if (!($where = System::mktemp(array('-d')))) {
+                return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed');
+            }
+        } elseif (!@System::mkDir(array('-p', $where))) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' .
+                ' not be created');
+        }
+        $newpkgfile = $where . DIRECTORY_SEPARATOR . $name;
+        $np = @fopen($newpkgfile, 'wb');
+        if (!$np) {
+            return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' .
+               "$name as $newpkgfile");
+        }
+        fwrite($np, $this->toXml($state, true));
+        fclose($np);
+        return $newpkgfile;
+    }
+
+    /**
+     * fix both XML encoding to be UTF8, and replace standard XML entities < > " & '
+     *
+     * @param string $string
+     * @return string
+     * @access private
+     */
+    function _fixXmlEncoding($string)
+    {
+        if (version_compare(phpversion(), '5.0.0', 'lt')) {
+            $string = utf8_encode($string);
+        }
+        return strtr($string, array(
+                                          '&'  => '&amp;',
+                                          '>'  => '&gt;',
+                                          '<'  => '&lt;',
+                                          '"'  => '&quot;',
+                                          '\'' => '&apos;' ));
+    }
+
+    /**
+     * Return an XML document based on the package info (as returned
+     * by the PEAR_Common::infoFrom* methods).
+     *
+     * @return string XML data
+     */
+    function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false)
+    {
+        $this->_packagefile->setDate(date('Y-m-d'));
+        if (!$this->_packagefile->validate($state, $nofilevalidation)) {
+            return false;
+        }
+        $pkginfo = $this->_packagefile->getArray();
+        static $maint_map = array(
+            "handle" => "user",
+            "name" => "name",
+            "email" => "email",
+            "role" => "role",
+            );
+        $ret = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+        $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
+        $ret .= "<package version=\"1.0\" packagerversion=\"1.9.4\">\n" .
+" <name>$pkginfo[package]</name>";
+        if (isset($pkginfo['extends'])) {
+            $ret .= "\n<extends>$pkginfo[extends]</extends>";
+        }
+        $ret .=
+ "\n <summary>".$this->_fixXmlEncoding($pkginfo['summary'])."</summary>\n" .
+" <description>".trim($this->_fixXmlEncoding($pkginfo['description']))."\n </description>\n" .
+" <maintainers>\n";
+        foreach ($pkginfo['maintainers'] as $maint) {
+            $ret .= "  <maintainer>\n";
+            foreach ($maint_map as $idx => $elm) {
+                $ret .= "   <$elm>";
+                $ret .= $this->_fixXmlEncoding($maint[$idx]);
+                $ret .= "</$elm>\n";
+            }
+            $ret .= "  </maintainer>\n";
+        }
+        $ret .= "  </maintainers>\n";
+        $ret .= $this->_makeReleaseXml($pkginfo, false, $state);
+        if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) {
+            $ret .= " <changelog>\n";
+            foreach ($pkginfo['changelog'] as $oldrelease) {
+                $ret .= $this->_makeReleaseXml($oldrelease, true);
+            }
+            $ret .= " </changelog>\n";
+        }
+        $ret .= "</package>\n";
+        return $ret;
+    }
+
+    // }}}
+    // {{{ _makeReleaseXml()
+
+    /**
+     * Generate part of an XML description with release information.
+     *
+     * @param array  $pkginfo    array with release information
+     * @param bool   $changelog  whether the result will be in a changelog element
+     *
+     * @return string XML data
+     *
+     * @access private
+     */
+    function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL)
+    {
+        // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
+        $indent = $changelog ? "  " : "";
+        $ret = "$indent <release>\n";
+        if (!empty($pkginfo['version'])) {
+            $ret .= "$indent  <version>$pkginfo[version]</version>\n";
+        }
+        if (!empty($pkginfo['release_date'])) {
+            $ret .= "$indent  <date>$pkginfo[release_date]</date>\n";
+        }
+        if (!empty($pkginfo['release_license'])) {
+            $ret .= "$indent  <license>$pkginfo[release_license]</license>\n";
+        }
+        if (!empty($pkginfo['release_state'])) {
+            $ret .= "$indent  <state>$pkginfo[release_state]</state>\n";
+        }
+        if (!empty($pkginfo['release_notes'])) {
+            $ret .= "$indent  <notes>".trim($this->_fixXmlEncoding($pkginfo['release_notes']))
+            ."\n$indent  </notes>\n";
+        }
+        if (!empty($pkginfo['release_warnings'])) {
+            $ret .= "$indent  <warnings>".$this->_fixXmlEncoding($pkginfo['release_warnings'])."</warnings>\n";
+        }
+        if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
+            $ret .= "$indent  <deps>\n";
+            foreach ($pkginfo['release_deps'] as $dep) {
+                $ret .= "$indent   <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
+                if (isset($dep['version'])) {
+                    $ret .= " version=\"$dep[version]\"";
+                }
+                if (isset($dep['optional'])) {
+                    $ret .= " optional=\"$dep[optional]\"";
+                }
+                if (isset($dep['name'])) {
+                    $ret .= ">$dep[name]</dep>\n";
+                } else {
+                    $ret .= "/>\n";
+                }
+            }
+            $ret .= "$indent  </deps>\n";
+        }
+        if (isset($pkginfo['configure_options'])) {
+            $ret .= "$indent  <configureoptions>\n";
+            foreach ($pkginfo['configure_options'] as $c) {
+                $ret .= "$indent   <configureoption name=\"".
+                    $this->_fixXmlEncoding($c['name']) . "\"";
+                if (isset($c['default'])) {
+                    $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\"";
+                }
+                $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\"";
+                $ret .= "/>\n";
+            }
+            $ret .= "$indent  </configureoptions>\n";
+        }
+        if (isset($pkginfo['provides'])) {
+            foreach ($pkginfo['provides'] as $key => $what) {
+                $ret .= "$indent  <provides type=\"$what[type]\" ";
+                $ret .= "name=\"$what[name]\" ";
+                if (isset($what['extends'])) {
+                    $ret .= "extends=\"$what[extends]\" ";
+                }
+                $ret .= "/>\n";
+            }
+        }
+        if (isset($pkginfo['filelist'])) {
+            $ret .= "$indent  <filelist>\n";
+            if ($state ^ PEAR_VALIDATE_PACKAGING) {
+                $ret .= $this->recursiveXmlFilelist($pkginfo['filelist']);
+            } else {
+                foreach ($pkginfo['filelist'] as $file => $fa) {
+                    if (!isset($fa['role'])) {
+                        $fa['role'] = '';
+                    }
+                    $ret .= "$indent   <file role=\"$fa[role]\"";
+                    if (isset($fa['baseinstalldir'])) {
+                        $ret .= ' baseinstalldir="' .
+                            $this->_fixXmlEncoding($fa['baseinstalldir']) . '"';
+                    }
+                    if (isset($fa['md5sum'])) {
+                        $ret .= " md5sum=\"$fa[md5sum]\"";
+                    }
+                    if (isset($fa['platform'])) {
+                        $ret .= " platform=\"$fa[platform]\"";
+                    }
+                    if (!empty($fa['install-as'])) {
+                        $ret .= ' install-as="' .
+                            $this->_fixXmlEncoding($fa['install-as']) . '"';
+                    }
+                    $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"';
+                    if (empty($fa['replacements'])) {
+                        $ret .= "/>\n";
+                    } else {
+                        $ret .= ">\n";
+                        foreach ($fa['replacements'] as $r) {
+                            $ret .= "$indent    <replace";
+                            foreach ($r as $k => $v) {
+                                $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+                            }
+                            $ret .= "/>\n";
+                        }
+                        $ret .= "$indent   </file>\n";
+                    }
+                }
+            }
+            $ret .= "$indent  </filelist>\n";
+        }
+        $ret .= "$indent </release>\n";
+        return $ret;
+    }
+
+    /**
+     * @param array
+     * @access protected
+     */
+    function recursiveXmlFilelist($list)
+    {
+        $this->_dirs = array();
+        foreach ($list as $file => $attributes) {
+            $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes);
+        }
+        return $this->_formatDir($this->_dirs);
+    }
+
+    /**
+     * @param array
+     * @param array
+     * @param string|null
+     * @param array|null
+     * @access private
+     */
+    function _addDir(&$dirs, $dir, $file = null, $attributes = null)
+    {
+        if ($dir == array() || $dir == array('.')) {
+            $dirs['files'][basename($file)] = $attributes;
+            return;
+        }
+        $curdir = array_shift($dir);
+        if (!isset($dirs['dirs'][$curdir])) {
+            $dirs['dirs'][$curdir] = array();
+        }
+        $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes);
+    }
+
+    /**
+     * @param array
+     * @param string
+     * @param string
+     * @access private
+     */
+    function _formatDir($dirs, $indent = '', $curdir = '')
+    {
+        $ret = '';
+        if (!count($dirs)) {
+            return '';
+        }
+        if (isset($dirs['dirs'])) {
+            uksort($dirs['dirs'], 'strnatcasecmp');
+            foreach ($dirs['dirs'] as $dir => $contents) {
+                $usedir = "$curdir/$dir";
+                $ret .= "$indent   <dir name=\"$dir\">\n";
+                $ret .= $this->_formatDir($contents, "$indent ", $usedir);
+                $ret .= "$indent   </dir> <!-- $usedir -->\n";
+            }
+        }
+        if (isset($dirs['files'])) {
+            uksort($dirs['files'], 'strnatcasecmp');
+            foreach ($dirs['files'] as $file => $attribs) {
+                $ret .= $this->_formatFile($file, $attribs, $indent);
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * @param string
+     * @param array
+     * @param string
+     * @access private
+     */
+    function _formatFile($file, $attributes, $indent)
+    {
+        $ret = "$indent   <file role=\"$attributes[role]\"";
+        if (isset($attributes['baseinstalldir'])) {
+            $ret .= ' baseinstalldir="' .
+                $this->_fixXmlEncoding($attributes['baseinstalldir']) . '"';
+        }
+        if (isset($attributes['md5sum'])) {
+            $ret .= " md5sum=\"$attributes[md5sum]\"";
+        }
+        if (isset($attributes['platform'])) {
+            $ret .= " platform=\"$attributes[platform]\"";
+        }
+        if (!empty($attributes['install-as'])) {
+            $ret .= ' install-as="' .
+                $this->_fixXmlEncoding($attributes['install-as']) . '"';
+        }
+        $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"';
+        if (empty($attributes['replacements'])) {
+            $ret .= "/>\n";
+        } else {
+            $ret .= ">\n";
+            foreach ($attributes['replacements'] as $r) {
+                $ret .= "$indent    <replace";
+                foreach ($r as $k => $v) {
+                    $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+                }
+                $ret .= "/>\n";
+            }
+            $ret .= "$indent   </file>\n";
+        }
+        return $ret;
+    }
+
+    // {{{ _unIndent()
+
+    /**
+     * Unindent given string (?)
+     *
+     * @param string $str The string that has to be unindented.
+     * @return string
+     * @access private
+     */
+    function _unIndent($str)
+    {
+        // remove leading newlines
+        $str = preg_replace('/^[\r\n]+/', '', $str);
+        // find whitespace at the beginning of the first line
+        $indent_len = strspn($str, " \t");
+        $indent = substr($str, 0, $indent_len);
+        $data = '';
+        // remove the same amount of whitespace from following lines
+        foreach (explode("\n", $str) as $line) {
+            if (substr($line, 0, $indent_len) == $indent) {
+                $data .= substr($line, $indent_len) . "\n";
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * @return array
+     */
+    function dependenciesToV2()
+    {
+        $arr = array();
+        $this->_convertDependencies2_0($arr);
+        return $arr['dependencies'];
+    }
+
+    /**
+     * Convert a package.xml version 1.0 into version 2.0
+     *
+     * Note that this does a basic conversion, to allow more advanced
+     * features like bundles and multiple releases
+     * @param string the classname to instantiate and return.  This must be
+     *               PEAR_PackageFile_v2 or a descendant
+     * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the
+     *                strictest parameters will be converted
+     * @return PEAR_PackageFile_v2|PEAR_Error
+     */
+    function &toV2($class = 'PEAR_PackageFile_v2', $strict = false)
+    {
+        if ($strict) {
+            if (!$this->_packagefile->validate()) {
+                $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' .
+                    ' to version 2.0', null, null, null,
+                    $this->_packagefile->getValidationWarnings(true));
+                return $a;
+            }
+        }
+
+        $arr = array(
+            'attribs' => array(
+                             'version' => '2.0',
+                             'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+                             'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+                             'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+                             'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" .
+"http://pear.php.net/dtd/tasks-1.0.xsd\n" .
+"http://pear.php.net/dtd/package-2.0\n" .
+'http://pear.php.net/dtd/package-2.0.xsd',
+                         ),
+            'name' => $this->_packagefile->getPackage(),
+            'channel' => 'pear.php.net',
+        );
+        $arr['summary'] = $this->_packagefile->getSummary();
+        $arr['description'] = $this->_packagefile->getDescription();
+        $maintainers = $this->_packagefile->getMaintainers();
+        foreach ($maintainers as $maintainer) {
+            if ($maintainer['role'] != 'lead') {
+                continue;
+            }
+            $new = array(
+                'name' => $maintainer['name'],
+                'user' => $maintainer['handle'],
+                'email' => $maintainer['email'],
+                'active' => 'yes',
+            );
+            $arr['lead'][] = $new;
+        }
+
+        if (!isset($arr['lead'])) { // some people... you know?
+            $arr['lead'] = array(
+                'name' => 'unknown',
+                'user' => 'unknown',
+                'email' => 'noleadmaintainer@example.com',
+                'active' => 'no',
+            );
+        }
+
+        if (count($arr['lead']) == 1) {
+            $arr['lead'] = $arr['lead'][0];
+        }
+
+        foreach ($maintainers as $maintainer) {
+            if ($maintainer['role'] == 'lead') {
+                continue;
+            }
+            $new = array(
+                'name' => $maintainer['name'],
+                'user' => $maintainer['handle'],
+                'email' => $maintainer['email'],
+                'active' => 'yes',
+            );
+            $arr[$maintainer['role']][] = $new;
+        }
+
+        if (isset($arr['developer']) && count($arr['developer']) == 1) {
+            $arr['developer'] = $arr['developer'][0];
+        }
+
+        if (isset($arr['contributor']) && count($arr['contributor']) == 1) {
+            $arr['contributor'] = $arr['contributor'][0];
+        }
+
+        if (isset($arr['helper']) && count($arr['helper']) == 1) {
+            $arr['helper'] = $arr['helper'][0];
+        }
+
+        $arr['date'] = $this->_packagefile->getDate();
+        $arr['version'] =
+            array(
+                'release' => $this->_packagefile->getVersion(),
+                'api' => $this->_packagefile->getVersion(),
+            );
+        $arr['stability'] =
+            array(
+                'release' => $this->_packagefile->getState(),
+                'api' => $this->_packagefile->getState(),
+            );
+        $licensemap =
+            array(
+                'php' => 'http://www.php.net/license',
+                'php license' => 'http://www.php.net/license',
+                'lgpl' => 'http://www.gnu.org/copyleft/lesser.html',
+                'bsd' => 'http://www.opensource.org/licenses/bsd-license.php',
+                'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php',
+                'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php',
+                'mit' => 'http://www.opensource.org/licenses/mit-license.php',
+                'gpl' => 'http://www.gnu.org/copyleft/gpl.html',
+                'apache' => 'http://www.opensource.org/licenses/apache2.0.php'
+            );
+
+        if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) {
+            $arr['license'] = array(
+                'attribs' => array('uri' =>
+                    $licensemap[strtolower($this->_packagefile->getLicense())]),
+                '_content' => $this->_packagefile->getLicense()
+                );
+        } else {
+            // don't use bogus uri
+            $arr['license'] = $this->_packagefile->getLicense();
+        }
+
+        $arr['notes'] = $this->_packagefile->getNotes();
+        $temp = array();
+        $arr['contents'] = $this->_convertFilelist2_0($temp);
+        $this->_convertDependencies2_0($arr);
+        $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ?
+            'extsrcrelease' : 'phprelease';
+        if ($release == 'extsrcrelease') {
+            $arr['channel'] = 'pecl.php.net';
+            $arr['providesextension'] = $arr['name']; // assumption
+        }
+
+        $arr[$release] = array();
+        if ($this->_packagefile->getConfigureOptions()) {
+            $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions();
+            foreach ($arr[$release]['configureoption'] as $i => $opt) {
+                $arr[$release]['configureoption'][$i] = array('attribs' => $opt);
+            }
+            if (count($arr[$release]['configureoption']) == 1) {
+                $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0];
+            }
+        }
+
+        $this->_convertRelease2_0($arr[$release], $temp);
+        if ($release == 'extsrcrelease' && count($arr[$release]) > 1) {
+            // multiple extsrcrelease tags added in PEAR 1.4.1
+            $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1';
+        }
+
+        if ($cl = $this->_packagefile->getChangelog()) {
+            foreach ($cl as $release) {
+                $rel = array();
+                $rel['version'] =
+                    array(
+                        'release' => $release['version'],
+                        'api' => $release['version'],
+                    );
+                if (!isset($release['release_state'])) {
+                    $release['release_state'] = 'stable';
+                }
+
+                $rel['stability'] =
+                    array(
+                        'release' => $release['release_state'],
+                        'api' => $release['release_state'],
+                    );
+                if (isset($release['release_date'])) {
+                    $rel['date'] = $release['release_date'];
+                } else {
+                    $rel['date'] = date('Y-m-d');
+                }
+
+                if (isset($release['release_license'])) {
+                    if (isset($licensemap[strtolower($release['release_license'])])) {
+                        $uri = $licensemap[strtolower($release['release_license'])];
+                    } else {
+                        $uri = 'http://www.example.com';
+                    }
+                    $rel['license'] = array(
+                            'attribs' => array('uri' => $uri),
+                            '_content' => $release['release_license']
+                        );
+                } else {
+                    $rel['license'] = $arr['license'];
+                }
+
+                if (!isset($release['release_notes'])) {
+                    $release['release_notes'] = 'no release notes';
+                }
+
+                $rel['notes'] = $release['release_notes'];
+                $arr['changelog']['release'][] = $rel;
+            }
+        }
+
+        $ret = new $class;
+        $ret->setConfig($this->_packagefile->_config);
+        if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) {
+            $ret->setLogger($this->_packagefile->_logger);
+        }
+
+        $ret->fromArray($arr);
+        return $ret;
+    }
+
+    /**
+     * @param array
+     * @param bool
+     * @access private
+     */
+    function _convertDependencies2_0(&$release, $internal = false)
+    {
+        $peardep = array('pearinstaller' =>
+            array('min' => '1.4.0b1')); // this is a lot safer
+        $required = $optional = array();
+        $release['dependencies'] = array('required' => array());
+        if ($this->_packagefile->hasDeps()) {
+            foreach ($this->_packagefile->getDeps() as $dep) {
+                if (!isset($dep['optional']) || $dep['optional'] == 'no') {
+                    $required[] = $dep;
+                } else {
+                    $optional[] = $dep;
+                }
+            }
+            foreach (array('required', 'optional') as $arr) {
+                $deps = array();
+                foreach ($$arr as $dep) {
+                    // organize deps by dependency type and name
+                    if (!isset($deps[$dep['type']])) {
+                        $deps[$dep['type']] = array();
+                    }
+                    if (isset($dep['name'])) {
+                        $deps[$dep['type']][$dep['name']][] = $dep;
+                    } else {
+                        $deps[$dep['type']][] = $dep;
+                    }
+                }
+                do {
+                    if (isset($deps['php'])) {
+                        $php = array();
+                        if (count($deps['php']) > 1) {
+                            $php = $this->_processPhpDeps($deps['php']);
+                        } else {
+                            if (!isset($deps['php'][0])) {
+                                list($key, $blah) = each ($deps['php']); // stupid buggy versions
+                                $deps['php'] = array($blah[0]);
+                            }
+                            $php = $this->_processDep($deps['php'][0]);
+                            if (!$php) {
+                                break; // poor mans throw
+                            }
+                        }
+                        $release['dependencies'][$arr]['php'] = $php;
+                    }
+                } while (false);
+                do {
+                    if (isset($deps['pkg'])) {
+                        $pkg = array();
+                        $pkg = $this->_processMultipleDepsName($deps['pkg']);
+                        if (!$pkg) {
+                            break; // poor mans throw
+                        }
+                        $release['dependencies'][$arr]['package'] = $pkg;
+                    }
+                } while (false);
+                do {
+                    if (isset($deps['ext'])) {
+                        $pkg = array();
+                        $pkg = $this->_processMultipleDepsName($deps['ext']);
+                        $release['dependencies'][$arr]['extension'] = $pkg;
+                    }
+                } while (false);
+                // skip sapi - it's not supported so nobody will have used it
+                // skip os - it's not supported in 1.0
+            }
+        }
+        if (isset($release['dependencies']['required'])) {
+            $release['dependencies']['required'] =
+                array_merge($peardep, $release['dependencies']['required']);
+        } else {
+            $release['dependencies']['required'] = $peardep;
+        }
+        if (!isset($release['dependencies']['required']['php'])) {
+            $release['dependencies']['required']['php'] =
+                array('min' => '4.0.0');
+        }
+        $order = array();
+        $bewm = $release['dependencies']['required'];
+        $order['php'] = $bewm['php'];
+        $order['pearinstaller'] = $bewm['pearinstaller'];
+        isset($bewm['package']) ? $order['package'] = $bewm['package'] :0;
+        isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0;
+        $release['dependencies']['required'] = $order;
+    }
+
+    /**
+     * @param array
+     * @access private
+     */
+    function _convertFilelist2_0(&$package)
+    {
+        $ret = array('dir' =>
+                    array(
+                        'attribs' => array('name' => '/'),
+                        'file' => array()
+                        )
+                    );
+        $package['platform'] =
+        $package['install-as'] = array();
+        $this->_isExtension = false;
+        foreach ($this->_packagefile->getFilelist() as $name => $file) {
+            $file['name'] = $name;
+            if (isset($file['role']) && $file['role'] == 'src') {
+                $this->_isExtension = true;
+            }
+            if (isset($file['replacements'])) {
+                $repl = $file['replacements'];
+                unset($file['replacements']);
+            } else {
+                unset($repl);
+            }
+            if (isset($file['install-as'])) {
+                $package['install-as'][$name] = $file['install-as'];
+                unset($file['install-as']);
+            }
+            if (isset($file['platform'])) {
+                $package['platform'][$name] = $file['platform'];
+                unset($file['platform']);
+            }
+            $file = array('attribs' => $file);
+            if (isset($repl)) {
+                foreach ($repl as $replace ) {
+                    $file['tasks:replace'][] = array('attribs' => $replace);
+                }
+                if (count($repl) == 1) {
+                    $file['tasks:replace'] = $file['tasks:replace'][0];
+                }
+            }
+            $ret['dir']['file'][] = $file;
+        }
+        return $ret;
+    }
+
+    /**
+     * Post-process special files with install-as/platform attributes and
+     * make the release tag.
+     *
+     * This complex method follows this work-flow to create the release tags:
+     *
+     * <pre>
+     * - if any install-as/platform exist, create a generic release and fill it with
+     *   o <install as=..> tags for <file name=... install-as=...>
+     *   o <install as=..> tags for <file name=... platform=!... install-as=..>
+     *   o <ignore> tags for <file name=... platform=...>
+     *   o <ignore> tags for <file name=... platform=... install-as=..>
+     * - create a release for each platform encountered and fill with
+     *   o <install as..> tags for <file name=... install-as=...>
+     *   o <install as..> tags for <file name=... platform=this platform install-as=..>
+     *   o <install as..> tags for <file name=... platform=!other platform install-as=..>
+     *   o <ignore> tags for <file name=... platform=!this platform>
+     *   o <ignore> tags for <file name=... platform=other platform>
+     *   o <ignore> tags for <file name=... platform=other platform install-as=..>
+     *   o <ignore> tags for <file name=... platform=!this platform install-as=..>
+     * </pre>
+     *
+     * It does this by accessing the $package parameter, which contains an array with
+     * indices:
+     *
+     *  - platform: mapping of file => OS the file should be installed on
+     *  - install-as: mapping of file => installed name
+     *  - osmap: mapping of OS => list of files that should be installed
+     *    on that OS
+     *  - notosmap: mapping of OS => list of files that should not be
+     *    installed on that OS
+     *
+     * @param array
+     * @param array
+     * @access private
+     */
+    function _convertRelease2_0(&$release, $package)
+    {
+        //- if any install-as/platform exist, create a generic release and fill it with
+        if (count($package['platform']) || count($package['install-as'])) {
+            $generic = array();
+            $genericIgnore = array();
+            foreach ($package['install-as'] as $file => $as) {
+                //o <install as=..> tags for <file name=... install-as=...>
+                if (!isset($package['platform'][$file])) {
+                    $generic[] = $file;
+                    continue;
+                }
+                //o <install as=..> tags for <file name=... platform=!... install-as=..>
+                if (isset($package['platform'][$file]) &&
+                      $package['platform'][$file]{0} == '!') {
+                    $generic[] = $file;
+                    continue;
+                }
+                //o <ignore> tags for <file name=... platform=... install-as=..>
+                if (isset($package['platform'][$file]) &&
+                      $package['platform'][$file]{0} != '!') {
+                    $genericIgnore[] = $file;
+                    continue;
+                }
+            }
+            foreach ($package['platform'] as $file => $platform) {
+                if (isset($package['install-as'][$file])) {
+                    continue;
+                }
+                if ($platform{0} != '!') {
+                    //o <ignore> tags for <file name=... platform=...>
+                    $genericIgnore[] = $file;
+                }
+            }
+            if (count($package['platform'])) {
+                $oses = $notplatform = $platform = array();
+                foreach ($package['platform'] as $file => $os) {
+                    // get a list of oses
+                    if ($os{0} == '!') {
+                        if (isset($oses[substr($os, 1)])) {
+                            continue;
+                        }
+                        $oses[substr($os, 1)] = count($oses);
+                    } else {
+                        if (isset($oses[$os])) {
+                            continue;
+                        }
+                        $oses[$os] = count($oses);
+                    }
+                }
+                //- create a release for each platform encountered and fill with
+                foreach ($oses as $os => $releaseNum) {
+                    $release[$releaseNum]['installconditions']['os']['name'] = $os;
+                    $release[$releaseNum]['filelist'] = array('install' => array(),
+                        'ignore' => array());
+                    foreach ($package['install-as'] as $file => $as) {
+                        //o <install as=..> tags for <file name=... install-as=...>
+                        if (!isset($package['platform'][$file])) {
+                            $release[$releaseNum]['filelist']['install'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                        'as' => $as,
+                                    ),
+                                );
+                            continue;
+                        }
+                        //o <install as..> tags for
+                        //  <file name=... platform=this platform install-as=..>
+                        if (isset($package['platform'][$file]) &&
+                              $package['platform'][$file] == $os) {
+                            $release[$releaseNum]['filelist']['install'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                        'as' => $as,
+                                    ),
+                                );
+                            continue;
+                        }
+                        //o <install as..> tags for
+                        //  <file name=... platform=!other platform install-as=..>
+                        if (isset($package['platform'][$file]) &&
+                              $package['platform'][$file] != "!$os" &&
+                              $package['platform'][$file]{0} == '!') {
+                            $release[$releaseNum]['filelist']['install'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                        'as' => $as,
+                                    ),
+                                );
+                            continue;
+                        }
+                        //o <ignore> tags for
+                        //  <file name=... platform=!this platform install-as=..>
+                        if (isset($package['platform'][$file]) &&
+                              $package['platform'][$file] == "!$os") {
+                            $release[$releaseNum]['filelist']['ignore'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                    ),
+                                );
+                            continue;
+                        }
+                        //o <ignore> tags for
+                        //  <file name=... platform=other platform install-as=..>
+                        if (isset($package['platform'][$file]) &&
+                              $package['platform'][$file]{0} != '!' &&
+                              $package['platform'][$file] != $os) {
+                            $release[$releaseNum]['filelist']['ignore'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                    ),
+                                );
+                            continue;
+                        }
+                    }
+                    foreach ($package['platform'] as $file => $platform) {
+                        if (isset($package['install-as'][$file])) {
+                            continue;
+                        }
+                        //o <ignore> tags for <file name=... platform=!this platform>
+                        if ($platform == "!$os") {
+                            $release[$releaseNum]['filelist']['ignore'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                    ),
+                                );
+                            continue;
+                        }
+                        //o <ignore> tags for <file name=... platform=other platform>
+                        if ($platform{0} != '!' && $platform != $os) {
+                            $release[$releaseNum]['filelist']['ignore'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                    ),
+                                );
+                        }
+                    }
+                    if (!count($release[$releaseNum]['filelist']['install'])) {
+                        unset($release[$releaseNum]['filelist']['install']);
+                    }
+                    if (!count($release[$releaseNum]['filelist']['ignore'])) {
+                        unset($release[$releaseNum]['filelist']['ignore']);
+                    }
+                }
+                if (count($generic) || count($genericIgnore)) {
+                    $release[count($oses)] = array();
+                    if (count($generic)) {
+                        foreach ($generic as $file) {
+                            if (isset($package['install-as'][$file])) {
+                                $installas = $package['install-as'][$file];
+                            } else {
+                                $installas = $file;
+                            }
+                            $release[count($oses)]['filelist']['install'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                        'as' => $installas,
+                                    )
+                                );
+                        }
+                    }
+                    if (count($genericIgnore)) {
+                        foreach ($genericIgnore as $file) {
+                            $release[count($oses)]['filelist']['ignore'][] =
+                                array(
+                                    'attribs' => array(
+                                        'name' => $file,
+                                    )
+                                );
+                        }
+                    }
+                }
+                // cleanup
+                foreach ($release as $i => $rel) {
+                    if (isset($rel['filelist']['install']) &&
+                          count($rel['filelist']['install']) == 1) {
+                        $release[$i]['filelist']['install'] =
+                            $release[$i]['filelist']['install'][0];
+                    }
+                    if (isset($rel['filelist']['ignore']) &&
+                          count($rel['filelist']['ignore']) == 1) {
+                        $release[$i]['filelist']['ignore'] =
+                            $release[$i]['filelist']['ignore'][0];
+                    }
+                }
+                if (count($release) == 1) {
+                    $release = $release[0];
+                }
+            } else {
+                // no platform atts, but some install-as atts
+                foreach ($package['install-as'] as $file => $value) {
+                    $release['filelist']['install'][] =
+                        array(
+                            'attribs' => array(
+                                'name' => $file,
+                                'as' => $value
+                            )
+                        );
+                }
+                if (count($release['filelist']['install']) == 1) {
+                    $release['filelist']['install'] = $release['filelist']['install'][0];
+                }
+            }
+        }
+    }
+
+    /**
+     * @param array
+     * @return array
+     * @access private
+     */
+    function _processDep($dep)
+    {
+        if ($dep['type'] == 'php') {
+            if ($dep['rel'] == 'has') {
+                // come on - everyone has php!
+                return false;
+            }
+        }
+        $php = array();
+        if ($dep['type'] != 'php') {
+            $php['name'] = $dep['name'];
+            if ($dep['type'] == 'pkg') {
+                $php['channel'] = 'pear.php.net';
+            }
+        }
+        switch ($dep['rel']) {
+            case 'gt' :
+                $php['min'] = $dep['version'];
+                $php['exclude'] = $dep['version'];
+            break;
+            case 'ge' :
+                if (!isset($dep['version'])) {
+                    if ($dep['type'] == 'php') {
+                        if (isset($dep['name'])) {
+                            $dep['version'] = $dep['name'];
+                        }
+                    }
+                }
+                $php['min'] = $dep['version'];
+            break;
+            case 'lt' :
+                $php['max'] = $dep['version'];
+                $php['exclude'] = $dep['version'];
+            break;
+            case 'le' :
+                $php['max'] = $dep['version'];
+            break;
+            case 'eq' :
+                $php['min'] = $dep['version'];
+                $php['max'] = $dep['version'];
+            break;
+            case 'ne' :
+                $php['exclude'] = $dep['version'];
+            break;
+            case 'not' :
+                $php['conflicts'] = 'yes';
+            break;
+        }
+        return $php;
+    }
+
+    /**
+     * @param array
+     * @return array
+     */
+    function _processPhpDeps($deps)
+    {
+        $test = array();
+        foreach ($deps as $dep) {
+            $test[] = $this->_processDep($dep);
+        }
+        $min = array();
+        $max = array();
+        foreach ($test as $dep) {
+            if (!$dep) {
+                continue;
+            }
+            if (isset($dep['min'])) {
+                $min[$dep['min']] = count($min);
+            }
+            if (isset($dep['max'])) {
+                $max[$dep['max']] = count($max);
+            }
+        }
+        if (count($min) > 0) {
+            uksort($min, 'version_compare');
+        }
+        if (count($max) > 0) {
+            uksort($max, 'version_compare');
+        }
+        if (count($min)) {
+            // get the highest minimum
+            $min = array_pop($a = array_flip($min));
+        } else {
+            $min = false;
+        }
+        if (count($max)) {
+            // get the lowest maximum
+            $max = array_shift($a = array_flip($max));
+        } else {
+            $max = false;
+        }
+        if ($min) {
+            $php['min'] = $min;
+        }
+        if ($max) {
+            $php['max'] = $max;
+        }
+        $exclude = array();
+        foreach ($test as $dep) {
+            if (!isset($dep['exclude'])) {
+                continue;
+            }
+            $exclude[] = $dep['exclude'];
+        }
+        if (count($exclude)) {
+            $php['exclude'] = $exclude;
+        }
+        return $php;
+    }
+
+    /**
+     * process multiple dependencies that have a name, like package deps
+     * @param array
+     * @return array
+     * @access private
+     */
+    function _processMultipleDepsName($deps)
+    {
+        $ret = $tests = array();
+        foreach ($deps as $name => $dep) {
+            foreach ($dep as $d) {
+                $tests[$name][] = $this->_processDep($d);
+            }
+        }
+
+        foreach ($tests as $name => $test) {
+            $max = $min = $php = array();
+            $php['name'] = $name;
+            foreach ($test as $dep) {
+                if (!$dep) {
+                    continue;
+                }
+                if (isset($dep['channel'])) {
+                    $php['channel'] = 'pear.php.net';
+                }
+                if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') {
+                    $php['conflicts'] = 'yes';
+                }
+                if (isset($dep['min'])) {
+                    $min[$dep['min']] = count($min);
+                }
+                if (isset($dep['max'])) {
+                    $max[$dep['max']] = count($max);
+                }
+            }
+            if (count($min) > 0) {
+                uksort($min, 'version_compare');
+            }
+            if (count($max) > 0) {
+                uksort($max, 'version_compare');
+            }
+            if (count($min)) {
+                // get the highest minimum
+                $min = array_pop($a = array_flip($min));
+            } else {
+                $min = false;
+            }
+            if (count($max)) {
+                // get the lowest maximum
+                $max = array_shift($a = array_flip($max));
+            } else {
+                $max = false;
+            }
+            if ($min) {
+                $php['min'] = $min;
+            }
+            if ($max) {
+                $php['max'] = $max;
+            }
+            $exclude = array();
+            foreach ($test as $dep) {
+                if (!isset($dep['exclude'])) {
+                    continue;
+                }
+                $exclude[] = $dep['exclude'];
+            }
+            if (count($exclude)) {
+                $php['exclude'] = $exclude;
+            }
+            $ret[] = $php;
+        }
+        return $ret;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php
new file mode 100644 (file)
index 0000000..4d202df
--- /dev/null
@@ -0,0 +1,893 @@
+<?php
+/**
+ * package.xml generation class, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Stephan Schmidt (original XML_Serializer code)
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * file/dir manipulation routines
+ */
+require_once 'System.php';
+require_once 'XML/Util.php';
+
+/**
+ * This class converts a PEAR_PackageFile_v2 object into any output format.
+ *
+ * Supported output formats include array, XML string (using S. Schmidt's
+ * XML_Serializer, slightly customized)
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Stephan Schmidt (original XML_Serializer code)
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Generator_v2
+{
+   /**
+    * default options for the serialization
+    * @access private
+    * @var array $_defaultOptions
+    */
+    var $_defaultOptions = array(
+        'indent'             => ' ',                    // string used for indentation
+        'linebreak'          => "\n",                  // string used for newlines
+        'typeHints'          => false,                 // automatically add type hin attributes
+        'addDecl'            => true,                 // add an XML declaration
+        'defaultTagName'     => 'XML_Serializer_Tag',  // tag used for indexed arrays or invalid names
+        'classAsTagName'     => false,                 // use classname for objects in indexed arrays
+        'keyAttribute'       => '_originalKey',        // attribute where original key is stored
+        'typeAttribute'      => '_type',               // attribute for type (only if typeHints => true)
+        'classAttribute'     => '_class',              // attribute for class of objects (only if typeHints => true)
+        'scalarAsAttributes' => false,                 // scalar values (strings, ints,..) will be serialized as attribute
+        'prependAttributes'  => '',                    // prepend string for attributes
+        'indentAttributes'   => false,                 // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column
+        'mode'               => 'simplexml',             // use 'simplexml' to use parent name as tagname if transforming an indexed array
+        'addDoctype'         => false,                 // add a doctype declaration
+        'doctype'            => null,                  // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()}
+        'rootName'           => 'package',                  // name of the root tag
+        'rootAttributes'     => array(
+            'version' => '2.0',
+            'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+            'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+            'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+            'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd',
+        ),               // attributes of the root tag
+        'attributesArray'    => 'attribs',                  // all values in this key will be treated as attributes
+        'contentName'        => '_content',                   // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray
+        'beautifyFilelist'   => false,
+        'encoding' => 'UTF-8',
+    );
+
+   /**
+    * options for the serialization
+    * @access private
+    * @var array $options
+    */
+    var $options = array();
+
+   /**
+    * current tag depth
+    * @var integer $_tagDepth
+    */
+    var $_tagDepth = 0;
+
+   /**
+    * serilialized representation of the data
+    * @var string $_serializedData
+    */
+    var $_serializedData = null;
+    /**
+     * @var PEAR_PackageFile_v2
+     */
+    var $_packagefile;
+    /**
+     * @param PEAR_PackageFile_v2
+     */
+    function PEAR_PackageFile_Generator_v2(&$packagefile)
+    {
+        $this->_packagefile = &$packagefile;
+        if (isset($this->_packagefile->encoding)) {
+            $this->_defaultOptions['encoding'] = $this->_packagefile->encoding;
+        }
+    }
+
+    /**
+     * @return string
+     */
+    function getPackagerVersion()
+    {
+        return '1.9.4';
+    }
+
+    /**
+     * @param PEAR_Packager
+     * @param bool generate a .tgz or a .tar
+     * @param string|null temporary directory to package in
+     */
+    function toTgz(&$packager, $compress = true, $where = null)
+    {
+        $a = null;
+        return $this->toTgz2($packager, $a, $compress, $where);
+    }
+
+    /**
+     * Package up both a package.xml and package2.xml for the same release
+     * @param PEAR_Packager
+     * @param PEAR_PackageFile_v1
+     * @param bool generate a .tgz or a .tar
+     * @param string|null temporary directory to package in
+     */
+    function toTgz2(&$packager, &$pf1, $compress = true, $where = null)
+    {
+        require_once 'Archive/Tar.php';
+        if (!$this->_packagefile->isEquivalent($pf1)) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' .
+                basename($pf1->getPackageFile()) .
+                '" is not equivalent to "' . basename($this->_packagefile->getPackageFile())
+                . '"');
+        }
+
+        if ($where === null) {
+            if (!($where = System::mktemp(array('-d')))) {
+                return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed');
+            }
+        } elseif (!@System::mkDir(array('-p', $where))) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' .
+                ' not be created');
+        }
+
+        $file = $where . DIRECTORY_SEPARATOR . 'package.xml';
+        if (file_exists($file) && !is_file($file)) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' .
+                ' "' . $file  .'"');
+        }
+
+        if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml');
+        }
+
+        $ext = $compress ? '.tgz' : '.tar';
+        $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion();
+        $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
+        if (file_exists($dest_package) && !is_file($dest_package)) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' .
+                $dest_package . '"');
+        }
+
+        $pkgfile = $this->_packagefile->getPackageFile();
+        if (!$pkgfile) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' .
+                'be created from a real file');
+        }
+
+        $pkgdir  = dirname(realpath($pkgfile));
+        $pkgfile = basename($pkgfile);
+
+        // {{{ Create the package file list
+        $filelist = array();
+        $i = 0;
+        $this->_packagefile->flattenFilelist();
+        $contents = $this->_packagefile->getContents();
+        if (isset($contents['bundledpackage'])) { // bundles of packages
+            $contents = $contents['bundledpackage'];
+            if (!isset($contents[0])) {
+                $contents = array($contents);
+            }
+
+            $packageDir = $where;
+            foreach ($contents as $i => $package) {
+                $fname = $package;
+                $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+                if (!file_exists($file)) {
+                    return $packager->raiseError("File does not exist: $fname");
+                }
+
+                $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
+                System::mkdir(array('-p', dirname($tfile)));
+                copy($file, $tfile);
+                $filelist[$i++] = $tfile;
+                $packager->log(2, "Adding package $fname");
+            }
+        } else { // normal packages
+            $contents = $contents['dir']['file'];
+            if (!isset($contents[0])) {
+                $contents = array($contents);
+            }
+
+            $packageDir = $where;
+            foreach ($contents as $i => $file) {
+                $fname = $file['attribs']['name'];
+                $atts = $file['attribs'];
+                $orig = $file;
+                $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+                if (!file_exists($file)) {
+                    return $packager->raiseError("File does not exist: $fname");
+                }
+
+                $origperms = fileperms($file);
+                $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
+                unset($orig['attribs']);
+                if (count($orig)) { // file with tasks
+                    // run any package-time tasks
+                    $contents = file_get_contents($file);
+                    foreach ($orig as $tag => $raw) {
+                        $tag = str_replace(
+                            array($this->_packagefile->getTasksNs() . ':', '-'),
+                            array('', '_'), $tag);
+                        $task = "PEAR_Task_$tag";
+                        $task = &new $task($this->_packagefile->_config,
+                            $this->_packagefile->_logger,
+                            PEAR_TASK_PACKAGE);
+                        $task->init($raw, $atts, null);
+                        $res = $task->startSession($this->_packagefile, $contents, $tfile);
+                        if (!$res) {
+                            continue; // skip this task
+                        }
+
+                        if (PEAR::isError($res)) {
+                            return $res;
+                        }
+
+                        $contents = $res; // save changes
+                        System::mkdir(array('-p', dirname($tfile)));
+                        $wp = fopen($tfile, "wb");
+                        fwrite($wp, $contents);
+                        fclose($wp);
+                    }
+                }
+
+                if (!file_exists($tfile)) {
+                    System::mkdir(array('-p', dirname($tfile)));
+                    copy($file, $tfile);
+                }
+
+                chmod($tfile, $origperms);
+                $filelist[$i++] = $tfile;
+                $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1);
+                $packager->log(2, "Adding file $fname");
+            }
+        }
+            // }}}
+
+        $name       = $pf1 !== null ? 'package2.xml' : 'package.xml';
+        $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name);
+        if ($packagexml) {
+            $tar =& new Archive_Tar($dest_package, $compress);
+            $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
+            // ----- Creates with the package.xml file
+            $ok = $tar->createModify(array($packagexml), '', $where);
+            if (PEAR::isError($ok)) {
+                return $packager->raiseError($ok);
+            } elseif (!$ok) {
+                return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name .
+                    ' failed');
+            }
+
+            // ----- Add the content of the package
+            if (!$tar->addModify($filelist, $pkgver, $where)) {
+                return $packager->raiseError(
+                    'PEAR_Packagefile_v2::toTgz(): tarball creation failed');
+            }
+
+            // add the package.xml version 1.0
+            if ($pf1 !== null) {
+                $pfgen = &$pf1->getDefaultGenerator();
+                $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true);
+                if (!$tar->addModify(array($packagexml1), '', $where)) {
+                    return $packager->raiseError(
+                        'PEAR_Packagefile_v2::toTgz(): adding package.xml failed');
+                }
+            }
+
+            return $dest_package;
+        }
+    }
+
+    function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml')
+    {
+        if (!$this->_packagefile->validate($state)) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml',
+                null, null, null, $this->_packagefile->getValidationWarnings());
+        }
+
+        if ($where === null) {
+            if (!($where = System::mktemp(array('-d')))) {
+                return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed');
+            }
+        } elseif (!@System::mkDir(array('-p', $where))) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' .
+                ' not be created');
+        }
+
+        $newpkgfile = $where . DIRECTORY_SEPARATOR . $name;
+        $np = @fopen($newpkgfile, 'wb');
+        if (!$np) {
+            return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' .
+               "$name as $newpkgfile");
+        }
+        fwrite($np, $this->toXml($state));
+        fclose($np);
+        return $newpkgfile;
+    }
+
+    function &toV2()
+    {
+        return $this->_packagefile;
+    }
+
+    /**
+     * Return an XML document based on the package info (as returned
+     * by the PEAR_Common::infoFrom* methods).
+     *
+     * @return string XML data
+     */
+    function toXml($state = PEAR_VALIDATE_NORMAL, $options = array())
+    {
+        $this->_packagefile->setDate(date('Y-m-d'));
+        $this->_packagefile->setTime(date('H:i:s'));
+        if (!$this->_packagefile->validate($state)) {
+            return false;
+        }
+
+        if (is_array($options)) {
+            $this->options = array_merge($this->_defaultOptions, $options);
+        } else {
+            $this->options = $this->_defaultOptions;
+        }
+
+        $arr = $this->_packagefile->getArray();
+        if (isset($arr['filelist'])) {
+            unset($arr['filelist']);
+        }
+
+        if (isset($arr['_lastversion'])) {
+            unset($arr['_lastversion']);
+        }
+
+        // Fix the notes a little bit
+        if (isset($arr['notes'])) {
+            // This trims out the indenting, needs fixing
+            $arr['notes'] = "\n" . trim($arr['notes']) . "\n";
+        }
+
+        if (isset($arr['changelog']) && !empty($arr['changelog'])) {
+            // Fix for inconsistency how the array is filled depending on the changelog release amount
+            if (!isset($arr['changelog']['release'][0])) {
+                $release = $arr['changelog']['release'];
+                unset($arr['changelog']['release']);
+
+                $arr['changelog']['release']    = array();
+                $arr['changelog']['release'][0] = $release;
+            }
+
+            foreach (array_keys($arr['changelog']['release']) as $key) {
+                $c =& $arr['changelog']['release'][$key];
+                if (isset($c['notes'])) {
+                    // This trims out the indenting, needs fixing
+                    $c['notes'] = "\n" . trim($c['notes']) . "\n";
+                }
+            }
+        }
+
+        if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) {
+            $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']);
+            unset($arr['contents']['dir']['file']);
+            if (isset($use['dir'])) {
+                $arr['contents']['dir']['dir'] = $use['dir'];
+            }
+            if (isset($use['file'])) {
+                $arr['contents']['dir']['file'] = $use['file'];
+            }
+            $this->options['beautifyFilelist'] = true;
+        }
+
+        $arr['attribs']['packagerversion'] = '1.9.4';
+        if ($this->serialize($arr, $options)) {
+            return $this->_serializedData . "\n";
+        }
+
+        return false;
+    }
+
+
+    function _recursiveXmlFilelist($list)
+    {
+        $dirs = array();
+        if (isset($list['attribs'])) {
+            $file = $list['attribs']['name'];
+            unset($list['attribs']['name']);
+            $attributes = $list['attribs'];
+            $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes);
+        } else {
+            foreach ($list as $a) {
+                $file = $a['attribs']['name'];
+                $attributes = $a['attribs'];
+                unset($a['attribs']);
+                $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a);
+            }
+        }
+        $this->_formatDir($dirs);
+        $this->_deFormat($dirs);
+        return $dirs;
+    }
+
+    function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null)
+    {
+        if (!$tasks) {
+            $tasks = array();
+        }
+        if ($dir == array() || $dir == array('.')) {
+            $dirs['file'][basename($file)] = $tasks;
+            $attributes['name'] = basename($file);
+            $dirs['file'][basename($file)]['attribs'] = $attributes;
+            return;
+        }
+        $curdir = array_shift($dir);
+        if (!isset($dirs['dir'][$curdir])) {
+            $dirs['dir'][$curdir] = array();
+        }
+        $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks);
+    }
+
+    function _formatDir(&$dirs)
+    {
+        if (!count($dirs)) {
+            return array();
+        }
+        $newdirs = array();
+        if (isset($dirs['dir'])) {
+            $newdirs['dir'] = $dirs['dir'];
+        }
+        if (isset($dirs['file'])) {
+            $newdirs['file'] = $dirs['file'];
+        }
+        $dirs = $newdirs;
+        if (isset($dirs['dir'])) {
+            uksort($dirs['dir'], 'strnatcasecmp');
+            foreach ($dirs['dir'] as $dir => $contents) {
+                $this->_formatDir($dirs['dir'][$dir]);
+            }
+        }
+        if (isset($dirs['file'])) {
+            uksort($dirs['file'], 'strnatcasecmp');
+        };
+    }
+
+    function _deFormat(&$dirs)
+    {
+        if (!count($dirs)) {
+            return array();
+        }
+        $newdirs = array();
+        if (isset($dirs['dir'])) {
+            foreach ($dirs['dir'] as $dir => $contents) {
+                $newdir = array();
+                $newdir['attribs']['name'] = $dir;
+                $this->_deFormat($contents);
+                foreach ($contents as $tag => $val) {
+                    $newdir[$tag] = $val;
+                }
+                $newdirs['dir'][] = $newdir;
+            }
+            if (count($newdirs['dir']) == 1) {
+                $newdirs['dir'] = $newdirs['dir'][0];
+            }
+        }
+        if (isset($dirs['file'])) {
+            foreach ($dirs['file'] as $name => $file) {
+                $newdirs['file'][] = $file;
+            }
+            if (count($newdirs['file']) == 1) {
+                $newdirs['file'] = $newdirs['file'][0];
+            }
+        }
+        $dirs = $newdirs;
+    }
+
+    /**
+    * reset all options to default options
+    *
+    * @access   public
+    * @see      setOption(), XML_Unserializer()
+    */
+    function resetOptions()
+    {
+        $this->options = $this->_defaultOptions;
+    }
+
+   /**
+    * set an option
+    *
+    * You can use this method if you do not want to set all options in the constructor
+    *
+    * @access   public
+    * @see      resetOption(), XML_Serializer()
+    */
+    function setOption($name, $value)
+    {
+        $this->options[$name] = $value;
+    }
+
+   /**
+    * sets several options at once
+    *
+    * You can use this method if you do not want to set all options in the constructor
+    *
+    * @access   public
+    * @see      resetOption(), XML_Unserializer(), setOption()
+    */
+    function setOptions($options)
+    {
+        $this->options = array_merge($this->options, $options);
+    }
+
+   /**
+    * serialize data
+    *
+    * @access   public
+    * @param    mixed    $data data to serialize
+    * @return   boolean  true on success, pear error on failure
+    */
+    function serialize($data, $options = null)
+    {
+        // if options have been specified, use them instead
+        // of the previously defined ones
+        if (is_array($options)) {
+            $optionsBak = $this->options;
+            if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) {
+                $this->options = array_merge($this->_defaultOptions, $options);
+            } else {
+                $this->options = array_merge($this->options, $options);
+            }
+        } else {
+            $optionsBak = null;
+        }
+
+        //  start depth is zero
+        $this->_tagDepth = 0;
+        $this->_serializedData = '';
+        // serialize an array
+        if (is_array($data)) {
+            $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array';
+            $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']);
+        }
+
+        // add doctype declaration
+        if ($this->options['addDoctype'] === true) {
+            $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype'])
+                                   . $this->options['linebreak']
+                                   . $this->_serializedData;
+        }
+
+        //  build xml declaration
+        if ($this->options['addDecl']) {
+            $atts = array();
+            $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null;
+            $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding)
+                                   . $this->options['linebreak']
+                                   . $this->_serializedData;
+        }
+
+
+        if ($optionsBak !== null) {
+            $this->options = $optionsBak;
+        }
+
+        return  true;
+    }
+
+   /**
+    * get the result of the serialization
+    *
+    * @access public
+    * @return string serialized XML
+    */
+    function getSerializedData()
+    {
+        if ($this->_serializedData === null) {
+            return  $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION);
+        }
+        return $this->_serializedData;
+    }
+
+   /**
+    * serialize any value
+    *
+    * This method checks for the type of the value and calls the appropriate method
+    *
+    * @access private
+    * @param  mixed     $value
+    * @param  string    $tagName
+    * @param  array     $attributes
+    * @return string
+    */
+    function _serializeValue($value, $tagName = null, $attributes = array())
+    {
+        if (is_array($value)) {
+            $xml = $this->_serializeArray($value, $tagName, $attributes);
+        } elseif (is_object($value)) {
+            $xml = $this->_serializeObject($value, $tagName);
+        } else {
+            $tag = array(
+                          'qname'      => $tagName,
+                          'attributes' => $attributes,
+                          'content'    => $value
+                        );
+            $xml = $this->_createXMLTag($tag);
+        }
+        return $xml;
+    }
+
+   /**
+    * serialize an array
+    *
+    * @access   private
+    * @param    array   $array       array to serialize
+    * @param    string  $tagName     name of the root tag
+    * @param    array   $attributes  attributes for the root tag
+    * @return   string  $string      serialized data
+    * @uses     XML_Util::isValidName() to check, whether key has to be substituted
+    */
+    function _serializeArray(&$array, $tagName = null, $attributes = array())
+    {
+        $_content = null;
+
+        /**
+         * check for special attributes
+         */
+        if ($this->options['attributesArray'] !== null) {
+            if (isset($array[$this->options['attributesArray']])) {
+                $attributes = $array[$this->options['attributesArray']];
+                unset($array[$this->options['attributesArray']]);
+            }
+            /**
+             * check for special content
+             */
+            if ($this->options['contentName'] !== null) {
+                if (isset($array[$this->options['contentName']])) {
+                    $_content = $array[$this->options['contentName']];
+                    unset($array[$this->options['contentName']]);
+                }
+            }
+        }
+
+        /*
+        * if mode is set to simpleXML, check whether
+        * the array is associative or indexed
+        */
+        if (is_array($array) && $this->options['mode'] == 'simplexml') {
+            $indexed = true;
+            if (!count($array)) {
+                $indexed = false;
+            }
+            foreach ($array as $key => $val) {
+                if (!is_int($key)) {
+                    $indexed = false;
+                    break;
+                }
+            }
+
+            if ($indexed && $this->options['mode'] == 'simplexml') {
+                $string = '';
+                foreach ($array as $key => $val) {
+                    if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
+                        if (!isset($this->_curdir)) {
+                            $this->_curdir = '';
+                        }
+                        $savedir = $this->_curdir;
+                        if (isset($val['attribs'])) {
+                            if ($val['attribs']['name'] == '/') {
+                                $this->_curdir = '/';
+                            } else {
+                                if ($this->_curdir == '/') {
+                                    $this->_curdir = '';
+                                }
+                                $this->_curdir .= '/' . $val['attribs']['name'];
+                            }
+                        }
+                    }
+                    $string .= $this->_serializeValue( $val, $tagName, $attributes);
+                    if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
+                        $string .= ' <!-- ' . $this->_curdir . ' -->';
+                        if (empty($savedir)) {
+                            unset($this->_curdir);
+                        } else {
+                            $this->_curdir = $savedir;
+                        }
+                    }
+
+                    $string .= $this->options['linebreak'];
+                    // do indentation
+                    if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
+                        $string .= str_repeat($this->options['indent'], $this->_tagDepth);
+                    }
+                }
+                return rtrim($string);
+            }
+        }
+
+        if ($this->options['scalarAsAttributes'] === true) {
+            foreach ($array as $key => $value) {
+                if (is_scalar($value) && (XML_Util::isValidName($key) === true)) {
+                    unset($array[$key]);
+                    $attributes[$this->options['prependAttributes'].$key] = $value;
+                }
+            }
+        }
+
+        // check for empty array => create empty tag
+        if (empty($array)) {
+            $tag = array(
+                            'qname'      => $tagName,
+                            'content'    => $_content,
+                            'attributes' => $attributes
+                        );
+
+        } else {
+            $this->_tagDepth++;
+            $tmp = $this->options['linebreak'];
+            foreach ($array as $key => $value) {
+                // do indentation
+                if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
+                    $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
+                }
+
+                // copy key
+                $origKey = $key;
+                // key cannot be used as tagname => use default tag
+                $valid = XML_Util::isValidName($key);
+                if (PEAR::isError($valid)) {
+                    if ($this->options['classAsTagName'] && is_object($value)) {
+                        $key = get_class($value);
+                    } else {
+                        $key = $this->options['defaultTagName'];
+                    }
+                }
+                $atts = array();
+                if ($this->options['typeHints'] === true) {
+                    $atts[$this->options['typeAttribute']] = gettype($value);
+                    if ($key !== $origKey) {
+                        $atts[$this->options['keyAttribute']] = (string)$origKey;
+                    }
+
+                }
+                if ($this->options['beautifyFilelist'] && $key == 'dir') {
+                    if (!isset($this->_curdir)) {
+                        $this->_curdir = '';
+                    }
+                    $savedir = $this->_curdir;
+                    if (isset($value['attribs'])) {
+                        if ($value['attribs']['name'] == '/') {
+                            $this->_curdir = '/';
+                        } else {
+                            $this->_curdir .= '/' . $value['attribs']['name'];
+                        }
+                    }
+                }
+
+                if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) {
+                    $value .= str_repeat($this->options['indent'], $this->_tagDepth);
+                }
+                $tmp .= $this->_createXMLTag(array(
+                                                    'qname'      => $key,
+                                                    'attributes' => $atts,
+                                                    'content'    => $value )
+                                            );
+                if ($this->options['beautifyFilelist'] && $key == 'dir') {
+                    if (isset($value['attribs'])) {
+                        $tmp .= ' <!-- ' . $this->_curdir . ' -->';
+                        if (empty($savedir)) {
+                            unset($this->_curdir);
+                        } else {
+                            $this->_curdir = $savedir;
+                        }
+                    }
+                }
+                $tmp .= $this->options['linebreak'];
+            }
+
+            $this->_tagDepth--;
+            if ($this->options['indent']!==null && $this->_tagDepth>0) {
+                $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
+            }
+
+            if (trim($tmp) === '') {
+                $tmp = null;
+            }
+
+            $tag = array(
+                'qname'      => $tagName,
+                'content'    => $tmp,
+                'attributes' => $attributes
+            );
+        }
+        if ($this->options['typeHints'] === true) {
+            if (!isset($tag['attributes'][$this->options['typeAttribute']])) {
+                $tag['attributes'][$this->options['typeAttribute']] = 'array';
+            }
+        }
+
+        $string = $this->_createXMLTag($tag, false);
+        return $string;
+    }
+
+   /**
+    * create a tag from an array
+    * this method awaits an array in the following format
+    * array(
+    *       'qname'        => $tagName,
+    *       'attributes'   => array(),
+    *       'content'      => $content,      // optional
+    *       'namespace'    => $namespace     // optional
+    *       'namespaceUri' => $namespaceUri  // optional
+    *   )
+    *
+    * @access   private
+    * @param    array   $tag tag definition
+    * @param    boolean $replaceEntities whether to replace XML entities in content or not
+    * @return   string  $string XML tag
+    */
+    function _createXMLTag($tag, $replaceEntities = true)
+    {
+        if ($this->options['indentAttributes'] !== false) {
+            $multiline = true;
+            $indent    = str_repeat($this->options['indent'], $this->_tagDepth);
+
+            if ($this->options['indentAttributes'] == '_auto') {
+                $indent .= str_repeat(' ', (strlen($tag['qname'])+2));
+
+            } else {
+                $indent .= $this->options['indentAttributes'];
+            }
+        } else {
+            $indent = $multiline = false;
+        }
+
+        if (is_array($tag['content'])) {
+            if (empty($tag['content'])) {
+                $tag['content'] = '';
+            }
+        } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') {
+            $tag['content'] = '';
+        }
+
+        if (is_scalar($tag['content']) || is_null($tag['content'])) {
+            if ($this->options['encoding'] == 'UTF-8' &&
+                  version_compare(phpversion(), '5.0.0', 'lt')
+            ) {
+                $tag['content'] = utf8_encode($tag['content']);
+            }
+
+            if ($replaceEntities === true) {
+                $replaceEntities = XML_UTIL_ENTITIES_XML;
+            }
+
+            $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']);
+        } elseif (is_array($tag['content'])) {
+            $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']);
+        } elseif (is_object($tag['content'])) {
+            $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']);
+        } elseif (is_resource($tag['content'])) {
+            settype($tag['content'], 'string');
+            $tag = XML_Util::createTagFromArray($tag, $replaceEntities);
+        }
+        return  $tag;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php
new file mode 100644 (file)
index 0000000..23395dc
--- /dev/null
@@ -0,0 +1,459 @@
+<?php
+/**
+ * package.xml parsing class, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * package.xml abstraction class
+ */
+require_once 'PEAR/PackageFile/v1.php';
+/**
+ * Parser for package.xml version 1.0
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: @PEAR-VER@
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Parser_v1
+{
+    var $_registry;
+    var $_config;
+    var $_logger;
+    /**
+     * BC hack to allow PEAR_Common::infoFromString() to sort of
+     * work with the version 2.0 format - there's no filelist though
+     * @param PEAR_PackageFile_v2
+     */
+    function fromV2($packagefile)
+    {
+        $info = $packagefile->getArray(true);
+        $ret = new PEAR_PackageFile_v1;
+        $ret->fromArray($info['old']);
+    }
+
+    function setConfig(&$c)
+    {
+        $this->_config = &$c;
+        $this->_registry = &$c->getRegistry();
+    }
+
+    function setLogger(&$l)
+    {
+        $this->_logger = &$l;
+    }
+
+    /**
+     * @param string contents of package.xml file, version 1.0
+     * @return bool success of parsing
+     */
+    function &parse($data, $file, $archive = false)
+    {
+        if (!extension_loaded('xml')) {
+            return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension');
+        }
+        $xp = xml_parser_create();
+        if (!$xp) {
+            $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml');
+            return $a;
+        }
+        xml_set_object($xp, $this);
+        xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0');
+        xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0');
+        xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
+
+        $this->element_stack = array();
+        $this->_packageInfo = array('provides' => array());
+        $this->current_element = false;
+        unset($this->dir_install);
+        $this->_packageInfo['filelist'] = array();
+        $this->filelist =& $this->_packageInfo['filelist'];
+        $this->dir_names = array();
+        $this->in_changelog = false;
+        $this->d_i = 0;
+        $this->cdata = '';
+        $this->_isValid = true;
+
+        if (!xml_parse($xp, $data, 1)) {
+            $code = xml_get_error_code($xp);
+            $line = xml_get_current_line_number($xp);
+            xml_parser_free($xp);
+            $a = &PEAR::raiseError(sprintf("XML error: %s at line %d",
+                           $str = xml_error_string($code), $line), 2);
+            return $a;
+        }
+
+        xml_parser_free($xp);
+
+        $pf = new PEAR_PackageFile_v1;
+        $pf->setConfig($this->_config);
+        if (isset($this->_logger)) {
+            $pf->setLogger($this->_logger);
+        }
+        $pf->setPackagefile($file, $archive);
+        $pf->fromArray($this->_packageInfo);
+        return $pf;
+    }
+    // {{{ _unIndent()
+
+    /**
+     * Unindent given string
+     *
+     * @param string $str The string that has to be unindented.
+     * @return string
+     * @access private
+     */
+    function _unIndent($str)
+    {
+        // remove leading newlines
+        $str = preg_replace('/^[\r\n]+/', '', $str);
+        // find whitespace at the beginning of the first line
+        $indent_len = strspn($str, " \t");
+        $indent = substr($str, 0, $indent_len);
+        $data = '';
+        // remove the same amount of whitespace from following lines
+        foreach (explode("\n", $str) as $line) {
+            if (substr($line, 0, $indent_len) == $indent) {
+                $data .= substr($line, $indent_len) . "\n";
+            } elseif (trim(substr($line, 0, $indent_len))) {
+                $data .= ltrim($line);
+            }
+        }
+        return $data;
+    }
+
+    // Support for package DTD v1.0:
+    // {{{ _element_start_1_0()
+
+    /**
+     * XML parser callback for ending elements.  Used for version 1.0
+     * packages.
+     *
+     * @param resource  $xp    XML parser resource
+     * @param string    $name  name of ending element
+     *
+     * @return void
+     *
+     * @access private
+     */
+    function _element_start_1_0($xp, $name, $attribs)
+    {
+        array_push($this->element_stack, $name);
+        $this->current_element = $name;
+        $spos = sizeof($this->element_stack) - 2;
+        $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
+        $this->current_attributes = $attribs;
+        $this->cdata = '';
+        switch ($name) {
+            case 'dir':
+                if ($this->in_changelog) {
+                    break;
+                }
+                if (array_key_exists('name', $attribs) && $attribs['name'] != '/') {
+                    $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+                        $attribs['name']);
+                    if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) {
+                        $attribs['name'] = substr($attribs['name'], 0,
+                            strlen($attribs['name']) - 1);
+                    }
+                    if (strpos($attribs['name'], '/') === 0) {
+                        $attribs['name'] = substr($attribs['name'], 1);
+                    }
+                    $this->dir_names[] = $attribs['name'];
+                }
+                if (isset($attribs['baseinstalldir'])) {
+                    $this->dir_install = $attribs['baseinstalldir'];
+                }
+                if (isset($attribs['role'])) {
+                    $this->dir_role = $attribs['role'];
+                }
+                break;
+            case 'file':
+                if ($this->in_changelog) {
+                    break;
+                }
+                if (isset($attribs['name'])) {
+                    $path = '';
+                    if (count($this->dir_names)) {
+                        foreach ($this->dir_names as $dir) {
+                            $path .= $dir . '/';
+                        }
+                    }
+                    $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+                        $attribs['name']);
+                    unset($attribs['name']);
+                    $this->current_path = $path;
+                    $this->filelist[$path] = $attribs;
+                    // Set the baseinstalldir only if the file don't have this attrib
+                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
+                        isset($this->dir_install))
+                    {
+                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
+                    }
+                    // Set the Role
+                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
+                        $this->filelist[$path]['role'] = $this->dir_role;
+                    }
+                }
+                break;
+            case 'replace':
+                if (!$this->in_changelog) {
+                    $this->filelist[$this->current_path]['replacements'][] = $attribs;
+                }
+                break;
+            case 'maintainers':
+                $this->_packageInfo['maintainers'] = array();
+                $this->m_i = 0; // maintainers array index
+                break;
+            case 'maintainer':
+                // compatibility check
+                if (!isset($this->_packageInfo['maintainers'])) {
+                    $this->_packageInfo['maintainers'] = array();
+                    $this->m_i = 0;
+                }
+                $this->_packageInfo['maintainers'][$this->m_i] = array();
+                $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i];
+                break;
+            case 'changelog':
+                $this->_packageInfo['changelog'] = array();
+                $this->c_i = 0; // changelog array index
+                $this->in_changelog = true;
+                break;
+            case 'release':
+                if ($this->in_changelog) {
+                    $this->_packageInfo['changelog'][$this->c_i] = array();
+                    $this->current_release = &$this->_packageInfo['changelog'][$this->c_i];
+                } else {
+                    $this->current_release = &$this->_packageInfo;
+                }
+                break;
+            case 'deps':
+                if (!$this->in_changelog) {
+                    $this->_packageInfo['release_deps'] = array();
+                }
+                break;
+            case 'dep':
+                // dependencies array index
+                if (!$this->in_changelog) {
+                    $this->d_i++;
+                    isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false;
+                    $this->_packageInfo['release_deps'][$this->d_i] = $attribs;
+                }
+                break;
+            case 'configureoptions':
+                if (!$this->in_changelog) {
+                    $this->_packageInfo['configure_options'] = array();
+                }
+                break;
+            case 'configureoption':
+                if (!$this->in_changelog) {
+                    $this->_packageInfo['configure_options'][] = $attribs;
+                }
+                break;
+            case 'provides':
+                if (empty($attribs['type']) || empty($attribs['name'])) {
+                    break;
+                }
+                $attribs['explicit'] = true;
+                $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
+                break;
+            case 'package' :
+                if (isset($attribs['version'])) {
+                    $this->_packageInfo['xsdversion'] = trim($attribs['version']);
+                } else {
+                    $this->_packageInfo['xsdversion'] = '1.0';
+                }
+                if (isset($attribs['packagerversion'])) {
+                    $this->_packageInfo['packagerversion'] = $attribs['packagerversion'];
+                }
+                break;
+        }
+    }
+
+    // }}}
+    // {{{ _element_end_1_0()
+
+    /**
+     * XML parser callback for ending elements.  Used for version 1.0
+     * packages.
+     *
+     * @param resource  $xp    XML parser resource
+     * @param string    $name  name of ending element
+     *
+     * @return void
+     *
+     * @access private
+     */
+    function _element_end_1_0($xp, $name)
+    {
+        $data = trim($this->cdata);
+        switch ($name) {
+            case 'name':
+                switch ($this->prev_element) {
+                    case 'package':
+                        $this->_packageInfo['package'] = $data;
+                        break;
+                    case 'maintainer':
+                        $this->current_maintainer['name'] = $data;
+                        break;
+                }
+                break;
+            case 'extends' :
+                $this->_packageInfo['extends'] = $data;
+                break;
+            case 'summary':
+                $this->_packageInfo['summary'] = $data;
+                break;
+            case 'description':
+                $data = $this->_unIndent($this->cdata);
+                $this->_packageInfo['description'] = $data;
+                break;
+            case 'user':
+                $this->current_maintainer['handle'] = $data;
+                break;
+            case 'email':
+                $this->current_maintainer['email'] = $data;
+                break;
+            case 'role':
+                $this->current_maintainer['role'] = $data;
+                break;
+            case 'version':
+                if ($this->in_changelog) {
+                    $this->current_release['version'] = $data;
+                } else {
+                    $this->_packageInfo['version'] = $data;
+                }
+                break;
+            case 'date':
+                if ($this->in_changelog) {
+                    $this->current_release['release_date'] = $data;
+                } else {
+                    $this->_packageInfo['release_date'] = $data;
+                }
+                break;
+            case 'notes':
+                // try to "de-indent" release notes in case someone
+                // has been over-indenting their xml ;-)
+                // Trim only on the right side
+                $data = rtrim($this->_unIndent($this->cdata));
+                if ($this->in_changelog) {
+                    $this->current_release['release_notes'] = $data;
+                } else {
+                    $this->_packageInfo['release_notes'] = $data;
+                }
+                break;
+            case 'warnings':
+                if ($this->in_changelog) {
+                    $this->current_release['release_warnings'] = $data;
+                } else {
+                    $this->_packageInfo['release_warnings'] = $data;
+                }
+                break;
+            case 'state':
+                if ($this->in_changelog) {
+                    $this->current_release['release_state'] = $data;
+                } else {
+                    $this->_packageInfo['release_state'] = $data;
+                }
+                break;
+            case 'license':
+                if ($this->in_changelog) {
+                    $this->current_release['release_license'] = $data;
+                } else {
+                    $this->_packageInfo['release_license'] = $data;
+                }
+                break;
+            case 'dep':
+                if ($data && !$this->in_changelog) {
+                    $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data;
+                }
+                break;
+            case 'dir':
+                if ($this->in_changelog) {
+                    break;
+                }
+                array_pop($this->dir_names);
+                break;
+            case 'file':
+                if ($this->in_changelog) {
+                    break;
+                }
+                if ($data) {
+                    $path = '';
+                    if (count($this->dir_names)) {
+                        foreach ($this->dir_names as $dir) {
+                            $path .= $dir . '/';
+                        }
+                    }
+                    $path .= $data;
+                    $this->filelist[$path] = $this->current_attributes;
+                    // Set the baseinstalldir only if the file don't have this attrib
+                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
+                        isset($this->dir_install))
+                    {
+                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
+                    }
+                    // Set the Role
+                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
+                        $this->filelist[$path]['role'] = $this->dir_role;
+                    }
+                }
+                break;
+            case 'maintainer':
+                if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) {
+                    $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead';
+                }
+                $this->m_i++;
+                break;
+            case 'release':
+                if ($this->in_changelog) {
+                    $this->c_i++;
+                }
+                break;
+            case 'changelog':
+                $this->in_changelog = false;
+                break;
+        }
+        array_pop($this->element_stack);
+        $spos = sizeof($this->element_stack) - 1;
+        $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
+        $this->cdata = '';
+    }
+
+    // }}}
+    // {{{ _pkginfo_cdata_1_0()
+
+    /**
+     * XML parser callback for character data.  Used for version 1.0
+     * packages.
+     *
+     * @param resource  $xp    XML parser resource
+     * @param string    $name  character data
+     *
+     * @return void
+     *
+     * @access private
+     */
+    function _pkginfo_cdata_1_0($xp, $data)
+    {
+        if (isset($this->cdata)) {
+            $this->cdata .= $data;
+        }
+    }
+
+    // }}}
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php
new file mode 100644 (file)
index 0000000..a3ba706
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * package.xml parsing class, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * base xml parser class
+ */
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * Parser for package.xml version 2.0
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: @PEAR-VER@
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser
+{
+    var $_config;
+    var $_logger;
+    var $_registry;
+
+    function setConfig(&$c)
+    {
+        $this->_config = &$c;
+        $this->_registry = &$c->getRegistry();
+    }
+
+    function setLogger(&$l)
+    {
+        $this->_logger = &$l;
+    }
+    /**
+     * Unindent given string
+     *
+     * @param string $str The string that has to be unindented.
+     * @return string
+     * @access private
+     */
+    function _unIndent($str)
+    {
+        // remove leading newlines
+        $str = preg_replace('/^[\r\n]+/', '', $str);
+        // find whitespace at the beginning of the first line
+        $indent_len = strspn($str, " \t");
+        $indent = substr($str, 0, $indent_len);
+        $data = '';
+        // remove the same amount of whitespace from following lines
+        foreach (explode("\n", $str) as $line) {
+            if (substr($line, 0, $indent_len) == $indent) {
+                $data .= substr($line, $indent_len) . "\n";
+            } else {
+                $data .= $line . "\n";
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * post-process data
+     *
+     * @param string $data
+     * @param string $element element name
+     */
+    function postProcess($data, $element)
+    {
+        if ($element == 'notes') {
+            return trim($this->_unIndent($data));
+        }
+        return trim($data);
+    }
+
+    /**
+     * @param string
+     * @param string file name of the package.xml
+     * @param string|false name of the archive this package.xml came from, if any
+     * @param string class name to instantiate and return.  This must be PEAR_PackageFile_v2 or
+     *               a subclass
+     * @return PEAR_PackageFile_v2
+     */
+    function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2')
+    {
+        if (PEAR::isError($err = parent::parse($data, $file))) {
+            return $err;
+        }
+
+        $ret = new $class;
+        $ret->encoding = $this->encoding;
+        $ret->setConfig($this->_config);
+        if (isset($this->_logger)) {
+            $ret->setLogger($this->_logger);
+        }
+
+        $ret->fromArray($this->_unserializedData);
+        $ret->setPackagefile($file, $archive);
+        return $ret;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/v1.php
new file mode 100644 (file)
index 0000000..43e346b
--- /dev/null
@@ -0,0 +1,1612 @@
+<?php
+/**
+ * PEAR_PackageFile_v1, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * For error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+
+/**
+ * Error code if parsing is attempted with no xml extension
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3);
+
+/**
+ * Error code if creating the xml parser resource fails
+ */
+define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4);
+
+/**
+ * Error code used for all sax xml parsing errors
+ */
+define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5);
+
+/**
+ * Error code used when there is no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6);
+
+/**
+ * Error code when a package name is not valid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7);
+
+/**
+ * Error code used when no summary is parsed
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8);
+
+/**
+ * Error code for summaries that are more than 1 line
+ */
+define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9);
+
+/**
+ * Error code used when no description is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10);
+
+/**
+ * Error code used when no license is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11);
+
+/**
+ * Error code used when a <version> version number is not present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12);
+
+/**
+ * Error code used when a <version> version number is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13);
+
+/**
+ * Error code when release state is missing
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14);
+
+/**
+ * Error code when release state is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15);
+
+/**
+ * Error code when release state is missing
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16);
+
+/**
+ * Error code when release state is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17);
+
+/**
+ * Error code when no release notes are found
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18);
+
+/**
+ * Error code when no maintainers are found
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21);
+
+/**
+ * Error code when a maintainer has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22);
+
+/**
+ * Error code when a maintainer has no email
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24);
+
+/**
+ * Error code when a dependency is not a PHP dependency, but has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25);
+
+/**
+ * Error code when a dependency has no type (pkg, php, etc.)
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26);
+
+/**
+ * Error code when a dependency has no relation (lt, ge, has, etc.)
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27);
+
+/**
+ * Error code when a dependency is not a 'has' relation, but has no version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28);
+
+/**
+ * Error code when a dependency has an invalid relation
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29);
+
+/**
+ * Error code when a dependency has an invalid type
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30);
+
+/**
+ * Error code when a dependency has an invalid optional option
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31);
+
+/**
+ * Error code when a dependency is a pkg dependency, and has an invalid package name
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32);
+
+/**
+ * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel
+ */
+define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33);
+
+/**
+ * Error code when rel="has" and version attribute is present.
+ */
+define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34);
+
+/**
+ * Error code when type="php" and dependency name is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35);
+
+/**
+ * Error code when a configure option has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36);
+
+/**
+ * Error code when a configure option has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37);
+
+/**
+ * Error code when a file in the filelist has an invalid role
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38);
+
+/**
+ * Error code when a file in the filelist has no role
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39);
+
+/**
+ * Error code when analyzing a php source file that has parse errors
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40);
+
+/**
+ * Error code when analyzing a php source file reveals a source element
+ * without a package name prefix
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41);
+
+/**
+ * Error code when an unknown channel is specified
+ */
+define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42);
+
+/**
+ * Error code when no files are found in the filelist
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43);
+
+/**
+ * Error code when a file is not valid php according to _analyzeSourceCode()
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44);
+
+/**
+ * Error code when the channel validator returns an error or warning
+ */
+define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45);
+
+/**
+ * Error code when a php5 package is packaged in php4 (analysis doesn't work)
+ */
+define('PEAR_PACKAGEFILE_ERROR_PHP5', 46);
+
+/**
+ * Error code when a file is listed in package.xml but does not exist
+ */
+define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47);
+
+/**
+ * Error code when a <dep type="php" rel="not"... is encountered (use rel="ne")
+ */
+define('PEAR_PACKAGEFILE_PHP_NO_NOT', 48);
+
+/**
+ * Error code when a package.xml contains non-ISO-8859-1 characters
+ */
+define('PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS', 49);
+
+/**
+ * Error code when a dependency is not a 'has' relation, but has no version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION', 50);
+
+/**
+ * Error code when a package has no lead developer
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_LEAD', 51);
+
+/**
+ * Error code when a filename begins with "."
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME', 52);
+/**
+ * package.xml encapsulator
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_v1
+{
+    /**
+     * @access private
+     * @var PEAR_ErrorStack
+     * @access private
+     */
+    var $_stack;
+
+    /**
+     * A registry object, used to access the package name validation regex for non-standard channels
+     * @var PEAR_Registry
+     * @access private
+     */
+    var $_registry;
+
+    /**
+     * An object that contains a log method that matches PEAR_Common::log's signature
+     * @var object
+     * @access private
+     */
+    var $_logger;
+
+    /**
+     * Parsed package information
+     * @var array
+     * @access private
+     */
+    var $_packageInfo;
+
+    /**
+     * path to package.xml
+     * @var string
+     * @access private
+     */
+    var $_packageFile;
+
+    /**
+     * path to package .tgz or false if this is a local/extracted package.xml
+     * @var string
+     * @access private
+     */
+    var $_archiveFile;
+
+    /**
+     * @var int
+     * @access private
+     */
+    var $_isValid = 0;
+
+    /**
+     * Determines whether this packagefile was initialized only with partial package info
+     *
+     * If this package file was constructed via parsing REST, it will only contain
+     *
+     * - package name
+     * - channel name
+     * - dependencies 
+     * @var boolean
+     * @access private
+     */
+    var $_incomplete = true;
+
+    /**
+     * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack
+     * @param string Name of Error Stack class to use.
+     */
+    function PEAR_PackageFile_v1()
+    {
+        $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1');
+        $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
+        $this->_isValid = 0;
+    }
+
+    function installBinary($installer)
+    {
+        return false;
+    }
+
+    function isExtension($name)
+    {
+        return false;
+    }
+
+    function setConfig(&$config)
+    {
+        $this->_config = &$config;
+        $this->_registry = &$config->getRegistry();
+    }
+
+    function setRequestedGroup()
+    {
+        // placeholder
+    }
+
+    /**
+     * For saving in the registry.
+     *
+     * Set the last version that was installed
+     * @param string
+     */
+    function setLastInstalledVersion($version)
+    {
+        $this->_packageInfo['_lastversion'] = $version;
+    }
+
+    /**
+     * @return string|false
+     */
+    function getLastInstalledVersion()
+    {
+        if (isset($this->_packageInfo['_lastversion'])) {
+            return $this->_packageInfo['_lastversion'];
+        }
+        return false;
+    }
+
+    function getInstalledBinary()
+    {
+        return false;
+    }
+
+    function listPostinstallScripts()
+    {
+        return false;
+    }
+
+    function initPostinstallScripts()
+    {
+        return false;
+    }
+
+    function setLogger(&$logger)
+    {
+        if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) {
+            return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
+        }
+        $this->_logger = &$logger;
+    }
+
+    function setPackagefile($file, $archive = false)
+    {
+        $this->_packageFile = $file;
+        $this->_archiveFile = $archive ? $archive : $file;
+    }
+
+    function getPackageFile()
+    {
+        return isset($this->_packageFile) ? $this->_packageFile : false;
+    }
+
+    function getPackageType()
+    {
+        return 'php';
+    }
+
+    function getArchiveFile()
+    {
+        return $this->_archiveFile;
+    }
+
+    function packageInfo($field)
+    {
+        if (!is_string($field) || empty($field) ||
+            !isset($this->_packageInfo[$field])) {
+            return false;
+        }
+        return $this->_packageInfo[$field];
+    }
+
+    function setDirtree($path)
+    {
+        if (!isset($this->_packageInfo['dirtree'])) {
+            $this->_packageInfo['dirtree'] = array();
+        }
+        $this->_packageInfo['dirtree'][$path] = true;
+    }
+
+    function getDirtree()
+    {
+        if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
+            return $this->_packageInfo['dirtree'];
+        }
+        return false;
+    }
+
+    function resetDirtree()
+    {
+        unset($this->_packageInfo['dirtree']);
+    }
+
+    function fromArray($pinfo)
+    {
+        $this->_incomplete = false;
+        $this->_packageInfo = $pinfo;
+    }
+
+    function isIncomplete()
+    {
+        return $this->_incomplete;
+    }
+
+    function getChannel()
+    {
+        return 'pear.php.net';
+    }
+
+    function getUri()
+    {
+        return false;
+    }
+
+    function getTime()
+    {
+        return false;
+    }
+
+    function getExtends()
+    {
+        if (isset($this->_packageInfo['extends'])) {
+            return $this->_packageInfo['extends'];
+        }
+        return false;
+    }
+
+    /**
+     * @return array
+     */
+    function toArray()
+    {
+        if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
+            return false;
+        }
+        return $this->getArray();
+    }
+
+    function getArray()
+    {
+        return $this->_packageInfo;
+    }
+
+    function getName()
+    {
+        return $this->getPackage();
+    }
+
+    function getPackage()
+    {
+        if (isset($this->_packageInfo['package'])) {
+            return $this->_packageInfo['package'];
+        }
+        return false;
+    }
+
+    /**
+     * WARNING - don't use this unless you know what you are doing
+     */
+    function setRawPackage($package)
+    {
+        $this->_packageInfo['package'] = $package;
+    }
+
+    function setPackage($package)
+    {
+        $this->_packageInfo['package'] = $package;
+        $this->_isValid = false;
+    }
+
+    function getVersion()
+    {
+        if (isset($this->_packageInfo['version'])) {
+            return $this->_packageInfo['version'];
+        }
+        return false;
+    }
+
+    function setVersion($version)
+    {
+        $this->_packageInfo['version'] = $version;
+        $this->_isValid = false;
+    }
+
+    function clearMaintainers()
+    {
+        unset($this->_packageInfo['maintainers']);
+    }
+
+    function getMaintainers()
+    {
+        if (isset($this->_packageInfo['maintainers'])) {
+            return $this->_packageInfo['maintainers'];
+        }
+        return false;
+    }
+
+    /**
+     * Adds a new maintainer - no checking of duplicates is performed, use
+     * updatemaintainer for that purpose.
+     */
+    function addMaintainer($role, $handle, $name, $email)
+    {
+        $this->_packageInfo['maintainers'][] =
+            array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name);
+        $this->_isValid = false;
+    }
+
+    function updateMaintainer($role, $handle, $name, $email)
+    {
+        $found = false;
+        if (!isset($this->_packageInfo['maintainers']) ||
+              !is_array($this->_packageInfo['maintainers'])) {
+            return $this->addMaintainer($role, $handle, $name, $email);
+        }
+        foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) {
+            if ($maintainer['handle'] == $handle) {
+                $found = $i;
+                break;
+            }
+        }
+        if ($found !== false) {
+            unset($this->_packageInfo['maintainers'][$found]);
+            $this->_packageInfo['maintainers'] =
+                array_values($this->_packageInfo['maintainers']);
+        }
+        $this->addMaintainer($role, $handle, $name, $email);
+    }
+
+    function deleteMaintainer($handle)
+    {
+        $found = false;
+        foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) {
+            if ($maintainer['handle'] == $handle) {
+                $found = $i;
+                break;
+            }
+        }
+        if ($found !== false) {
+            unset($this->_packageInfo['maintainers'][$found]);
+            $this->_packageInfo['maintainers'] =
+                array_values($this->_packageInfo['maintainers']);
+            return true;
+        }
+        return false;
+    }
+
+    function getState()
+    {
+        if (isset($this->_packageInfo['release_state'])) {
+            return $this->_packageInfo['release_state'];
+        }
+        return false;
+    }
+
+    function setRawState($state)
+    {
+        $this->_packageInfo['release_state'] = $state;
+    }
+
+    function setState($state)
+    {
+        $this->_packageInfo['release_state'] = $state;
+        $this->_isValid = false;
+    }
+
+    function getDate()
+    {
+        if (isset($this->_packageInfo['release_date'])) {
+            return $this->_packageInfo['release_date'];
+        }
+        return false;
+    }
+
+    function setDate($date)
+    {
+        $this->_packageInfo['release_date'] = $date;
+        $this->_isValid = false;
+    }
+
+    function getLicense()
+    {
+        if (isset($this->_packageInfo['release_license'])) {
+            return $this->_packageInfo['release_license'];
+        }
+        return false;
+    }
+
+    function setLicense($date)
+    {
+        $this->_packageInfo['release_license'] = $date;
+        $this->_isValid = false;
+    }
+
+    function getSummary()
+    {
+        if (isset($this->_packageInfo['summary'])) {
+            return $this->_packageInfo['summary'];
+        }
+        return false;
+    }
+
+    function setSummary($summary)
+    {
+        $this->_packageInfo['summary'] = $summary;
+        $this->_isValid = false;
+    }
+
+    function getDescription()
+    {
+        if (isset($this->_packageInfo['description'])) {
+            return $this->_packageInfo['description'];
+        }
+        return false;
+    }
+
+    function setDescription($desc)
+    {
+        $this->_packageInfo['description'] = $desc;
+        $this->_isValid = false;
+    }
+
+    function getNotes()
+    {
+        if (isset($this->_packageInfo['release_notes'])) {
+            return $this->_packageInfo['release_notes'];
+        }
+        return false;
+    }
+
+    function setNotes($notes)
+    {
+        $this->_packageInfo['release_notes'] = $notes;
+        $this->_isValid = false;
+    }
+
+    function getDeps()
+    {
+        if (isset($this->_packageInfo['release_deps'])) {
+            return $this->_packageInfo['release_deps'];
+        }
+        return false;
+    }
+
+    /**
+     * Reset dependencies prior to adding new ones
+     */
+    function clearDeps()
+    {
+        unset($this->_packageInfo['release_deps']);
+    }
+
+    function addPhpDep($version, $rel)
+    {
+        $this->_isValid = false;
+        $this->_packageInfo['release_deps'][] =
+            array('type' => 'php',
+                  'rel' => $rel,
+                  'version' => $version);
+    }
+
+    function addPackageDep($name, $version, $rel, $optional = 'no')
+    {
+        $this->_isValid = false;
+        $dep =
+            array('type' => 'pkg',
+                  'name' => $name,
+                  'rel' => $rel,
+                  'optional' => $optional);
+        if ($rel != 'has' && $rel != 'not') {
+            $dep['version'] = $version;
+        }
+        $this->_packageInfo['release_deps'][] = $dep;
+    }
+
+    function addExtensionDep($name, $version, $rel, $optional = 'no')
+    {
+        $this->_isValid = false;
+        $this->_packageInfo['release_deps'][] =
+            array('type' => 'ext',
+                  'name' => $name,
+                  'rel' => $rel,
+                  'version' => $version,
+                  'optional' => $optional);
+    }
+
+    /**
+     * WARNING - do not use this function directly unless you know what you're doing
+     */
+    function setDeps($deps)
+    {
+        $this->_packageInfo['release_deps'] = $deps;
+    }
+
+    function hasDeps()
+    {
+        return isset($this->_packageInfo['release_deps']) &&
+            count($this->_packageInfo['release_deps']);
+    }
+
+    function getDependencyGroup($group)
+    {
+        return false;
+    }
+
+    function isCompatible($pf)
+    {
+        return false;
+    }
+
+    function isSubpackageOf($p)
+    {
+        return $p->isSubpackage($this);
+    }
+
+    function isSubpackage($p)
+    {
+        return false;
+    }
+
+    function dependsOn($package, $channel)
+    {
+        if (strtolower($channel) != 'pear.php.net') {
+            return false;
+        }
+        if (!($deps = $this->getDeps())) {
+            return false;
+        }
+        foreach ($deps as $dep) {
+            if ($dep['type'] != 'pkg') {
+                continue;
+            }
+            if (strtolower($dep['name']) == strtolower($package)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function getConfigureOptions()
+    {
+        if (isset($this->_packageInfo['configure_options'])) {
+            return $this->_packageInfo['configure_options'];
+        }
+        return false;
+    }
+
+    function hasConfigureOptions()
+    {
+        return isset($this->_packageInfo['configure_options']) &&
+            count($this->_packageInfo['configure_options']);
+    }
+
+    function addConfigureOption($name, $prompt, $default = false)
+    {
+        $o = array('name' => $name, 'prompt' => $prompt);
+        if ($default !== false) {
+            $o['default'] = $default;
+        }
+        if (!isset($this->_packageInfo['configure_options'])) {
+            $this->_packageInfo['configure_options'] = array();
+        }
+        $this->_packageInfo['configure_options'][] = $o;
+    }
+
+    function clearConfigureOptions()
+    {
+        unset($this->_packageInfo['configure_options']);
+    }
+
+    function getProvides()
+    {
+        if (isset($this->_packageInfo['provides'])) {
+            return $this->_packageInfo['provides'];
+        }
+        return false;
+    }
+
+    function getProvidesExtension()
+    {
+        return false;
+    }
+
+    function addFile($dir, $file, $attrs)
+    {
+        $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir);
+        if ($dir == '/' || $dir == '') {
+            $dir = '';
+        } else {
+            $dir .= '/';
+        }
+        $file = $dir . $file;
+        $file = preg_replace('![\\/]+!', '/', $file);
+        $this->_packageInfo['filelist'][$file] = $attrs;
+    }
+
+    function getInstallationFilelist()
+    {
+        return $this->getFilelist();
+    }
+
+    function getFilelist()
+    {
+        if (isset($this->_packageInfo['filelist'])) {
+            return $this->_packageInfo['filelist'];
+        }
+        return false;
+    }
+
+    function setFileAttribute($file, $attr, $value)
+    {
+        $this->_packageInfo['filelist'][$file][$attr] = $value;
+    }
+
+    function resetFilelist()
+    {
+        $this->_packageInfo['filelist'] = array();
+    }
+
+    function setInstalledAs($file, $path)
+    {
+        if ($path) {
+            return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+        }
+        unset($this->_packageInfo['filelist'][$file]['installed_as']);
+    }
+
+    function installedFile($file, $atts)
+    {
+        if (isset($this->_packageInfo['filelist'][$file])) {
+            $this->_packageInfo['filelist'][$file] =
+                array_merge($this->_packageInfo['filelist'][$file], $atts);
+        } else {
+            $this->_packageInfo['filelist'][$file] = $atts;
+        }
+    }
+
+    function getChangelog()
+    {
+        if (isset($this->_packageInfo['changelog'])) {
+            return $this->_packageInfo['changelog'];
+        }
+        return false;
+    }
+
+    function getPackagexmlVersion()
+    {
+        return '1.0';
+    }
+
+    /**
+     * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+     * @param boolean determines whether to purge the error stack after retrieving
+     * @return array
+     */
+    function getValidationWarnings($purge = true)
+    {
+        return $this->_stack->getErrors($purge);
+    }
+
+    // }}}
+    /**
+     * Validation error.  Also marks the object contents as invalid
+     * @param error code
+     * @param array error information
+     * @access private
+     */
+    function _validateError($code, $params = array())
+    {
+        $this->_stack->push($code, 'error', $params, false, false, debug_backtrace());
+        $this->_isValid = false;
+    }
+
+    /**
+     * Validation warning.  Does not mark the object contents invalid.
+     * @param error code
+     * @param array error information
+     * @access private
+     */
+    function _validateWarning($code, $params = array())
+    {
+        $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace());
+    }
+
+    /**
+     * @param integer error code
+     * @access protected
+     */
+    function _getErrorMessage()
+    {
+        return array(
+                PEAR_PACKAGEFILE_ERROR_NO_NAME =>
+                    'Missing Package Name',
+                PEAR_PACKAGEFILE_ERROR_NO_SUMMARY =>
+                    'No summary found',
+                PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY =>
+                    'Summary should be on one line',
+                PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION =>
+                    'Missing description',
+                PEAR_PACKAGEFILE_ERROR_NO_LICENSE =>
+                    'Missing license',
+                PEAR_PACKAGEFILE_ERROR_NO_VERSION =>
+                    'No release version found',
+                PEAR_PACKAGEFILE_ERROR_NO_STATE =>
+                    'No release state found',
+                PEAR_PACKAGEFILE_ERROR_NO_DATE =>
+                    'No release date found',
+                PEAR_PACKAGEFILE_ERROR_NO_NOTES =>
+                    'No release notes found',
+                PEAR_PACKAGEFILE_ERROR_NO_LEAD =>
+                    'Package must have at least one lead maintainer',
+                PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS =>
+                    'No maintainers found, at least one must be defined',
+                PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE =>
+                    'Maintainer %index% has no handle (user ID at channel server)',
+                PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE =>
+                    'Maintainer %index% has no role',
+                PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME =>
+                    'Maintainer %index% has no name',
+                PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL =>
+                    'Maintainer %index% has no email',
+                PEAR_PACKAGEFILE_ERROR_NO_DEPNAME =>
+                    'Dependency %index% is not a php dependency, and has no name',
+                PEAR_PACKAGEFILE_ERROR_NO_DEPREL =>
+                    'Dependency %index% has no relation (rel)',
+                PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE =>
+                    'Dependency %index% has no type',
+                PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED =>
+                    'PHP Dependency %index% has a name attribute of "%name%" which will be' .
+                        ' ignored!',
+                PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION =>
+                    'Dependency %index% is not a rel="has" or rel="not" dependency, ' .
+                        'and has no version',
+                PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION =>
+                    'Dependency %index% is a type="php" dependency, ' .
+                        'and has no version',
+                PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED =>
+                    'Dependency %index% is a rel="%rel%" dependency, versioning is ignored',
+                PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL =>
+                    'Dependency %index% has invalid optional value "%opt%", should be yes or no',
+                PEAR_PACKAGEFILE_PHP_NO_NOT =>
+                    'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' .
+                        ' to exclude specific versions',
+                PEAR_PACKAGEFILE_ERROR_NO_CONFNAME =>
+                    'Configure Option %index% has no name',
+                PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT =>
+                    'Configure Option %index% has no prompt',
+                PEAR_PACKAGEFILE_ERROR_NO_FILES =>
+                    'No files in <filelist> section of package.xml',
+                PEAR_PACKAGEFILE_ERROR_NO_FILEROLE =>
+                    'File "%file%" has no role, expecting one of "%roles%"',
+                PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE =>
+                    'File "%file%" has invalid role "%role%", expecting one of "%roles%"',
+                PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME =>
+                    'File "%file%" cannot start with ".", cannot package or install',
+                PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE =>
+                    'Parser error: invalid PHP found in file "%file%"',
+                PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX =>
+                    'in %file%: %type% "%name%" not prefixed with package name "%package%"',
+                PEAR_PACKAGEFILE_ERROR_INVALID_FILE =>
+                    'Parser error: invalid PHP file "%file%"',
+                PEAR_PACKAGEFILE_ERROR_CHANNELVAL =>
+                    'Channel validator error: field "%field%" - %reason%',
+                PEAR_PACKAGEFILE_ERROR_PHP5 =>
+                    'Error, PHP5 token encountered in %file%, analysis should be in PHP5',
+                PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND =>
+                    'File "%file%" in package.xml does not exist',
+                PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS =>
+                    'Package.xml contains non-ISO-8859-1 characters, and may not validate',
+            );
+    }
+
+    /**
+     * Validate XML package definition file.
+     *
+     * @access public
+     * @return boolean
+     */
+    function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false)
+    {
+        if (($this->_isValid & $state) == $state) {
+            return true;
+        }
+        $this->_isValid = true;
+        $info = $this->_packageInfo;
+        if (empty($info['package'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME);
+            $this->_packageName = $pn = 'unknown';
+        } else {
+            $this->_packageName = $pn = $info['package'];
+        }
+
+        if (empty($info['summary'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY);
+        } elseif (strpos(trim($info['summary']), "\n") !== false) {
+            $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY,
+                array('summary' => $info['summary']));
+        }
+        if (empty($info['description'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION);
+        }
+        if (empty($info['release_license'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE);
+        }
+        if (empty($info['version'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION);
+        }
+        if (empty($info['release_state'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE);
+        }
+        if (empty($info['release_date'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE);
+        }
+        if (empty($info['release_notes'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES);
+        }
+        if (empty($info['maintainers'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS);
+        } else {
+            $haslead = false;
+            $i = 1;
+            foreach ($info['maintainers'] as $m) {
+                if (empty($m['handle'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE,
+                        array('index' => $i));
+                }
+                if (empty($m['role'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE,
+                        array('index' => $i, 'roles' => PEAR_Common::getUserRoles()));
+                } elseif ($m['role'] == 'lead') {
+                    $haslead = true;
+                }
+                if (empty($m['name'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME,
+                        array('index' => $i));
+                }
+                if (empty($m['email'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL,
+                        array('index' => $i));
+                }
+                $i++;
+            }
+            if (!$haslead) {
+                $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD);
+            }
+        }
+        if (!empty($info['release_deps'])) {
+            $i = 1;
+            foreach ($info['release_deps'] as $d) {
+                if (!isset($d['type']) || empty($d['type'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE,
+                        array('index' => $i, 'types' => PEAR_Common::getDependencyTypes()));
+                    continue;
+                }
+                if (!isset($d['rel']) || empty($d['rel'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL,
+                        array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations()));
+                    continue;
+                }
+                if (!empty($d['optional'])) {
+                    if (!in_array($d['optional'], array('yes', 'no'))) {
+                        $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL,
+                            array('index' => $i, 'opt' => $d['optional']));
+                    }
+                }
+                if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION,
+                        array('index' => $i));
+                } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) {
+                    $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED,
+                        array('index' => $i, 'rel' => $d['rel']));
+                }
+                if ($d['type'] == 'php' && !empty($d['name'])) {
+                    $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED,
+                        array('index' => $i, 'name' => $d['name']));
+                } elseif ($d['type'] != 'php' && empty($d['name'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME,
+                        array('index' => $i));
+                }
+                if ($d['type'] == 'php' && empty($d['version'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION,
+                        array('index' => $i));
+                }
+                if (($d['rel'] == 'not') && ($d['type'] == 'php')) {
+                    $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT,
+                        array('index' => $i));
+                }
+                $i++;
+            }
+        }
+        if (!empty($info['configure_options'])) {
+            $i = 1;
+            foreach ($info['configure_options'] as $c) {
+                if (empty($c['name'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME,
+                        array('index' => $i));
+                }
+                if (empty($c['prompt'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT,
+                        array('index' => $i));
+                }
+                $i++;
+            }
+        }
+        if (empty($info['filelist'])) {
+            $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES);
+            $errors[] = 'no files';
+        } else {
+            foreach ($info['filelist'] as $file => $fa) {
+                if (empty($fa['role'])) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE,
+                        array('file' => $file, 'roles' => PEAR_Common::getFileRoles()));
+                    continue;
+                } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE,
+                        array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles()));
+                }
+                if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) {
+                    // file contains .. parent directory or . cur directory references
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+                        array('file' => $file));
+                }
+                if (isset($fa['install-as']) &&
+                      preg_match('~/\.\.?(/|\\z)|^\.\.?/~', 
+                                 str_replace('\\', '/', $fa['install-as']))) {
+                    // install-as contains .. parent directory or . cur directory references
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+                        array('file' => $file . ' [installed as ' . $fa['install-as'] . ']'));
+                }
+                if (isset($fa['baseinstalldir']) &&
+                      preg_match('~/\.\.?(/|\\z)|^\.\.?/~', 
+                                 str_replace('\\', '/', $fa['baseinstalldir']))) {
+                    // install-as contains .. parent directory or . cur directory references
+                    $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+                        array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']'));
+                }
+            }
+        }
+        if (isset($this->_registry) && $this->_isValid) {
+            $chan = $this->_registry->getChannel('pear.php.net');
+            if (PEAR::isError($chan)) {
+                $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage());
+                return $this->_isValid = 0;
+            }
+            $validator = $chan->getValidationObject();
+            $validator->setPackageFile($this);
+            $validator->validate($state);
+            $failures = $validator->getFailures();
+            foreach ($failures['errors'] as $error) {
+                $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error);
+            }
+            foreach ($failures['warnings'] as $warning) {
+                $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning);
+            }
+        }
+        if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) {
+            if ($this->_analyzePhpFiles()) {
+                $this->_isValid = true;
+            }
+        }
+        if ($this->_isValid) {
+            return $this->_isValid = $state;
+        }
+        return $this->_isValid = 0;
+    }
+
+    function _analyzePhpFiles()
+    {
+        if (!$this->_isValid) {
+            return false;
+        }
+        if (!isset($this->_packageFile)) {
+            return false;
+        }
+        $dir_prefix = dirname($this->_packageFile);
+        $common = new PEAR_Common;
+        $log = isset($this->_logger) ? array(&$this->_logger, 'log') :
+            array($common, 'log');
+        $info = $this->getFilelist();
+        foreach ($info as $file => $fa) {
+            if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
+                $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND,
+                    array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file));
+                continue;
+            }
+            if ($fa['role'] == 'php' && $dir_prefix) {
+                call_user_func_array($log, array(1, "Analyzing $file"));
+                $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
+                if ($srcinfo) {
+                    $this->_buildProvidesArray($srcinfo);
+                }
+            }
+        }
+        $this->_packageName = $pn = $this->getPackage();
+        $pnl = strlen($pn);
+        if (isset($this->_packageInfo['provides'])) {
+            foreach ((array) $this->_packageInfo['provides'] as $key => $what) {
+                if (isset($what['explicit'])) {
+                    // skip conformance checks if the provides entry is
+                    // specified in the package.xml file
+                    continue;
+                }
+                extract($what);
+                if ($type == 'class') {
+                    if (!strncasecmp($name, $pn, $pnl)) {
+                        continue;
+                    }
+                    $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX,
+                        array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn));
+                } elseif ($type == 'function') {
+                    if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
+                        continue;
+                    }
+                    $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX,
+                        array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn));
+                }
+            }
+        }
+        return $this->_isValid;
+    }
+
+    /**
+     * Get the default xml generator object
+     *
+     * @return PEAR_PackageFile_Generator_v1
+     */
+    function &getDefaultGenerator()
+    {
+        if (!class_exists('PEAR_PackageFile_Generator_v1')) {
+            require_once 'PEAR/PackageFile/Generator/v1.php';
+        }
+        $a = &new PEAR_PackageFile_Generator_v1($this);
+        return $a;
+    }
+
+    /**
+     * Get the contents of a file listed within the package.xml
+     * @param string
+     * @return string
+     */
+    function getFileContents($file)
+    {
+        if ($this->_archiveFile == $this->_packageFile) { // unpacked
+            $dir = dirname($this->_packageFile);
+            $file = $dir . DIRECTORY_SEPARATOR . $file;
+            $file = str_replace(array('/', '\\'),
+                array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
+            if (file_exists($file) && is_readable($file)) {
+                return implode('', file($file));
+            }
+        } else { // tgz
+            if (!class_exists('Archive_Tar')) {
+                require_once 'Archive/Tar.php';
+            }
+            $tar = &new Archive_Tar($this->_archiveFile);
+            $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+            if ($file != 'package.xml' && $file != 'package2.xml') {
+                $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
+            }
+            $file = $tar->extractInString($file);
+            $tar->popErrorHandling();
+            if (PEAR::isError($file)) {
+                return PEAR::raiseError("Cannot locate file '$file' in archive");
+            }
+            return $file;
+        }
+    }
+
+    // {{{ analyzeSourceCode()
+    /**
+     * Analyze the source code of the given PHP file
+     *
+     * @param  string Filename of the PHP file
+     * @return mixed
+     * @access private
+     */
+    function _analyzeSourceCode($file)
+    {
+        if (!function_exists("token_get_all")) {
+            return false;
+        }
+        if (!defined('T_DOC_COMMENT')) {
+            define('T_DOC_COMMENT', T_COMMENT);
+        }
+        if (!defined('T_INTERFACE')) {
+            define('T_INTERFACE', -1);
+        }
+        if (!defined('T_IMPLEMENTS')) {
+            define('T_IMPLEMENTS', -1);
+        }
+        if (!$fp = @fopen($file, "r")) {
+            return false;
+        }
+        fclose($fp);
+        $contents = file_get_contents($file);
+        $tokens = token_get_all($contents);
+/*
+        for ($i = 0; $i < sizeof($tokens); $i++) {
+            @list($token, $data) = $tokens[$i];
+            if (is_string($token)) {
+                var_dump($token);
+            } else {
+                print token_name($token) . ' ';
+                var_dump(rtrim($data));
+            }
+        }
+*/
+        $look_for = 0;
+        $paren_level = 0;
+        $bracket_level = 0;
+        $brace_level = 0;
+        $lastphpdoc = '';
+        $current_class = '';
+        $current_interface = '';
+        $current_class_level = -1;
+        $current_function = '';
+        $current_function_level = -1;
+        $declared_classes = array();
+        $declared_interfaces = array();
+        $declared_functions = array();
+        $declared_methods = array();
+        $used_classes = array();
+        $used_functions = array();
+        $extends = array();
+        $implements = array();
+        $nodeps = array();
+        $inquote = false;
+        $interface = false;
+        for ($i = 0; $i < sizeof($tokens); $i++) {
+            if (is_array($tokens[$i])) {
+                list($token, $data) = $tokens[$i];
+            } else {
+                $token = $tokens[$i];
+                $data = '';
+            }
+            if ($inquote) {
+                if ($token != '"' && $token != T_END_HEREDOC) {
+                    continue;
+                } else {
+                    $inquote = false;
+                    continue;
+                }
+            }
+            switch ($token) {
+                case T_WHITESPACE :
+                    continue;
+                case ';':
+                    if ($interface) {
+                        $current_function = '';
+                        $current_function_level = -1;
+                    }
+                    break;
+                case '"':
+                case T_START_HEREDOC:
+                    $inquote = true;
+                    break;
+                case T_CURLY_OPEN:
+                case T_DOLLAR_OPEN_CURLY_BRACES:
+                case '{': $brace_level++; continue 2;
+                case '}':
+                    $brace_level--;
+                    if ($current_class_level == $brace_level) {
+                        $current_class = '';
+                        $current_class_level = -1;
+                    }
+                    if ($current_function_level == $brace_level) {
+                        $current_function = '';
+                        $current_function_level = -1;
+                    }
+                    continue 2;
+                case '[': $bracket_level++; continue 2;
+                case ']': $bracket_level--; continue 2;
+                case '(': $paren_level++;   continue 2;
+                case ')': $paren_level--;   continue 2;
+                case T_INTERFACE:
+                    $interface = true;
+                case T_CLASS:
+                    if (($current_class_level != -1) || ($current_function_level != -1)) {
+                        $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE,
+                            array('file' => $file));
+                        return false;
+                    }
+                case T_FUNCTION:
+                case T_NEW:
+                case T_EXTENDS:
+                case T_IMPLEMENTS:
+                    $look_for = $token;
+                    continue 2;
+                case T_STRING:
+                    if (version_compare(zend_version(), '2.0', '<')) {
+                        if (in_array(strtolower($data),
+                            array('public', 'private', 'protected', 'abstract',
+                                  'interface', 'implements', 'throw') 
+                                 )) {
+                            $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5,
+                                array($file));
+                        }
+                    }
+                    if ($look_for == T_CLASS) {
+                        $current_class = $data;
+                        $current_class_level = $brace_level;
+                        $declared_classes[] = $current_class;
+                    } elseif ($look_for == T_INTERFACE) {
+                        $current_interface = $data;
+                        $current_class_level = $brace_level;
+                        $declared_interfaces[] = $current_interface;
+                    } elseif ($look_for == T_IMPLEMENTS) {
+                        $implements[$current_class] = $data;
+                    } elseif ($look_for == T_EXTENDS) {
+                        $extends[$current_class] = $data;
+                    } elseif ($look_for == T_FUNCTION) {
+                        if ($current_class) {
+                            $current_function = "$current_class::$data";
+                            $declared_methods[$current_class][] = $data;
+                        } elseif ($current_interface) {
+                            $current_function = "$current_interface::$data";
+                            $declared_methods[$current_interface][] = $data;
+                        } else {
+                            $current_function = $data;
+                            $declared_functions[] = $current_function;
+                        }
+                        $current_function_level = $brace_level;
+                        $m = array();
+                    } elseif ($look_for == T_NEW) {
+                        $used_classes[$data] = true;
+                    }
+                    $look_for = 0;
+                    continue 2;
+                case T_VARIABLE:
+                    $look_for = 0;
+                    continue 2;
+                case T_DOC_COMMENT:
+                case T_COMMENT:
+                    if (preg_match('!^/\*\*\s!', $data)) {
+                        $lastphpdoc = $data;
+                        if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
+                            $nodeps = array_merge($nodeps, $m[1]);
+                        }
+                    }
+                    continue 2;
+                case T_DOUBLE_COLON:
+                    if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
+                        $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE,
+                            array('file' => $file));
+                        return false;
+                    }
+                    $class = $tokens[$i - 1][1];
+                    if (strtolower($class) != 'parent') {
+                        $used_classes[$class] = true;
+                    }
+                    continue 2;
+            }
+        }
+        return array(
+            "source_file" => $file,
+            "declared_classes" => $declared_classes,
+            "declared_interfaces" => $declared_interfaces,
+            "declared_methods" => $declared_methods,
+            "declared_functions" => $declared_functions,
+            "used_classes" => array_diff(array_keys($used_classes), $nodeps),
+            "inheritance" => $extends,
+            "implements" => $implements,
+            );
+    }
+
+    /**
+     * Build a "provides" array from data returned by
+     * analyzeSourceCode().  The format of the built array is like
+     * this:
+     *
+     *  array(
+     *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+     *    ...
+     *  )
+     *
+     *
+     * @param array $srcinfo array with information about a source file
+     * as returned by the analyzeSourceCode() method.
+     *
+     * @return void
+     *
+     * @access private
+     *
+     */
+    function _buildProvidesArray($srcinfo)
+    {
+        if (!$this->_isValid) {
+            return false;
+        }
+        $file = basename($srcinfo['source_file']);
+        $pn = $this->getPackage();
+        $pnl = strlen($pn);
+        foreach ($srcinfo['declared_classes'] as $class) {
+            $key = "class;$class";
+            if (isset($this->_packageInfo['provides'][$key])) {
+                continue;
+            }
+            $this->_packageInfo['provides'][$key] =
+                array('file'=> $file, 'type' => 'class', 'name' => $class);
+            if (isset($srcinfo['inheritance'][$class])) {
+                $this->_packageInfo['provides'][$key]['extends'] =
+                    $srcinfo['inheritance'][$class];
+            }
+        }
+        foreach ($srcinfo['declared_methods'] as $class => $methods) {
+            foreach ($methods as $method) {
+                $function = "$class::$method";
+                $key = "function;$function";
+                if ($method{0} == '_' || !strcasecmp($method, $class) ||
+                    isset($this->_packageInfo['provides'][$key])) {
+                    continue;
+                }
+                $this->_packageInfo['provides'][$key] =
+                    array('file'=> $file, 'type' => 'function', 'name' => $function);
+            }
+        }
+
+        foreach ($srcinfo['declared_functions'] as $function) {
+            $key = "function;$function";
+            if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) {
+                continue;
+            }
+            if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+                $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+            }
+            $this->_packageInfo['provides'][$key] =
+                array('file'=> $file, 'type' => 'function', 'name' => $function);
+        }
+    }
+
+    // }}}
+}
+?>
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2.php
new file mode 100644 (file)
index 0000000..1ca412d
--- /dev/null
@@ -0,0 +1,2049 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * For error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_v2
+{
+
+    /**
+     * Parsed package information
+     * @var array
+     * @access private
+     */
+    var $_packageInfo = array();
+
+    /**
+     * path to package .tgz or false if this is a local/extracted package.xml
+     * @var string|false
+     * @access private
+     */
+    var $_archiveFile;
+
+    /**
+     * path to package .xml or false if this is an abstract parsed-from-string xml
+     * @var string|false
+     * @access private
+     */
+    var $_packageFile;
+
+    /**
+     * This is used by file analysis routines to log progress information
+     * @var PEAR_Common
+     * @access protected
+     */
+    var $_logger;
+
+    /**
+     * This is set to the highest validation level that has been validated
+     *
+     * If the package.xml is invalid or unknown, this is set to 0.  If
+     * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL.  If
+     * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING
+     * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING.  This allows validation
+     * "caching" to occur, which is particularly important for package validation, so
+     * that PHP files are not validated twice
+     * @var int
+     * @access private
+     */
+    var $_isValid = 0;
+
+    /**
+     * True if the filelist has been validated
+     * @param bool
+     */
+    var $_filesValid = false;
+
+    /**
+     * @var PEAR_Registry
+     * @access protected
+     */
+    var $_registry;
+
+    /**
+     * @var PEAR_Config
+     * @access protected
+     */
+    var $_config;
+
+    /**
+     * Optional Dependency group requested for installation
+     * @var string
+     * @access private
+     */
+    var $_requestedGroup = false;
+
+    /**
+     * @var PEAR_ErrorStack
+     * @access protected
+     */
+    var $_stack;
+
+    /**
+     * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible
+     */
+    var $_tasksNs;
+
+    /**
+     * Determines whether this packagefile was initialized only with partial package info
+     *
+     * If this package file was constructed via parsing REST, it will only contain
+     *
+     * - package name
+     * - channel name
+     * - dependencies
+     * @var boolean
+     * @access private
+     */
+    var $_incomplete = true;
+
+    /**
+     * @var PEAR_PackageFile_v2_Validator
+     */
+    var $_v2Validator;
+
+    /**
+     * The constructor merely sets up the private error stack
+     */
+    function PEAR_PackageFile_v2()
+    {
+        $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null);
+        $this->_isValid = false;
+    }
+
+    /**
+     * To make unit-testing easier
+     * @param PEAR_Frontend_*
+     * @param array options
+     * @param PEAR_Config
+     * @return PEAR_Downloader
+     * @access protected
+     */
+    function &getPEARDownloader(&$i, $o, &$c)
+    {
+        $z = &new PEAR_Downloader($i, $o, $c);
+        return $z;
+    }
+
+    /**
+     * To make unit-testing easier
+     * @param PEAR_Config
+     * @param array options
+     * @param array package name as returned from {@link PEAR_Registry::parsePackageName()}
+     * @param int PEAR_VALIDATE_* constant
+     * @return PEAR_Dependency2
+     * @access protected
+     */
+    function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING)
+    {
+        if (!class_exists('PEAR_Dependency2')) {
+            require_once 'PEAR/Dependency2.php';
+        }
+        $z = &new PEAR_Dependency2($c, $o, $p, $s);
+        return $z;
+    }
+
+    function getInstalledBinary()
+    {
+        return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] :
+            false;
+    }
+
+    /**
+     * Installation of source package has failed, attempt to download and install the
+     * binary version of this package.
+     * @param PEAR_Installer
+     * @return array|false
+     */
+    function installBinary(&$installer)
+    {
+        if (!OS_WINDOWS) {
+            $a = false;
+            return $a;
+        }
+        if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+            $releasetype = $this->getPackageType() . 'release';
+            if (!is_array($installer->getInstallPackages())) {
+                $a = false;
+                return $a;
+            }
+            foreach ($installer->getInstallPackages() as $p) {
+                if ($p->isExtension($this->_packageInfo['providesextension'])) {
+                    if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') {
+                        $a = false;
+                        return $a; // the user probably downloaded it separately
+                    }
+                }
+            }
+            if (isset($this->_packageInfo[$releasetype]['binarypackage'])) {
+                $installer->log(0, 'Attempting to download binary version of extension "' .
+                    $this->_packageInfo['providesextension'] . '"');
+                $params = $this->_packageInfo[$releasetype]['binarypackage'];
+                if (!is_array($params) || !isset($params[0])) {
+                    $params = array($params);
+                }
+                if (isset($this->_packageInfo['channel'])) {
+                    foreach ($params as $i => $param) {
+                        $params[$i] = array('channel' => $this->_packageInfo['channel'],
+                            'package' => $param, 'version' => $this->getVersion());
+                    }
+                }
+                $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(),
+                    $installer->config);
+                $verbose = $dl->config->get('verbose');
+                $dl->config->set('verbose', -1);
+                foreach ($params as $param) {
+                    PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                    $ret = $dl->download(array($param));
+                    PEAR::popErrorHandling();
+                    if (is_array($ret) && count($ret)) {
+                        break;
+                    }
+                }
+                $dl->config->set('verbose', $verbose);
+                if (is_array($ret)) {
+                    if (count($ret) == 1) {
+                        $pf = $ret[0]->getPackageFile();
+                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                        $err = $installer->install($ret[0]);
+                        PEAR::popErrorHandling();
+                        if (is_array($err)) {
+                            $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage();
+                            // "install" self, so all dependencies will work transparently
+                            $this->_registry->addPackage2($this);
+                            $installer->log(0, 'Download and install of binary extension "' .
+                                $this->_registry->parsedPackageNameToString(
+                                    array('channel' => $pf->getChannel(),
+                                          'package' => $pf->getPackage()), true) . '" successful');
+                            $a = array($ret[0], $err);
+                            return $a;
+                        }
+                        $installer->log(0, 'Download and install of binary extension "' .
+                            $this->_registry->parsedPackageNameToString(
+                                    array('channel' => $pf->getChannel(),
+                                          'package' => $pf->getPackage()), true) . '" failed');
+                    }
+                }
+            }
+        }
+        $a = false;
+        return $a;
+    }
+
+    /**
+     * @return string|false Extension name
+     */
+    function getProvidesExtension()
+    {
+        if (in_array($this->getPackageType(),
+              array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+            if (isset($this->_packageInfo['providesextension'])) {
+                return $this->_packageInfo['providesextension'];
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param string Extension name
+     * @return bool
+     */
+    function isExtension($extension)
+    {
+        if (in_array($this->getPackageType(),
+              array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+            return $this->_packageInfo['providesextension'] == $extension;
+        }
+        return false;
+    }
+
+    /**
+     * Tests whether every part of the package.xml 1.0 is represented in
+     * this package.xml 2.0
+     * @param PEAR_PackageFile_v1
+     * @return bool
+     */
+    function isEquivalent($pf1)
+    {
+        if (!$pf1) {
+            return true;
+        }
+        if ($this->getPackageType() == 'bundle') {
+            return false;
+        }
+        $this->_stack->getErrors(true);
+        if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) {
+            return false;
+        }
+        $pass = true;
+        if ($pf1->getPackage() != $this->getPackage()) {
+            $this->_differentPackage($pf1->getPackage());
+            $pass = false;
+        }
+        if ($pf1->getVersion() != $this->getVersion()) {
+            $this->_differentVersion($pf1->getVersion());
+            $pass = false;
+        }
+        if (trim($pf1->getSummary()) != $this->getSummary()) {
+            $this->_differentSummary($pf1->getSummary());
+            $pass = false;
+        }
+        if (preg_replace('/\s+/', '', $pf1->getDescription()) !=
+              preg_replace('/\s+/', '', $this->getDescription())) {
+            $this->_differentDescription($pf1->getDescription());
+            $pass = false;
+        }
+        if ($pf1->getState() != $this->getState()) {
+            $this->_differentState($pf1->getState());
+            $pass = false;
+        }
+        if (!strstr(preg_replace('/\s+/', '', $this->getNotes()),
+              preg_replace('/\s+/', '', $pf1->getNotes()))) {
+            $this->_differentNotes($pf1->getNotes());
+            $pass = false;
+        }
+        $mymaintainers = $this->getMaintainers();
+        $yourmaintainers = $pf1->getMaintainers();
+        for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) {
+            $reset = false;
+            for ($i2 = 0; $i2 < count($mymaintainers); $i2++) {
+                if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) {
+                    if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) {
+                        $this->_differentRole($mymaintainers[$i2]['handle'],
+                            $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']);
+                        $pass = false;
+                    }
+                    if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) {
+                        $this->_differentEmail($mymaintainers[$i2]['handle'],
+                            $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']);
+                        $pass = false;
+                    }
+                    if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) {
+                        $this->_differentName($mymaintainers[$i2]['handle'],
+                            $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']);
+                        $pass = false;
+                    }
+                    unset($mymaintainers[$i2]);
+                    $mymaintainers = array_values($mymaintainers);
+                    unset($yourmaintainers[$i1]);
+                    $yourmaintainers = array_values($yourmaintainers);
+                    $reset = true;
+                    break;
+                }
+            }
+            if ($reset) {
+                $i1 = -1;
+            }
+        }
+        $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers);
+        $filelist = $this->getFilelist();
+        foreach ($pf1->getFilelist() as $file => $atts) {
+            if (!isset($filelist[$file])) {
+                $this->_missingFile($file);
+                $pass = false;
+            }
+        }
+        return $pass;
+    }
+
+    function _differentPackage($package)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('package' => $package,
+            'self' => $this->getPackage()),
+            'package.xml 1.0 package "%package%" does not match "%self%"');
+    }
+
+    function _differentVersion($version)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('version' => $version,
+            'self' => $this->getVersion()),
+            'package.xml 1.0 version "%version%" does not match "%self%"');
+    }
+
+    function _differentState($state)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('state' => $state,
+            'self' => $this->getState()),
+            'package.xml 1.0 state "%state%" does not match "%self%"');
+    }
+
+    function _differentRole($handle, $role, $selfrole)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+            'role' => $role, 'self' => $selfrole),
+            'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"');
+    }
+
+    function _differentEmail($handle, $email, $selfemail)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+            'email' => $email, 'self' => $selfemail),
+            'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"');
+    }
+
+    function _differentName($handle, $name, $selfname)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+            'name' => $name, 'self' => $selfname),
+            'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"');
+    }
+
+    function _unmatchedMaintainers($my, $yours)
+    {
+        if ($my) {
+            array_walk($my, create_function('&$i, $k', '$i = $i["handle"];'));
+            $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my),
+                'package.xml 2.0 has unmatched extra maintainers "%handles%"');
+        }
+        if ($yours) {
+            array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];'));
+            $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours),
+                'package.xml 1.0 has unmatched extra maintainers "%handles%"');
+        }
+    }
+
+    function _differentNotes($notes)
+    {
+        $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...';
+        $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() :
+            substr($this->getNotes(), 0, 24) . '...';
+        $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes,
+            'self' => $truncmynotes),
+            'package.xml 1.0 release notes "%notes%" do not match "%self%"');
+    }
+
+    function _differentSummary($summary)
+    {
+        $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...';
+        $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() :
+            substr($this->getsummary(), 0, 24) . '...';
+        $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary,
+            'self' => $truncmysummary),
+            'package.xml 1.0 summary "%summary%" does not match "%self%"');
+    }
+
+    function _differentDescription($description)
+    {
+        $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...');
+        $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() :
+            substr($this->getdescription(), 0, 24) . '...');
+        $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription,
+            'self' => $truncmydescription),
+            'package.xml 1.0 description "%description%" does not match "%self%"');
+    }
+
+    function _missingFile($file)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+            'package.xml 1.0 file "%file%" is not present in <contents>');
+    }
+
+    /**
+     * WARNING - do not use this function unless you know what you're doing
+     */
+    function setRawState($state)
+    {
+        if (!isset($this->_packageInfo['stability'])) {
+            $this->_packageInfo['stability'] = array();
+        }
+        $this->_packageInfo['stability']['release'] = $state;
+    }
+
+    /**
+     * WARNING - do not use this function unless you know what you're doing
+     */
+    function setRawCompatible($compatible)
+    {
+        $this->_packageInfo['compatible'] = $compatible;
+    }
+
+    /**
+     * WARNING - do not use this function unless you know what you're doing
+     */
+    function setRawPackage($package)
+    {
+        $this->_packageInfo['name'] = $package;
+    }
+
+    /**
+     * WARNING - do not use this function unless you know what you're doing
+     */
+    function setRawChannel($channel)
+    {
+        $this->_packageInfo['channel'] = $channel;
+    }
+
+    function setRequestedGroup($group)
+    {
+        $this->_requestedGroup = $group;
+    }
+
+    function getRequestedGroup()
+    {
+        if (isset($this->_requestedGroup)) {
+            return $this->_requestedGroup;
+        }
+        return false;
+    }
+
+    /**
+     * For saving in the registry.
+     *
+     * Set the last version that was installed
+     * @param string
+     */
+    function setLastInstalledVersion($version)
+    {
+        $this->_packageInfo['_lastversion'] = $version;
+    }
+
+    /**
+     * @return string|false
+     */
+    function getLastInstalledVersion()
+    {
+        if (isset($this->_packageInfo['_lastversion'])) {
+            return $this->_packageInfo['_lastversion'];
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether this package.xml has post-install scripts or not
+     * @return array|false
+     */
+    function listPostinstallScripts()
+    {
+        $filelist = $this->getFilelist();
+        $contents = $this->getContents();
+        $contents = $contents['dir']['file'];
+        if (!is_array($contents) || !isset($contents[0])) {
+            $contents = array($contents);
+        }
+        $taskfiles = array();
+        foreach ($contents as $file) {
+            $atts = $file['attribs'];
+            unset($file['attribs']);
+            if (count($file)) {
+                $taskfiles[$atts['name']] = $file;
+            }
+        }
+        $common = new PEAR_Common;
+        $common->debug = $this->_config->get('verbose');
+        $this->_scripts = array();
+        $ret = array();
+        foreach ($taskfiles as $name => $tasks) {
+            if (!isset($filelist[$name])) {
+                // ignored files will not be in the filelist
+                continue;
+            }
+            $atts = $filelist[$name];
+            foreach ($tasks as $tag => $raw) {
+                $task = $this->getTask($tag);
+                $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL);
+                if ($task->isScript()) {
+                    $ret[] = $filelist[$name]['installed_as'];
+                }
+            }
+        }
+        if (count($ret)) {
+            return $ret;
+        }
+        return false;
+    }
+
+    /**
+     * Initialize post-install scripts for running
+     *
+     * This method can be used to detect post-install scripts, as the return value
+     * indicates whether any exist
+     * @return bool
+     */
+    function initPostinstallScripts()
+    {
+        $filelist = $this->getFilelist();
+        $contents = $this->getContents();
+        $contents = $contents['dir']['file'];
+        if (!is_array($contents) || !isset($contents[0])) {
+            $contents = array($contents);
+        }
+        $taskfiles = array();
+        foreach ($contents as $file) {
+            $atts = $file['attribs'];
+            unset($file['attribs']);
+            if (count($file)) {
+                $taskfiles[$atts['name']] = $file;
+            }
+        }
+        $common = new PEAR_Common;
+        $common->debug = $this->_config->get('verbose');
+        $this->_scripts = array();
+        foreach ($taskfiles as $name => $tasks) {
+            if (!isset($filelist[$name])) {
+                // file was not installed due to installconditions
+                continue;
+            }
+            $atts = $filelist[$name];
+            foreach ($tasks as $tag => $raw) {
+                $taskname = $this->getTask($tag);
+                $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL);
+                if (!$task->isScript()) {
+                    continue; // scripts are only handled after installation
+                }
+                $lastversion = isset($this->_packageInfo['_lastversion']) ?
+                    $this->_packageInfo['_lastversion'] : null;
+                $task->init($raw, $atts, $lastversion);
+                $res = $task->startSession($this, $atts['installed_as']);
+                if (!$res) {
+                    continue; // skip this file
+                }
+                if (PEAR::isError($res)) {
+                    return $res;
+                }
+                $assign = &$task;
+                $this->_scripts[] = &$assign;
+            }
+        }
+        if (count($this->_scripts)) {
+            return true;
+        }
+        return false;
+    }
+
+    function runPostinstallScripts()
+    {
+        if ($this->initPostinstallScripts()) {
+            $ui = &PEAR_Frontend::singleton();
+            if ($ui) {
+                $ui->runPostinstallScripts($this->_scripts, $this);
+            }
+        }
+    }
+
+
+    /**
+     * Convert a recursive set of <dir> and <file> tags into a single <dir> tag with
+     * <file> tags.
+     */
+    function flattenFilelist()
+    {
+        if (isset($this->_packageInfo['bundle'])) {
+            return;
+        }
+        $filelist = array();
+        if (isset($this->_packageInfo['contents']['dir']['dir'])) {
+            $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']);
+            if (!isset($filelist[1])) {
+                $filelist = $filelist[0];
+            }
+            $this->_packageInfo['contents']['dir']['file'] = $filelist;
+            unset($this->_packageInfo['contents']['dir']['dir']);
+        } else {
+            // else already flattened but check for baseinstalldir propagation
+            if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) {
+                if (isset($this->_packageInfo['contents']['dir']['file'][0])) {
+                    foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) {
+                        if (isset($file['attribs']['baseinstalldir'])) {
+                            continue;
+                        }
+                        $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir']
+                            = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
+                    }
+                } else {
+                    if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) {
+                       $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir']
+                            = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param array the final flattened file list
+     * @param array the current directory being processed
+     * @param string|false any recursively inherited baeinstalldir attribute
+     * @param string private recursion variable
+     * @return array
+     * @access protected
+     */
+    function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '')
+    {
+        if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) {
+            $baseinstall = $dir['attribs']['baseinstalldir'];
+        }
+        if (isset($dir['dir'])) {
+            if (!isset($dir['dir'][0])) {
+                $dir['dir'] = array($dir['dir']);
+            }
+            foreach ($dir['dir'] as $subdir) {
+                if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) {
+                    $name = '*unknown*';
+                } else {
+                    $name = $subdir['attribs']['name'];
+                }
+                $newpath = empty($path) ? $name :
+                    $path . '/' . $name;
+                $this->_getFlattenedFilelist($files, $subdir,
+                    $baseinstall, $newpath);
+            }
+        }
+        if (isset($dir['file'])) {
+            if (!isset($dir['file'][0])) {
+                $dir['file'] = array($dir['file']);
+            }
+            foreach ($dir['file'] as $file) {
+                $attrs = $file['attribs'];
+                $name = $attrs['name'];
+                if ($baseinstall && !isset($attrs['baseinstalldir'])) {
+                    $attrs['baseinstalldir'] = $baseinstall;
+                }
+                $attrs['name'] = empty($path) ? $name : $path . '/' . $name;
+                $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+                    $attrs['name']);
+                $file['attribs'] = $attrs;
+                $files[] = $file;
+            }
+        }
+    }
+
+    function setConfig(&$config)
+    {
+        $this->_config = &$config;
+        $this->_registry = &$config->getRegistry();
+    }
+
+    function setLogger(&$logger)
+    {
+        if (!is_object($logger) || !method_exists($logger, 'log')) {
+            return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
+        }
+        $this->_logger = &$logger;
+    }
+
+    /**
+     * WARNING - do not use this function directly unless you know what you're doing
+     */
+    function setDeps($deps)
+    {
+        $this->_packageInfo['dependencies'] = $deps;
+    }
+
+    /**
+     * WARNING - do not use this function directly unless you know what you're doing
+     */
+    function setCompatible($compat)
+    {
+        $this->_packageInfo['compatible'] = $compat;
+    }
+
+    function setPackagefile($file, $archive = false)
+    {
+        $this->_packageFile = $file;
+        $this->_archiveFile = $archive ? $archive : $file;
+    }
+
+    /**
+     * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+     * @param boolean determines whether to purge the error stack after retrieving
+     * @return array
+     */
+    function getValidationWarnings($purge = true)
+    {
+        return $this->_stack->getErrors($purge);
+    }
+
+    function getPackageFile()
+    {
+        return $this->_packageFile;
+    }
+
+    function getArchiveFile()
+    {
+        return $this->_archiveFile;
+    }
+
+
+    /**
+     * Directly set the array that defines this packagefile
+     *
+     * WARNING: no validation.  This should only be performed by internal methods
+     * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2
+     * @param array
+     */
+    function fromArray($pinfo)
+    {
+        unset($pinfo['old']);
+        unset($pinfo['xsdversion']);
+        // If the changelog isn't an array then it was passed in as an empty tag
+        if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) {
+          unset($pinfo['changelog']);
+        }
+        $this->_incomplete = false;
+        $this->_packageInfo = $pinfo;
+    }
+
+    function isIncomplete()
+    {
+        return $this->_incomplete;
+    }
+
+    /**
+     * @return array
+     */
+    function toArray($forreg = false)
+    {
+        if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
+            return false;
+        }
+        return $this->getArray($forreg);
+    }
+
+    function getArray($forReg = false)
+    {
+        if ($forReg) {
+            $arr = $this->_packageInfo;
+            $arr['old'] = array();
+            $arr['old']['version'] = $this->getVersion();
+            $arr['old']['release_date'] = $this->getDate();
+            $arr['old']['release_state'] = $this->getState();
+            $arr['old']['release_license'] = $this->getLicense();
+            $arr['old']['release_notes'] = $this->getNotes();
+            $arr['old']['release_deps'] = $this->getDeps();
+            $arr['old']['maintainers'] = $this->getMaintainers();
+            $arr['xsdversion'] = '2.0';
+            return $arr;
+        } else {
+            $info = $this->_packageInfo;
+            unset($info['dirtree']);
+            if (isset($info['_lastversion'])) {
+                unset($info['_lastversion']);
+            }
+            if (isset($info['#binarypackage'])) {
+                unset($info['#binarypackage']);
+            }
+            return $info;
+        }
+    }
+
+    function packageInfo($field)
+    {
+        $arr = $this->getArray(true);
+        if ($field == 'state') {
+            return $arr['stability']['release'];
+        }
+        if ($field == 'api-version') {
+            return $arr['version']['api'];
+        }
+        if ($field == 'api-state') {
+            return $arr['stability']['api'];
+        }
+        if (isset($arr['old'][$field])) {
+            if (!is_string($arr['old'][$field])) {
+                return null;
+            }
+            return $arr['old'][$field];
+        }
+        if (isset($arr[$field])) {
+            if (!is_string($arr[$field])) {
+                return null;
+            }
+            return $arr[$field];
+        }
+        return null;
+    }
+
+    function getName()
+    {
+        return $this->getPackage();
+    }
+
+    function getPackage()
+    {
+        if (isset($this->_packageInfo['name'])) {
+            return $this->_packageInfo['name'];
+        }
+        return false;
+    }
+
+    function getChannel()
+    {
+        if (isset($this->_packageInfo['uri'])) {
+            return '__uri';
+        }
+        if (isset($this->_packageInfo['channel'])) {
+            return strtolower($this->_packageInfo['channel']);
+        }
+        return false;
+    }
+
+    function getUri()
+    {
+        if (isset($this->_packageInfo['uri'])) {
+            return $this->_packageInfo['uri'];
+        }
+        return false;
+    }
+
+    function getExtends()
+    {
+        if (isset($this->_packageInfo['extends'])) {
+            return $this->_packageInfo['extends'];
+        }
+        return false;
+    }
+
+    function getSummary()
+    {
+        if (isset($this->_packageInfo['summary'])) {
+            return $this->_packageInfo['summary'];
+        }
+        return false;
+    }
+
+    function getDescription()
+    {
+        if (isset($this->_packageInfo['description'])) {
+            return $this->_packageInfo['description'];
+        }
+        return false;
+    }
+
+    function getMaintainers($raw = false)
+    {
+        if (!isset($this->_packageInfo['lead'])) {
+            return false;
+        }
+        if ($raw) {
+            $ret = array('lead' => $this->_packageInfo['lead']);
+            (isset($this->_packageInfo['developer'])) ?
+                $ret['developer'] = $this->_packageInfo['developer'] :null;
+            (isset($this->_packageInfo['contributor'])) ?
+                $ret['contributor'] = $this->_packageInfo['contributor'] :null;
+            (isset($this->_packageInfo['helper'])) ?
+                $ret['helper'] = $this->_packageInfo['helper'] :null;
+            return $ret;
+        } else {
+            $ret = array();
+            $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] :
+                array($this->_packageInfo['lead']);
+            foreach ($leads as $lead) {
+                $s = $lead;
+                $s['handle'] = $s['user'];
+                unset($s['user']);
+                $s['role'] = 'lead';
+                $ret[] = $s;
+            }
+            if (isset($this->_packageInfo['developer'])) {
+                $leads = isset($this->_packageInfo['developer'][0]) ?
+                    $this->_packageInfo['developer'] :
+                    array($this->_packageInfo['developer']);
+                foreach ($leads as $maintainer) {
+                    $s = $maintainer;
+                    $s['handle'] = $s['user'];
+                    unset($s['user']);
+                    $s['role'] = 'developer';
+                    $ret[] = $s;
+                }
+            }
+            if (isset($this->_packageInfo['contributor'])) {
+                $leads = isset($this->_packageInfo['contributor'][0]) ?
+                    $this->_packageInfo['contributor'] :
+                    array($this->_packageInfo['contributor']);
+                foreach ($leads as $maintainer) {
+                    $s = $maintainer;
+                    $s['handle'] = $s['user'];
+                    unset($s['user']);
+                    $s['role'] = 'contributor';
+                    $ret[] = $s;
+                }
+            }
+            if (isset($this->_packageInfo['helper'])) {
+                $leads = isset($this->_packageInfo['helper'][0]) ?
+                    $this->_packageInfo['helper'] :
+                    array($this->_packageInfo['helper']);
+                foreach ($leads as $maintainer) {
+                    $s = $maintainer;
+                    $s['handle'] = $s['user'];
+                    unset($s['user']);
+                    $s['role'] = 'helper';
+                    $ret[] = $s;
+                }
+            }
+            return $ret;
+        }
+        return false;
+    }
+
+    function getLeads()
+    {
+        if (isset($this->_packageInfo['lead'])) {
+            return $this->_packageInfo['lead'];
+        }
+        return false;
+    }
+
+    function getDevelopers()
+    {
+        if (isset($this->_packageInfo['developer'])) {
+            return $this->_packageInfo['developer'];
+        }
+        return false;
+    }
+
+    function getContributors()
+    {
+        if (isset($this->_packageInfo['contributor'])) {
+            return $this->_packageInfo['contributor'];
+        }
+        return false;
+    }
+
+    function getHelpers()
+    {
+        if (isset($this->_packageInfo['helper'])) {
+            return $this->_packageInfo['helper'];
+        }
+        return false;
+    }
+
+    function setDate($date)
+    {
+        if (!isset($this->_packageInfo['date'])) {
+            // ensure that the extends tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('time', 'version',
+                    'stability', 'license', 'notes', 'contents', 'compatible',
+                    'dependencies', 'providesextension', 'srcpackage', 'srcuri',
+                    'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
+                    'zendextbinrelease', 'bundle', 'changelog'), array(), 'date');
+        }
+        $this->_packageInfo['date'] = $date;
+        $this->_isValid = 0;
+    }
+
+    function setTime($time)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['time'])) {
+            // ensure that the time tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                    array('version',
+                    'stability', 'license', 'notes', 'contents', 'compatible',
+                    'dependencies', 'providesextension', 'srcpackage', 'srcuri',
+                    'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
+                    'zendextbinrelease', 'bundle', 'changelog'), $time, 'time');
+        }
+        $this->_packageInfo['time'] = $time;
+    }
+
+    function getDate()
+    {
+        if (isset($this->_packageInfo['date'])) {
+            return $this->_packageInfo['date'];
+        }
+        return false;
+    }
+
+    function getTime()
+    {
+        if (isset($this->_packageInfo['time'])) {
+            return $this->_packageInfo['time'];
+        }
+        return false;
+    }
+
+    /**
+     * @param package|api version category to return
+     */
+    function getVersion($key = 'release')
+    {
+        if (isset($this->_packageInfo['version'][$key])) {
+            return $this->_packageInfo['version'][$key];
+        }
+        return false;
+    }
+
+    function getStability()
+    {
+        if (isset($this->_packageInfo['stability'])) {
+            return $this->_packageInfo['stability'];
+        }
+        return false;
+    }
+
+    function getState($key = 'release')
+    {
+        if (isset($this->_packageInfo['stability'][$key])) {
+            return $this->_packageInfo['stability'][$key];
+        }
+        return false;
+    }
+
+    function getLicense($raw = false)
+    {
+        if (isset($this->_packageInfo['license'])) {
+            if ($raw) {
+                return $this->_packageInfo['license'];
+            }
+            if (is_array($this->_packageInfo['license'])) {
+                return $this->_packageInfo['license']['_content'];
+            } else {
+                return $this->_packageInfo['license'];
+            }
+        }
+        return false;
+    }
+
+    function getLicenseLocation()
+    {
+        if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) {
+            return false;
+        }
+        return $this->_packageInfo['license']['attribs'];
+    }
+
+    function getNotes()
+    {
+        if (isset($this->_packageInfo['notes'])) {
+            return $this->_packageInfo['notes'];
+        }
+        return false;
+    }
+
+    /**
+     * Return the <usesrole> tag contents, if any
+     * @return array|false
+     */
+    function getUsesrole()
+    {
+        if (isset($this->_packageInfo['usesrole'])) {
+            return $this->_packageInfo['usesrole'];
+        }
+        return false;
+    }
+
+    /**
+     * Return the <usestask> tag contents, if any
+     * @return array|false
+     */
+    function getUsestask()
+    {
+        if (isset($this->_packageInfo['usestask'])) {
+            return $this->_packageInfo['usestask'];
+        }
+        return false;
+    }
+
+    /**
+     * This should only be used to retrieve filenames and install attributes
+     */
+    function getFilelist($preserve = false)
+    {
+        if (isset($this->_packageInfo['filelist']) && !$preserve) {
+            return $this->_packageInfo['filelist'];
+        }
+        $this->flattenFilelist();
+        if ($contents = $this->getContents()) {
+            $ret = array();
+            if (!isset($contents['dir'])) {
+                return false;
+            }
+            if (!isset($contents['dir']['file'][0])) {
+                $contents['dir']['file'] = array($contents['dir']['file']);
+            }
+            foreach ($contents['dir']['file'] as $file) {
+                $name = $file['attribs']['name'];
+                if (!$preserve) {
+                    $file = $file['attribs'];
+                }
+                $ret[$name] = $file;
+            }
+            if (!$preserve) {
+                $this->_packageInfo['filelist'] = $ret;
+            }
+            return $ret;
+        }
+        return false;
+    }
+
+    /**
+     * Return configure options array, if any
+     *
+     * @return array|false
+     */
+    function getConfigureOptions()
+    {
+        if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+            return false;
+        }
+
+        $releases = $this->getReleases();
+        if (isset($releases[0])) {
+            $releases = $releases[0];
+        }
+
+        if (isset($releases['configureoption'])) {
+            if (!isset($releases['configureoption'][0])) {
+                $releases['configureoption'] = array($releases['configureoption']);
+            }
+
+            for ($i = 0; $i < count($releases['configureoption']); $i++) {
+                $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs'];
+            }
+
+            return $releases['configureoption'];
+        }
+
+        return false;
+    }
+
+    /**
+     * This is only used at install-time, after all serialization
+     * is over.
+     */
+    function resetFilelist()
+    {
+        $this->_packageInfo['filelist'] = array();
+    }
+
+    /**
+     * Retrieve a list of files that should be installed on this computer
+     * @return array
+     */
+    function getInstallationFilelist($forfilecheck = false)
+    {
+        $contents = $this->getFilelist(true);
+        if (isset($contents['dir']['attribs']['baseinstalldir'])) {
+            $base = $contents['dir']['attribs']['baseinstalldir'];
+        }
+        if (isset($this->_packageInfo['bundle'])) {
+            return PEAR::raiseError(
+                'Exception: bundles should be handled in download code only');
+        }
+        $release = $this->getReleases();
+        if ($release) {
+            if (!isset($release[0])) {
+                if (!isset($release['installconditions']) && !isset($release['filelist'])) {
+                    if ($forfilecheck) {
+                        return $this->getFilelist();
+                    }
+                    return $contents;
+                }
+                $release = array($release);
+            }
+            $depchecker = &$this->getPEARDependency2($this->_config, array(),
+                array('channel' => $this->getChannel(), 'package' => $this->getPackage()),
+                PEAR_VALIDATE_INSTALLING);
+            foreach ($release as $instance) {
+                if (isset($instance['installconditions'])) {
+                    $installconditions = $instance['installconditions'];
+                    if (is_array($installconditions)) {
+                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                        foreach ($installconditions as $type => $conditions) {
+                            if (!isset($conditions[0])) {
+                                $conditions = array($conditions);
+                            }
+                            foreach ($conditions as $condition) {
+                                $ret = $depchecker->{"validate{$type}Dependency"}($condition);
+                                if (PEAR::isError($ret)) {
+                                    PEAR::popErrorHandling();
+                                    continue 3; // skip this release
+                                }
+                            }
+                        }
+                        PEAR::popErrorHandling();
+                    }
+                }
+                // this is the release to use
+                if (isset($instance['filelist'])) {
+                    // ignore files
+                    if (isset($instance['filelist']['ignore'])) {
+                        $ignore = isset($instance['filelist']['ignore'][0]) ?
+                            $instance['filelist']['ignore'] :
+                            array($instance['filelist']['ignore']);
+                        foreach ($ignore as $ig) {
+                            unset ($contents[$ig['attribs']['name']]);
+                        }
+                    }
+                    // install files as this name
+                    if (isset($instance['filelist']['install'])) {
+                        $installas = isset($instance['filelist']['install'][0]) ?
+                            $instance['filelist']['install'] :
+                            array($instance['filelist']['install']);
+                        foreach ($installas as $as) {
+                            $contents[$as['attribs']['name']]['attribs']['install-as'] =
+                                $as['attribs']['as'];
+                        }
+                    }
+                }
+                if ($forfilecheck) {
+                    foreach ($contents as $file => $attrs) {
+                        $contents[$file] = $attrs['attribs'];
+                    }
+                }
+                return $contents;
+            }
+        } else { // simple release - no installconditions or install-as
+            if ($forfilecheck) {
+                return $this->getFilelist();
+            }
+            return $contents;
+        }
+        // no releases matched
+        return PEAR::raiseError('No releases in package.xml matched the existing operating ' .
+            'system, extensions installed, or architecture, cannot install');
+    }
+
+    /**
+     * This is only used at install-time, after all serialization
+     * is over.
+     * @param string file name
+     * @param string installed path
+     */
+    function setInstalledAs($file, $path)
+    {
+        if ($path) {
+            return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+        }
+        unset($this->_packageInfo['filelist'][$file]['installed_as']);
+    }
+
+    function getInstalledLocation($file)
+    {
+        if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) {
+            return $this->_packageInfo['filelist'][$file]['installed_as'];
+        }
+        return false;
+    }
+
+    /**
+     * This is only used at install-time, after all serialization
+     * is over.
+     */
+    function installedFile($file, $atts)
+    {
+        if (isset($this->_packageInfo['filelist'][$file])) {
+            $this->_packageInfo['filelist'][$file] =
+                array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
+        } else {
+            $this->_packageInfo['filelist'][$file] = $atts['attribs'];
+        }
+    }
+
+    /**
+     * Retrieve the contents tag
+     */
+    function getContents()
+    {
+        if (isset($this->_packageInfo['contents'])) {
+            return $this->_packageInfo['contents'];
+        }
+        return false;
+    }
+
+    /**
+     * @param string full path to file
+     * @param string attribute name
+     * @param string attribute value
+     * @param int risky but fast - use this to choose a file based on its position in the list
+     *            of files.  Index is zero-based like PHP arrays.
+     * @return bool success of operation
+     */
+    function setFileAttribute($filename, $attr, $value, $index = false)
+    {
+        $this->_isValid = 0;
+        if (in_array($attr, array('role', 'name', 'baseinstalldir'))) {
+            $this->_filesValid = false;
+        }
+        if ($index !== false &&
+              isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) {
+            $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value;
+            return true;
+        }
+        if (!isset($this->_packageInfo['contents']['dir']['file'])) {
+            return false;
+        }
+        $files = $this->_packageInfo['contents']['dir']['file'];
+        if (!isset($files[0])) {
+            $files = array($files);
+            $ind = false;
+        } else {
+            $ind = true;
+        }
+        foreach ($files as $i => $file) {
+            if (isset($file['attribs'])) {
+                if ($file['attribs']['name'] == $filename) {
+                    if ($ind) {
+                        $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value;
+                    } else {
+                        $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value;
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    function setDirtree($path)
+    {
+        if (!isset($this->_packageInfo['dirtree'])) {
+            $this->_packageInfo['dirtree'] = array();
+        }
+        $this->_packageInfo['dirtree'][$path] = true;
+    }
+
+    function getDirtree()
+    {
+        if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
+            return $this->_packageInfo['dirtree'];
+        }
+        return false;
+    }
+
+    function resetDirtree()
+    {
+        unset($this->_packageInfo['dirtree']);
+    }
+
+    /**
+     * Determines whether this package claims it is compatible with the version of
+     * the package that has a recommended version dependency
+     * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package
+     * @return boolean
+     */
+    function isCompatible($pf)
+    {
+        if (!isset($this->_packageInfo['compatible'])) {
+            return false;
+        }
+        if (!isset($this->_packageInfo['channel'])) {
+            return false;
+        }
+        $me = $pf->getVersion();
+        $compatible = $this->_packageInfo['compatible'];
+        if (!isset($compatible[0])) {
+            $compatible = array($compatible);
+        }
+        $found = false;
+        foreach ($compatible as $info) {
+            if (strtolower($info['name']) == strtolower($pf->getPackage())) {
+                if (strtolower($info['channel']) == strtolower($pf->getChannel())) {
+                    $found = true;
+                    break;
+                }
+            }
+        }
+        if (!$found) {
+            return false;
+        }
+        if (isset($info['exclude'])) {
+            if (!isset($info['exclude'][0])) {
+                $info['exclude'] = array($info['exclude']);
+            }
+            foreach ($info['exclude'] as $exclude) {
+                if (version_compare($me, $exclude, '==')) {
+                    return false;
+                }
+            }
+        }
+        if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return array|false
+     */
+    function getCompatible()
+    {
+        if (isset($this->_packageInfo['compatible'])) {
+            return $this->_packageInfo['compatible'];
+        }
+        return false;
+    }
+
+    function getDependencies()
+    {
+        if (isset($this->_packageInfo['dependencies'])) {
+            return $this->_packageInfo['dependencies'];
+        }
+        return false;
+    }
+
+    function isSubpackageOf($p)
+    {
+        return $p->isSubpackage($this);
+    }
+
+    /**
+     * Determines whether the passed in package is a subpackage of this package.
+     *
+     * No version checking is done, only name verification.
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @return bool
+     */
+    function isSubpackage($p)
+    {
+        $sub = array();
+        if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) {
+            $sub = $this->_packageInfo['dependencies']['required']['subpackage'];
+            if (!isset($sub[0])) {
+                $sub = array($sub);
+            }
+        }
+        if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) {
+            $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage'];
+            if (!isset($sub1[0])) {
+                $sub1 = array($sub1);
+            }
+            $sub = array_merge($sub, $sub1);
+        }
+        if (isset($this->_packageInfo['dependencies']['group'])) {
+            $group = $this->_packageInfo['dependencies']['group'];
+            if (!isset($group[0])) {
+                $group = array($group);
+            }
+            foreach ($group as $deps) {
+                if (isset($deps['subpackage'])) {
+                    $sub2 = $deps['subpackage'];
+                    if (!isset($sub2[0])) {
+                        $sub2 = array($sub2);
+                    }
+                    $sub = array_merge($sub, $sub2);
+                }
+            }
+        }
+        foreach ($sub as $dep) {
+            if (strtolower($dep['name']) == strtolower($p->getPackage())) {
+                if (isset($dep['channel'])) {
+                    if (strtolower($dep['channel']) == strtolower($p->getChannel())) {
+                        return true;
+                    }
+                } else {
+                    if ($dep['uri'] == $p->getURI()) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    function dependsOn($package, $channel)
+    {
+        if (!($deps = $this->getDependencies())) {
+            return false;
+        }
+        foreach (array('package', 'subpackage') as $type) {
+            foreach (array('required', 'optional') as $needed) {
+                if (isset($deps[$needed][$type])) {
+                    if (!isset($deps[$needed][$type][0])) {
+                        $deps[$needed][$type] = array($deps[$needed][$type]);
+                    }
+                    foreach ($deps[$needed][$type] as $dep) {
+                        $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+                        if (strtolower($dep['name']) == strtolower($package) &&
+                              $depchannel == $channel) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            if (isset($deps['group'])) {
+                if (!isset($deps['group'][0])) {
+                    $dep['group'] = array($deps['group']);
+                }
+                foreach ($deps['group'] as $group) {
+                    if (isset($group[$type])) {
+                        if (!is_array($group[$type])) {
+                            $group[$type] = array($group[$type]);
+                        }
+                        foreach ($group[$type] as $dep) {
+                            $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+                            if (strtolower($dep['name']) == strtolower($package) &&
+                                  $depchannel == $channel) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the contents of a dependency group
+     * @param string
+     * @return array|false
+     */
+    function getDependencyGroup($name)
+    {
+        $name = strtolower($name);
+        if (!isset($this->_packageInfo['dependencies']['group'])) {
+            return false;
+        }
+        $groups = $this->_packageInfo['dependencies']['group'];
+        if (!isset($groups[0])) {
+            $groups = array($groups);
+        }
+        foreach ($groups as $group) {
+            if (strtolower($group['attribs']['name']) == $name) {
+                return $group;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve a partial package.xml 1.0 representation of dependencies
+     *
+     * a very limited representation of dependencies is returned by this method.
+     * The <exclude> tag for excluding certain versions of a dependency is
+     * completely ignored.  In addition, dependency groups are ignored, with the
+     * assumption that all dependencies in dependency groups are also listed in
+     * the optional group that work with all dependency groups
+     * @param boolean return package.xml 2.0 <dependencies> tag
+     * @return array|false
+     */
+    function getDeps($raw = false, $nopearinstaller = false)
+    {
+        if (isset($this->_packageInfo['dependencies'])) {
+            if ($raw) {
+                return $this->_packageInfo['dependencies'];
+            }
+            $ret = array();
+            $map = array(
+                'php' => 'php',
+                'package' => 'pkg',
+                'subpackage' => 'pkg',
+                'extension' => 'ext',
+                'os' => 'os',
+                'pearinstaller' => 'pkg',
+                );
+            foreach (array('required', 'optional') as $type) {
+                $optional = ($type == 'optional') ? 'yes' : 'no';
+                if (!isset($this->_packageInfo['dependencies'][$type])
+                    || empty($this->_packageInfo['dependencies'][$type])) {
+                    continue;
+                }
+                foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) {
+                    if ($dtype == 'pearinstaller' && $nopearinstaller) {
+                        continue;
+                    }
+                    if (!isset($deps[0])) {
+                        $deps = array($deps);
+                    }
+                    foreach ($deps as $dep) {
+                        if (!isset($map[$dtype])) {
+                            // no support for arch type
+                            continue;
+                        }
+                        if ($dtype == 'pearinstaller') {
+                            $dep['name'] = 'PEAR';
+                            $dep['channel'] = 'pear.php.net';
+                        }
+                        $s = array('type' => $map[$dtype]);
+                        if (isset($dep['channel'])) {
+                            $s['channel'] = $dep['channel'];
+                        }
+                        if (isset($dep['uri'])) {
+                            $s['uri'] = $dep['uri'];
+                        }
+                        if (isset($dep['name'])) {
+                            $s['name'] = $dep['name'];
+                        }
+                        if (isset($dep['conflicts'])) {
+                            $s['rel'] = 'not';
+                        } else {
+                            if (!isset($dep['min']) &&
+                                  !isset($dep['max'])) {
+                                $s['rel'] = 'has';
+                                $s['optional'] = $optional;
+                            } elseif (isset($dep['min']) &&
+                                  isset($dep['max'])) {
+                                $s['rel'] = 'ge';
+                                $s1 = $s;
+                                $s1['rel'] = 'le';
+                                $s['version'] = $dep['min'];
+                                $s1['version'] = $dep['max'];
+                                if (isset($dep['channel'])) {
+                                    $s1['channel'] = $dep['channel'];
+                                }
+                                if ($dtype != 'php') {
+                                    $s['name'] = $dep['name'];
+                                    $s1['name'] = $dep['name'];
+                                }
+                                $s['optional'] = $optional;
+                                $s1['optional'] = $optional;
+                                $ret[] = $s1;
+                            } elseif (isset($dep['min'])) {
+                                if (isset($dep['exclude']) &&
+                                      $dep['exclude'] == $dep['min']) {
+                                    $s['rel'] = 'gt';
+                                } else {
+                                    $s['rel'] = 'ge';
+                                }
+                                $s['version'] = $dep['min'];
+                                $s['optional'] = $optional;
+                                if ($dtype != 'php') {
+                                    $s['name'] = $dep['name'];
+                                }
+                            } elseif (isset($dep['max'])) {
+                                if (isset($dep['exclude']) &&
+                                      $dep['exclude'] == $dep['max']) {
+                                    $s['rel'] = 'lt';
+                                } else {
+                                    $s['rel'] = 'le';
+                                }
+                                $s['version'] = $dep['max'];
+                                $s['optional'] = $optional;
+                                if ($dtype != 'php') {
+                                    $s['name'] = $dep['name'];
+                                }
+                            }
+                        }
+                        $ret[] = $s;
+                    }
+                }
+            }
+            if (count($ret)) {
+                return $ret;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false
+     */
+    function getPackageType()
+    {
+        if (isset($this->_packageInfo['phprelease'])) {
+            return 'php';
+        }
+        if (isset($this->_packageInfo['extsrcrelease'])) {
+            return 'extsrc';
+        }
+        if (isset($this->_packageInfo['extbinrelease'])) {
+            return 'extbin';
+        }
+        if (isset($this->_packageInfo['zendextsrcrelease'])) {
+            return 'zendextsrc';
+        }
+        if (isset($this->_packageInfo['zendextbinrelease'])) {
+            return 'zendextbin';
+        }
+        if (isset($this->_packageInfo['bundle'])) {
+            return 'bundle';
+        }
+        return false;
+    }
+
+    /**
+     * @return array|false
+     */
+    function getReleases()
+    {
+        $type = $this->getPackageType();
+        if ($type != 'bundle') {
+            $type .= 'release';
+        }
+        if ($this->getPackageType() && isset($this->_packageInfo[$type])) {
+            return $this->_packageInfo[$type];
+        }
+        return false;
+    }
+
+    /**
+     * @return array
+     */
+    function getChangelog()
+    {
+        if (isset($this->_packageInfo['changelog'])) {
+            return $this->_packageInfo['changelog'];
+        }
+        return false;
+    }
+
+    function hasDeps()
+    {
+        return isset($this->_packageInfo['dependencies']);
+    }
+
+    function getPackagexmlVersion()
+    {
+        if (isset($this->_packageInfo['zendextsrcrelease'])) {
+            return '2.1';
+        }
+        if (isset($this->_packageInfo['zendextbinrelease'])) {
+            return '2.1';
+        }
+        return '2.0';
+    }
+
+    /**
+     * @return array|false
+     */
+    function getSourcePackage()
+    {
+        if (isset($this->_packageInfo['extbinrelease']) ||
+              isset($this->_packageInfo['zendextbinrelease'])) {
+            return array('channel' => $this->_packageInfo['srcchannel'],
+                         'package' => $this->_packageInfo['srcpackage']);
+        }
+        return false;
+    }
+
+    function getBundledPackages()
+    {
+        if (isset($this->_packageInfo['bundle'])) {
+            return $this->_packageInfo['contents']['bundledpackage'];
+        }
+        return false;
+    }
+
+    function getLastModified()
+    {
+        if (isset($this->_packageInfo['_lastmodified'])) {
+            return $this->_packageInfo['_lastmodified'];
+        }
+        return false;
+    }
+
+    /**
+     * Get the contents of a file listed within the package.xml
+     * @param string
+     * @return string
+     */
+    function getFileContents($file)
+    {
+        if ($this->_archiveFile == $this->_packageFile) { // unpacked
+            $dir = dirname($this->_packageFile);
+            $file = $dir . DIRECTORY_SEPARATOR . $file;
+            $file = str_replace(array('/', '\\'),
+                array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
+            if (file_exists($file) && is_readable($file)) {
+                return implode('', file($file));
+            }
+        } else { // tgz
+            $tar = &new Archive_Tar($this->_archiveFile);
+            $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+            if ($file != 'package.xml' && $file != 'package2.xml') {
+                $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
+            }
+            $file = $tar->extractInString($file);
+            $tar->popErrorHandling();
+            if (PEAR::isError($file)) {
+                return PEAR::raiseError("Cannot locate file '$file' in archive");
+            }
+            return $file;
+        }
+    }
+
+    function &getRW()
+    {
+        if (!class_exists('PEAR_PackageFile_v2_rw')) {
+            require_once 'PEAR/PackageFile/v2/rw.php';
+        }
+        $a = new PEAR_PackageFile_v2_rw;
+        foreach (get_object_vars($this) as $name => $unused) {
+            if (!isset($this->$name)) {
+                continue;
+            }
+            if ($name == '_config' || $name == '_logger'|| $name == '_registry' ||
+                  $name == '_stack') {
+                $a->$name = &$this->$name;
+            } else {
+                $a->$name = $this->$name;
+            }
+        }
+        return $a;
+    }
+
+    function &getDefaultGenerator()
+    {
+        if (!class_exists('PEAR_PackageFile_Generator_v2')) {
+            require_once 'PEAR/PackageFile/Generator/v2.php';
+        }
+        $a = &new PEAR_PackageFile_Generator_v2($this);
+        return $a;
+    }
+
+    function analyzeSourceCode($file, $string = false)
+    {
+        if (!isset($this->_v2Validator) ||
+              !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
+            if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+                require_once 'PEAR/PackageFile/v2/Validator.php';
+            }
+            $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
+        }
+        return $this->_v2Validator->analyzeSourceCode($file, $string);
+    }
+
+    function validate($state = PEAR_VALIDATE_NORMAL)
+    {
+        if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
+            return false;
+        }
+        if (!isset($this->_v2Validator) ||
+              !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
+            if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+                require_once 'PEAR/PackageFile/v2/Validator.php';
+            }
+            $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
+        }
+        if (isset($this->_packageInfo['xsdversion'])) {
+            unset($this->_packageInfo['xsdversion']);
+        }
+        return $this->_v2Validator->validate($this, $state);
+    }
+
+    function getTasksNs()
+    {
+        if (!isset($this->_tasksNs)) {
+            if (isset($this->_packageInfo['attribs'])) {
+                foreach ($this->_packageInfo['attribs'] as $name => $value) {
+                    if ($value == 'http://pear.php.net/dtd/tasks-1.0') {
+                        $this->_tasksNs = str_replace('xmlns:', '', $name);
+                        break;
+                    }
+                }
+            }
+        }
+        return $this->_tasksNs;
+    }
+
+    /**
+     * Determine whether a task name is a valid task.  Custom tasks may be defined
+     * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task>
+     *
+     * Note that this method will auto-load the task class file and test for the existence
+     * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class
+     * PEAR_Task_mycustom_task
+     * @param string
+     * @return boolean
+     */
+    function getTask($task)
+    {
+        $this->getTasksNs();
+        // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace
+        $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task);
+        $taskfile = str_replace(' ', '/', ucwords($task));
+        $task = str_replace(array(' ', '/'), '_', ucwords($task));
+        if (class_exists("PEAR_Task_$task")) {
+            return "PEAR_Task_$task";
+        }
+        $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true);
+        if ($fp) {
+            fclose($fp);
+            require_once "PEAR/Task/$taskfile.php";
+            return "PEAR_Task_$task";
+        }
+        return false;
+    }
+
+    /**
+     * Key-friendly array_splice
+     * @param tagname to splice a value in before
+     * @param mixed the value to splice in
+     * @param string the new tag name
+     */
+    function _ksplice($array, $key, $value, $newkey)
+    {
+        $offset = array_search($key, array_keys($array));
+        $after = array_slice($array, $offset);
+        $before = array_slice($array, 0, $offset);
+        $before[$newkey] = $value;
+        return array_merge($before, $after);
+    }
+
+    /**
+     * @param array a list of possible keys, in the order they may occur
+     * @param mixed contents of the new package.xml tag
+     * @param string tag name
+     * @access private
+     */
+    function _insertBefore($array, $keys, $contents, $newkey)
+    {
+        foreach ($keys as $key) {
+            if (isset($array[$key])) {
+                return $array = $this->_ksplice($array, $key, $contents, $newkey);
+            }
+        }
+        $array[$newkey] = $contents;
+        return $array;
+    }
+
+    /**
+     * @param subsection of {@link $_packageInfo}
+     * @param array|string tag contents
+     * @param array format:
+     * <pre>
+     * array(
+     *   tagname => array(list of tag names that follow this one),
+     *   childtagname => array(list of child tag names that follow this one),
+     * )
+     * </pre>
+     *
+     * This allows construction of nested tags
+     * @access private
+     */
+    function _mergeTag($manip, $contents, $order)
+    {
+        if (count($order)) {
+            foreach ($order as $tag => $curorder) {
+                if (!isset($manip[$tag])) {
+                    // ensure that the tag is set up
+                    $manip = $this->_insertBefore($manip, $curorder, array(), $tag);
+                }
+                if (count($order) > 1) {
+                    $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1));
+                    return $manip;
+                }
+            }
+        } else {
+            return $manip;
+        }
+        if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) {
+            $manip[$tag][] = $contents;
+        } else {
+            if (!count($manip[$tag])) {
+                $manip[$tag] = $contents;
+            } else {
+                $manip[$tag] = array($manip[$tag]);
+                $manip[$tag][] = $contents;
+            }
+        }
+        return $manip;
+    }
+}
+?>
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php
new file mode 100644 (file)
index 0000000..33c8eee
--- /dev/null
@@ -0,0 +1,2154 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a8
+ */
+/**
+ * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
+ * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a8
+ * @access private
+ */
+class PEAR_PackageFile_v2_Validator
+{
+    /**
+     * @var array
+     */
+    var $_packageInfo;
+    /**
+     * @var PEAR_PackageFile_v2
+     */
+    var $_pf;
+    /**
+     * @var PEAR_ErrorStack
+     */
+    var $_stack;
+    /**
+     * @var int
+     */
+    var $_isValid = 0;
+    /**
+     * @var int
+     */
+    var $_filesValid = 0;
+    /**
+     * @var int
+     */
+    var $_curState = 0;
+    /**
+     * @param PEAR_PackageFile_v2
+     * @param int
+     */
+    function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
+    {
+        $this->_pf = &$pf;
+        $this->_curState = $state;
+        $this->_packageInfo = $this->_pf->getArray();
+        $this->_isValid = $this->_pf->_isValid;
+        $this->_filesValid = $this->_pf->_filesValid;
+        $this->_stack = &$pf->_stack;
+        $this->_stack->getErrors(true);
+        if (($this->_isValid & $state) == $state) {
+            return true;
+        }
+        if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
+            return false;
+        }
+        if (!isset($this->_packageInfo['attribs']['version']) ||
+              ($this->_packageInfo['attribs']['version'] != '2.0' &&
+               $this->_packageInfo['attribs']['version'] != '2.1')
+        ) {
+            $this->_noPackageVersion();
+        }
+        $structure =
+        array(
+            'name',
+            'channel|uri',
+            '*extends', // can't be multiple, but this works fine
+            'summary',
+            'description',
+            '+lead', // these all need content checks
+            '*developer',
+            '*contributor',
+            '*helper',
+            'date',
+            '*time',
+            'version',
+            'stability',
+            'license->?uri->?filesource',
+            'notes',
+            'contents', //special validation needed
+            '*compatible',
+            'dependencies', //special validation needed
+            '*usesrole',
+            '*usestask', // reserve these for 1.4.0a1 to implement
+                         // this will allow a package.xml to gracefully say it
+                         // needs a certain package installed in order to implement a role or task
+            '*providesextension',
+            '*srcpackage|*srcuri',
+            '+phprelease|+extsrcrelease|+extbinrelease|' .
+                '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
+            '*changelog',
+        );
+        $test = $this->_packageInfo;
+        if (isset($test['dependencies']) &&
+              isset($test['dependencies']['required']) &&
+              isset($test['dependencies']['required']['pearinstaller']) &&
+              isset($test['dependencies']['required']['pearinstaller']['min']) &&
+              version_compare('1.9.4',
+                $test['dependencies']['required']['pearinstaller']['min'], '<')
+        ) {
+            $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
+            return false;
+        }
+        // ignore post-installation array fields
+        if (array_key_exists('filelist', $test)) {
+            unset($test['filelist']);
+        }
+        if (array_key_exists('_lastmodified', $test)) {
+            unset($test['_lastmodified']);
+        }
+        if (array_key_exists('#binarypackage', $test)) {
+            unset($test['#binarypackage']);
+        }
+        if (array_key_exists('old', $test)) {
+            unset($test['old']);
+        }
+        if (array_key_exists('_lastversion', $test)) {
+            unset($test['_lastversion']);
+        }
+        if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
+            return false;
+        }
+        if (empty($this->_packageInfo['name'])) {
+            $this->_tagCannotBeEmpty('name');
+        }
+        $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
+        if (empty($this->_packageInfo[$test])) {
+            $this->_tagCannotBeEmpty($test);
+        }
+        if (is_array($this->_packageInfo['license']) &&
+              (!isset($this->_packageInfo['license']['_content']) ||
+              empty($this->_packageInfo['license']['_content']))) {
+            $this->_tagCannotBeEmpty('license');
+        } elseif (empty($this->_packageInfo['license'])) {
+            $this->_tagCannotBeEmpty('license');
+        }
+        if (empty($this->_packageInfo['summary'])) {
+            $this->_tagCannotBeEmpty('summary');
+        }
+        if (empty($this->_packageInfo['description'])) {
+            $this->_tagCannotBeEmpty('description');
+        }
+        if (empty($this->_packageInfo['date'])) {
+            $this->_tagCannotBeEmpty('date');
+        }
+        if (empty($this->_packageInfo['notes'])) {
+            $this->_tagCannotBeEmpty('notes');
+        }
+        if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
+            $this->_tagCannotBeEmpty('time');
+        }
+        if (isset($this->_packageInfo['dependencies'])) {
+            $this->_validateDependencies();
+        }
+        if (isset($this->_packageInfo['compatible'])) {
+            $this->_validateCompatible();
+        }
+        if (!isset($this->_packageInfo['bundle'])) {
+            if (empty($this->_packageInfo['contents'])) {
+                $this->_tagCannotBeEmpty('contents');
+            }
+            if (!isset($this->_packageInfo['contents']['dir'])) {
+                $this->_filelistMustContainDir('contents');
+                return false;
+            }
+            if (isset($this->_packageInfo['contents']['file'])) {
+                $this->_filelistCannotContainFile('contents');
+                return false;
+            }
+        }
+        $this->_validateMaintainers();
+        $this->_validateStabilityVersion();
+        $fail = false;
+        if (array_key_exists('usesrole', $this->_packageInfo)) {
+            $roles = $this->_packageInfo['usesrole'];
+            if (!is_array($roles) || !isset($roles[0])) {
+                $roles = array($roles);
+            }
+            foreach ($roles as $role) {
+                if (!isset($role['role'])) {
+                    $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
+                    $fail = true;
+                } else {
+                    if (!isset($role['channel'])) {
+                        if (!isset($role['uri'])) {
+                            $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
+                            $fail = true;
+                        }
+                    } elseif (!isset($role['package'])) {
+                        $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
+                        $fail = true;
+                    }
+                }
+            }
+        }
+        if (array_key_exists('usestask', $this->_packageInfo)) {
+            $roles = $this->_packageInfo['usestask'];
+            if (!is_array($roles) || !isset($roles[0])) {
+                $roles = array($roles);
+            }
+            foreach ($roles as $role) {
+                if (!isset($role['task'])) {
+                    $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
+                    $fail = true;
+                } else {
+                    if (!isset($role['channel'])) {
+                        if (!isset($role['uri'])) {
+                            $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
+                            $fail = true;
+                        }
+                    } elseif (!isset($role['package'])) {
+                        $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
+                        $fail = true;
+                    }
+                }
+            }
+        }
+
+        if ($fail) {
+            return false;
+        }
+
+        $list = $this->_packageInfo['contents'];
+        if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
+            $this->_multipleToplevelDirNotAllowed();
+            return $this->_isValid = 0;
+        }
+
+        $this->_validateFilelist();
+        $this->_validateRelease();
+        if (!$this->_stack->hasErrors()) {
+            $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
+            if (PEAR::isError($chan)) {
+                $this->_unknownChannel($this->_pf->getChannel());
+            } else {
+                $valpack = $chan->getValidationPackage();
+                // for channel validator packages, always use the default PEAR validator.
+                // otherwise, they can't be installed or packaged
+                $validator = $chan->getValidationObject($this->_pf->getPackage());
+                if (!$validator) {
+                    $this->_stack->push(__FUNCTION__, 'error',
+                        array('channel' => $chan->getName(),
+                              'package' => $this->_pf->getPackage(),
+                              'name'    => $valpack['_content'],
+                              'version' => $valpack['attribs']['version']),
+                        'package "%channel%/%package%" cannot be properly validated without ' .
+                        'validation package "%channel%/%name%-%version%"');
+                    return $this->_isValid = 0;
+                }
+                $validator->setPackageFile($this->_pf);
+                $validator->validate($state);
+                $failures = $validator->getFailures();
+                foreach ($failures['errors'] as $error) {
+                    $this->_stack->push(__FUNCTION__, 'error', $error,
+                        'Channel validator error: field "%field%" - %reason%');
+                }
+                foreach ($failures['warnings'] as $warning) {
+                    $this->_stack->push(__FUNCTION__, 'warning', $warning,
+                        'Channel validator warning: field "%field%" - %reason%');
+                }
+            }
+        }
+
+        $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
+        if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
+            if ($this->_pf->getPackageType() == 'bundle') {
+                if ($this->_analyzeBundledPackages()) {
+                    $this->_filesValid = $this->_pf->_filesValid = true;
+                } else {
+                    $this->_pf->_isValid = $this->_isValid = 0;
+                }
+            } else {
+                if (!$this->_analyzePhpFiles()) {
+                    $this->_pf->_isValid = $this->_isValid = 0;
+                } else {
+                    $this->_filesValid = $this->_pf->_filesValid = true;
+                }
+            }
+        }
+
+        if ($this->_isValid) {
+            return $this->_pf->_isValid = $this->_isValid = $state;
+        }
+
+        return $this->_pf->_isValid = $this->_isValid = 0;
+    }
+
+    function _stupidSchemaValidate($structure, $xml, $root)
+    {
+        if (!is_array($xml)) {
+            $xml = array();
+        }
+        $keys = array_keys($xml);
+        reset($keys);
+        $key = current($keys);
+        while ($key == 'attribs' || $key == '_contents') {
+            $key = next($keys);
+        }
+        $unfoundtags = $optionaltags = array();
+        $ret = true;
+        $mismatch = false;
+        foreach ($structure as $struc) {
+            if ($key) {
+                $tag = $xml[$key];
+            }
+            $test = $this->_processStructure($struc);
+            if (isset($test['choices'])) {
+                $loose = true;
+                foreach ($test['choices'] as $choice) {
+                    if ($key == $choice['tag']) {
+                        $key = next($keys);
+                        while ($key == 'attribs' || $key == '_contents') {
+                            $key = next($keys);
+                        }
+                        $unfoundtags = $optionaltags = array();
+                        $mismatch = false;
+                        if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
+                            $unfoundtags[] = $choice['tag'];
+                            $optionaltags[] = $choice['tag'];
+                            if ($key) {
+                                $mismatch = true;
+                            }
+                        }
+                        $ret &= $this->_processAttribs($choice, $tag, $root);
+                        continue 2;
+                    } else {
+                        $unfoundtags[] = $choice['tag'];
+                        $mismatch = true;
+                    }
+                    if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
+                        $loose = false;
+                    } else {
+                        $optionaltags[] = $choice['tag'];
+                    }
+                }
+                if (!$loose) {
+                    $this->_invalidTagOrder($unfoundtags, $key, $root);
+                    return false;
+                }
+            } else {
+                if ($key != $test['tag']) {
+                    if (isset($test['multiple']) && $test['multiple'] != '*') {
+                        $unfoundtags[] = $test['tag'];
+                        $this->_invalidTagOrder($unfoundtags, $key, $root);
+                        return false;
+                    } else {
+                        if ($key) {
+                            $mismatch = true;
+                        }
+                        $unfoundtags[] = $test['tag'];
+                        $optionaltags[] = $test['tag'];
+                    }
+                    if (!isset($test['multiple'])) {
+                        $this->_invalidTagOrder($unfoundtags, $key, $root);
+                        return false;
+                    }
+                    continue;
+                } else {
+                    $unfoundtags = $optionaltags = array();
+                    $mismatch = false;
+                }
+                $key = next($keys);
+                while ($key == 'attribs' || $key == '_contents') {
+                    $key = next($keys);
+                }
+                if ($key && $key != $test['tag'] && isset($test['multiple'])) {
+                    $unfoundtags[] = $test['tag'];
+                    $optionaltags[] = $test['tag'];
+                    $mismatch = true;
+                }
+                $ret &= $this->_processAttribs($test, $tag, $root);
+                continue;
+            }
+        }
+        if (!$mismatch && count($optionaltags)) {
+            // don't error out on any optional tags
+            $unfoundtags = array_diff($unfoundtags, $optionaltags);
+        }
+        if (count($unfoundtags)) {
+            $this->_invalidTagOrder($unfoundtags, $key, $root);
+        } elseif ($key) {
+            // unknown tags
+            $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
+            while ($key = next($keys)) {
+                $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
+            }
+        }
+        return $ret;
+    }
+
+    function _processAttribs($choice, $tag, $context)
+    {
+        if (isset($choice['attribs'])) {
+            if (!is_array($tag)) {
+                $tag = array($tag);
+            }
+            $tags = $tag;
+            if (!isset($tags[0])) {
+                $tags = array($tags);
+            }
+            $ret = true;
+            foreach ($tags as $i => $tag) {
+                if (!is_array($tag) || !isset($tag['attribs'])) {
+                    foreach ($choice['attribs'] as $attrib) {
+                        if ($attrib{0} != '?') {
+                            $ret &= $this->_tagHasNoAttribs($choice['tag'],
+                                $context);
+                            continue 2;
+                        }
+                    }
+                }
+                foreach ($choice['attribs'] as $attrib) {
+                    if ($attrib{0} != '?') {
+                        if (!isset($tag['attribs'][$attrib])) {
+                            $ret &= $this->_tagMissingAttribute($choice['tag'],
+                                $attrib, $context);
+                        }
+                    }
+                }
+            }
+            return $ret;
+        }
+        return true;
+    }
+
+    function _processStructure($key)
+    {
+        $ret = array();
+        if (count($pieces = explode('|', $key)) > 1) {
+            $ret['choices'] = array();
+            foreach ($pieces as $piece) {
+                $ret['choices'][] = $this->_processStructure($piece);
+            }
+            return $ret;
+        }
+        $multi = $key{0};
+        if ($multi == '+' || $multi == '*') {
+            $ret['multiple'] = $key{0};
+            $key = substr($key, 1);
+        }
+        if (count($attrs = explode('->', $key)) > 1) {
+            $ret['tag'] = array_shift($attrs);
+            $ret['attribs'] = $attrs;
+        } else {
+            $ret['tag'] = $key;
+        }
+        return $ret;
+    }
+
+    function _validateStabilityVersion()
+    {
+        $structure = array('release', 'api');
+        $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
+        $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
+        if ($a) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $this->_packageInfo['version']['release'])) {
+                $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
+            }
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $this->_packageInfo['version']['api'])) {
+                $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
+            }
+            if (!in_array($this->_packageInfo['stability']['release'],
+                  array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
+                $this->_invalidState('release', $this->_packageInfo['stability']['release']);
+            }
+            if (!in_array($this->_packageInfo['stability']['api'],
+                  array('devel', 'alpha', 'beta', 'stable'))) {
+                $this->_invalidState('api', $this->_packageInfo['stability']['api']);
+            }
+        }
+    }
+
+    function _validateMaintainers()
+    {
+        $structure =
+            array(
+                'name',
+                'user',
+                'email',
+                'active',
+            );
+        foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
+            if (!isset($this->_packageInfo[$type])) {
+                continue;
+            }
+            if (isset($this->_packageInfo[$type][0])) {
+                foreach ($this->_packageInfo[$type] as $lead) {
+                    $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
+                }
+            } else {
+                $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
+                    '<' . $type . '>');
+            }
+        }
+    }
+
+    function _validatePhpDep($dep, $installcondition = false)
+    {
+        $structure = array(
+            'min',
+            '*max',
+            '*exclude',
+        );
+        $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
+        $this->_stupidSchemaValidate($structure, $dep, $type);
+        if (isset($dep['min'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+                  $dep['min'])) {
+                $this->_invalidVersion($type . '<min>', $dep['min']);
+            }
+        }
+        if (isset($dep['max'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+                  $dep['max'])) {
+                $this->_invalidVersion($type . '<max>', $dep['max']);
+            }
+        }
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+            foreach ($dep['exclude'] as $exclude) {
+                if (!preg_match(
+                     '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+                     $exclude)) {
+                    $this->_invalidVersion($type . '<exclude>', $exclude);
+                }
+            }
+        }
+    }
+
+    function _validatePearinstallerDep($dep)
+    {
+        $structure = array(
+            'min',
+            '*max',
+            '*recommended',
+            '*exclude',
+        );
+        $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
+        if (isset($dep['min'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['min'])) {
+                $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
+                    $dep['min']);
+            }
+        }
+        if (isset($dep['max'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['max'])) {
+                $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
+                    $dep['max']);
+            }
+        }
+        if (isset($dep['recommended'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['recommended'])) {
+                $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
+                    $dep['recommended']);
+            }
+        }
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+            foreach ($dep['exclude'] as $exclude) {
+                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                      $exclude)) {
+                    $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
+                        $exclude);
+                }
+            }
+        }
+    }
+
+    function _validatePackageDep($dep, $group, $type = '<package>')
+    {
+        if (isset($dep['uri'])) {
+            if (isset($dep['conflicts'])) {
+                $structure = array(
+                    'name',
+                    'uri',
+                    'conflicts',
+                    '*providesextension',
+                );
+            } else {
+                $structure = array(
+                    'name',
+                    'uri',
+                    '*providesextension',
+                );
+            }
+        } else {
+            if (isset($dep['conflicts'])) {
+                $structure = array(
+                    'name',
+                    'channel',
+                    '*min',
+                    '*max',
+                    '*exclude',
+                    'conflicts',
+                    '*providesextension',
+                );
+            } else {
+                $structure = array(
+                    'name',
+                    'channel',
+                    '*min',
+                    '*max',
+                    '*recommended',
+                    '*exclude',
+                    '*nodefault',
+                    '*providesextension',
+                );
+            }
+        }
+        if (isset($dep['name'])) {
+            $type .= '<name>' . $dep['name'] . '</name>';
+        }
+        $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
+        if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
+              isset($dep['recommended']) || isset($dep['exclude']))) {
+            $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
+        }
+        if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
+            $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
+        }
+        if (isset($dep['min'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['min'])) {
+                $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
+            }
+        }
+        if (isset($dep['max'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['max'])) {
+                $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
+            }
+        }
+        if (isset($dep['recommended'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['recommended'])) {
+                $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
+                    $dep['recommended']);
+            }
+        }
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+            foreach ($dep['exclude'] as $exclude) {
+                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                      $exclude)) {
+                    $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
+                        $exclude);
+                }
+            }
+        }
+    }
+
+    function _validateSubpackageDep($dep, $group)
+    {
+        $this->_validatePackageDep($dep, $group, '<subpackage>');
+        if (isset($dep['providesextension'])) {
+            $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
+        }
+        if (isset($dep['conflicts'])) {
+            $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
+        }
+    }
+
+    function _validateExtensionDep($dep, $group = false, $installcondition = false)
+    {
+        if (isset($dep['conflicts'])) {
+            $structure = array(
+                'name',
+                '*min',
+                '*max',
+                '*exclude',
+                'conflicts',
+            );
+        } else {
+            $structure = array(
+                'name',
+                '*min',
+                '*max',
+                '*recommended',
+                '*exclude',
+            );
+        }
+        if ($installcondition) {
+            $type = '<installcondition><extension>';
+        } else {
+            $type = '<dependencies>' . $group . '<extension>';
+        }
+        if (isset($dep['name'])) {
+            $type .= '<name>' . $dep['name'] . '</name>';
+        }
+        $this->_stupidSchemaValidate($structure, $dep, $type);
+        if (isset($dep['min'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['min'])) {
+                $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
+            }
+        }
+        if (isset($dep['max'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['max'])) {
+                $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
+            }
+        }
+        if (isset($dep['recommended'])) {
+            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                  $dep['recommended'])) {
+                $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
+            }
+        }
+        if (isset($dep['exclude'])) {
+            if (!is_array($dep['exclude'])) {
+                $dep['exclude'] = array($dep['exclude']);
+            }
+            foreach ($dep['exclude'] as $exclude) {
+                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                      $exclude)) {
+                    $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
+                }
+            }
+        }
+    }
+
+    function _validateOsDep($dep, $installcondition = false)
+    {
+        $structure = array(
+            'name',
+            '*conflicts',
+        );
+        $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
+        if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
+            if ($dep['name'] == '*') {
+                if (array_key_exists('conflicts', $dep)) {
+                    $this->_cannotConflictWithAllOs($type);
+                }
+            }
+        }
+    }
+
+    function _validateArchDep($dep, $installcondition = false)
+    {
+        $structure = array(
+            'pattern',
+            '*conflicts',
+        );
+        $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
+        $this->_stupidSchemaValidate($structure, $dep, $type);
+    }
+
+    function _validateInstallConditions($cond, $release)
+    {
+        $structure = array(
+            '*php',
+            '*extension',
+            '*os',
+            '*arch',
+        );
+        if (!$this->_stupidSchemaValidate($structure,
+              $cond, $release)) {
+            return false;
+        }
+        foreach (array('php', 'extension', 'os', 'arch') as $type) {
+            if (isset($cond[$type])) {
+                $iter = $cond[$type];
+                if (!is_array($iter) || !isset($iter[0])) {
+                    $iter = array($iter);
+                }
+                foreach ($iter as $package) {
+                    if ($type == 'extension') {
+                        $this->{"_validate{$type}Dep"}($package, false, true);
+                    } else {
+                        $this->{"_validate{$type}Dep"}($package, true);
+                    }
+                }
+            }
+        }
+    }
+
+    function _validateDependencies()
+    {
+        $structure = array(
+            'required',
+            '*optional',
+            '*group->name->hint'
+        );
+        if (!$this->_stupidSchemaValidate($structure,
+              $this->_packageInfo['dependencies'], '<dependencies>')) {
+            return false;
+        }
+        foreach (array('required', 'optional') as $simpledep) {
+            if (isset($this->_packageInfo['dependencies'][$simpledep])) {
+                if ($simpledep == 'optional') {
+                    $structure = array(
+                        '*package',
+                        '*subpackage',
+                        '*extension',
+                    );
+                } else {
+                    $structure = array(
+                        'php',
+                        'pearinstaller',
+                        '*package',
+                        '*subpackage',
+                        '*extension',
+                        '*os',
+                        '*arch',
+                    );
+                }
+                if ($this->_stupidSchemaValidate($structure,
+                      $this->_packageInfo['dependencies'][$simpledep],
+                      "<dependencies><$simpledep>")) {
+                    foreach (array('package', 'subpackage', 'extension') as $type) {
+                        if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
+                            $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
+                            if (!isset($iter[0])) {
+                                $iter = array($iter);
+                            }
+                            foreach ($iter as $package) {
+                                if ($type != 'extension') {
+                                    if (isset($package['uri'])) {
+                                        if (isset($package['channel'])) {
+                                            $this->_UrlOrChannel($type,
+                                                $package['name']);
+                                        }
+                                    } else {
+                                        if (!isset($package['channel'])) {
+                                            $this->_NoChannel($type, $package['name']);
+                                        }
+                                    }
+                                }
+                                $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
+                            }
+                        }
+                    }
+                    if ($simpledep == 'optional') {
+                        continue;
+                    }
+                    foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
+                        if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
+                            $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
+                            if (!isset($iter[0])) {
+                                $iter = array($iter);
+                            }
+                            foreach ($iter as $package) {
+                                $this->{"_validate{$type}Dep"}($package);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (isset($this->_packageInfo['dependencies']['group'])) {
+            $groups = $this->_packageInfo['dependencies']['group'];
+            if (!isset($groups[0])) {
+                $groups = array($groups);
+            }
+            $structure = array(
+                '*package',
+                '*subpackage',
+                '*extension',
+            );
+            foreach ($groups as $group) {
+                if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
+                    if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
+                        $this->_invalidDepGroupName($group['attribs']['name']);
+                    }
+                    foreach (array('package', 'subpackage', 'extension') as $type) {
+                        if (isset($group[$type])) {
+                            $iter = $group[$type];
+                            if (!isset($iter[0])) {
+                                $iter = array($iter);
+                            }
+                            foreach ($iter as $package) {
+                                if ($type != 'extension') {
+                                    if (isset($package['uri'])) {
+                                        if (isset($package['channel'])) {
+                                            $this->_UrlOrChannelGroup($type,
+                                                $package['name'],
+                                                $group['name']);
+                                        }
+                                    } else {
+                                        if (!isset($package['channel'])) {
+                                            $this->_NoChannelGroup($type,
+                                                $package['name'],
+                                                $group['name']);
+                                        }
+                                    }
+                                }
+                                $this->{"_validate{$type}Dep"}($package, '<group name="' .
+                                    $group['attribs']['name'] . '">');
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    function _validateCompatible()
+    {
+        $compat = $this->_packageInfo['compatible'];
+        if (!isset($compat[0])) {
+            $compat = array($compat);
+        }
+        $required = array('name', 'channel', 'min', 'max', '*exclude');
+        foreach ($compat as $package) {
+            $type = '<compatible>';
+            if (is_array($package) && array_key_exists('name', $package)) {
+                $type .= '<name>' . $package['name'] . '</name>';
+            }
+            $this->_stupidSchemaValidate($required, $package, $type);
+            if (is_array($package) && array_key_exists('min', $package)) {
+                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                      $package['min'])) {
+                    $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
+                }
+            }
+            if (is_array($package) && array_key_exists('max', $package)) {
+                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                      $package['max'])) {
+                    $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
+                }
+            }
+            if (is_array($package) && array_key_exists('exclude', $package)) {
+                if (!is_array($package['exclude'])) {
+                    $package['exclude'] = array($package['exclude']);
+                }
+                foreach ($package['exclude'] as $exclude) {
+                    if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+                          $exclude)) {
+                        $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
+                    }
+                }
+            }
+        }
+    }
+
+    function _validateBundle($list)
+    {
+        if (!is_array($list) || !isset($list['bundledpackage'])) {
+            return $this->_NoBundledPackages();
+        }
+        if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
+            return $this->_AtLeast2BundledPackages();
+        }
+        foreach ($list['bundledpackage'] as $package) {
+            if (!is_string($package)) {
+                $this->_bundledPackagesMustBeFilename();
+            }
+        }
+    }
+
+    function _validateFilelist($list = false, $allowignore = false, $dirs = '')
+    {
+        $iscontents = false;
+        if (!$list) {
+            $iscontents = true;
+            $list = $this->_packageInfo['contents'];
+            if (isset($this->_packageInfo['bundle'])) {
+                return $this->_validateBundle($list);
+            }
+        }
+        if ($allowignore) {
+            $struc = array(
+                '*install->name->as',
+                '*ignore->name'
+            );
+        } else {
+            $struc = array(
+                '*dir->name->?baseinstalldir',
+                '*file->name->role->?baseinstalldir->?md5sum'
+            );
+            if (isset($list['dir']) && isset($list['file'])) {
+                // stave off validation errors without requiring a set order.
+                $_old = $list;
+                if (isset($list['attribs'])) {
+                    $list = array('attribs' => $_old['attribs']);
+                }
+                $list['dir'] = $_old['dir'];
+                $list['file'] = $_old['file'];
+            }
+        }
+        if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
+            $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
+            $dirname = $iscontents ? '<contents>' : $unknown;
+        } else {
+            $dirname = '<dir name="' . $list['attribs']['name'] . '">';
+            if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+                          str_replace('\\', '/', $list['attribs']['name']))) {
+                // file contains .. parent directory or . cur directory
+                $this->_invalidDirName($list['attribs']['name']);
+            }
+        }
+        $res = $this->_stupidSchemaValidate($struc, $list, $dirname);
+        if ($allowignore && $res) {
+            $ignored_or_installed = array();
+            $this->_pf->getFilelist();
+            $fcontents = $this->_pf->getContents();
+            $filelist = array();
+            if (!isset($fcontents['dir']['file'][0])) {
+                $fcontents['dir']['file'] = array($fcontents['dir']['file']);
+            }
+            foreach ($fcontents['dir']['file'] as $file) {
+                $filelist[$file['attribs']['name']] = true;
+            }
+            if (isset($list['install'])) {
+                if (!isset($list['install'][0])) {
+                    $list['install'] = array($list['install']);
+                }
+                foreach ($list['install'] as $file) {
+                    if (!isset($filelist[$file['attribs']['name']])) {
+                        $this->_notInContents($file['attribs']['name'], 'install');
+                        continue;
+                    }
+                    if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
+                        $this->_multipleInstallAs($file['attribs']['name']);
+                    }
+                    if (!isset($ignored_or_installed[$file['attribs']['name']])) {
+                        $ignored_or_installed[$file['attribs']['name']] = array();
+                    }
+                    $ignored_or_installed[$file['attribs']['name']][] = 1;
+                    if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+                                  str_replace('\\', '/', $file['attribs']['as']))) {
+                        // file contains .. parent directory or . cur directory references
+                        $this->_invalidFileInstallAs($file['attribs']['name'],
+                            $file['attribs']['as']);
+                    }
+                }
+            }
+            if (isset($list['ignore'])) {
+                if (!isset($list['ignore'][0])) {
+                    $list['ignore'] = array($list['ignore']);
+                }
+                foreach ($list['ignore'] as $file) {
+                    if (!isset($filelist[$file['attribs']['name']])) {
+                        $this->_notInContents($file['attribs']['name'], 'ignore');
+                        continue;
+                    }
+                    if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
+                        $this->_ignoreAndInstallAs($file['attribs']['name']);
+                    }
+                }
+            }
+        }
+        if (!$allowignore && isset($list['file'])) {
+            if (is_string($list['file'])) {
+                $this->_oldStyleFileNotAllowed();
+                return false;
+            }
+            if (!isset($list['file'][0])) {
+                // single file
+                $list['file'] = array($list['file']);
+            }
+            foreach ($list['file'] as $i => $file)
+            {
+                if (isset($file['attribs']) && isset($file['attribs']['name'])) {
+                    if ($file['attribs']['name']{0} == '.' &&
+                          $file['attribs']['name']{1} == '/') {
+                        // name is something like "./doc/whatever.txt"
+                        $this->_invalidFileName($file['attribs']['name'], $dirname);
+                    }
+                    if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+                                  str_replace('\\', '/', $file['attribs']['name']))) {
+                        // file contains .. parent directory or . cur directory
+                        $this->_invalidFileName($file['attribs']['name'], $dirname);
+                    }
+                }
+                if (isset($file['attribs']) && isset($file['attribs']['role'])) {
+                    if (!$this->_validateRole($file['attribs']['role'])) {
+                        if (isset($this->_packageInfo['usesrole'])) {
+                            $roles = $this->_packageInfo['usesrole'];
+                            if (!isset($roles[0])) {
+                                $roles = array($roles);
+                            }
+                            foreach ($roles as $role) {
+                                if ($role['role'] = $file['attribs']['role']) {
+                                    $msg = 'This package contains role "%role%" and requires ' .
+                                        'package "%package%" to be used';
+                                    if (isset($role['uri'])) {
+                                        $params = array('role' => $role['role'],
+                                            'package' => $role['uri']);
+                                    } else {
+                                        $params = array('role' => $role['role'],
+                                            'package' => $this->_pf->_registry->
+                                            parsedPackageNameToString(array('package' =>
+                                                $role['package'], 'channel' => $role['channel']),
+                                                true));
+                                    }
+                                    $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
+                                }
+                            }
+                        }
+                        $this->_invalidFileRole($file['attribs']['name'],
+                            $dirname, $file['attribs']['role']);
+                    }
+                }
+                if (!isset($file['attribs'])) {
+                    continue;
+                }
+                $save = $file['attribs'];
+                if ($dirs) {
+                    $save['name'] = $dirs . '/' . $save['name'];
+                }
+                unset($file['attribs']);
+                if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
+                    foreach ($file as $task => $value) {
+                        if ($tagClass = $this->_pf->getTask($task)) {
+                            if (!is_array($value) || !isset($value[0])) {
+                                $value = array($value);
+                            }
+                            foreach ($value as $v) {
+                                $ret = call_user_func(array($tagClass, 'validateXml'),
+                                    $this->_pf, $v, $this->_pf->_config, $save);
+                                if (is_array($ret)) {
+                                    $this->_invalidTask($task, $ret, isset($save['name']) ?
+                                        $save['name'] : '');
+                                }
+                            }
+                        } else {
+                            if (isset($this->_packageInfo['usestask'])) {
+                                $roles = $this->_packageInfo['usestask'];
+                                if (!isset($roles[0])) {
+                                    $roles = array($roles);
+                                }
+                                foreach ($roles as $role) {
+                                    if ($role['task'] = $task) {
+                                        $msg = 'This package contains task "%task%" and requires ' .
+                                            'package "%package%" to be used';
+                                        if (isset($role['uri'])) {
+                                            $params = array('task' => $role['task'],
+                                                'package' => $role['uri']);
+                                        } else {
+                                            $params = array('task' => $role['task'],
+                                                'package' => $this->_pf->_registry->
+                                                parsedPackageNameToString(array('package' =>
+                                                    $role['package'], 'channel' => $role['channel']),
+                                                    true));
+                                        }
+                                        $this->_stack->push('_mustInstallTask', 'error',
+                                            $params, $msg);
+                                    }
+                                }
+                            }
+                            $this->_unknownTask($task, $save['name']);
+                        }
+                    }
+                }
+            }
+        }
+        if (isset($list['ignore'])) {
+            if (!$allowignore) {
+                $this->_ignoreNotAllowed('ignore');
+            }
+        }
+        if (isset($list['install'])) {
+            if (!$allowignore) {
+                $this->_ignoreNotAllowed('install');
+            }
+        }
+        if (isset($list['file'])) {
+            if ($allowignore) {
+                $this->_fileNotAllowed('file');
+            }
+        }
+        if (isset($list['dir'])) {
+            if ($allowignore) {
+                $this->_fileNotAllowed('dir');
+            } else {
+                if (!isset($list['dir'][0])) {
+                    $list['dir'] = array($list['dir']);
+                }
+                foreach ($list['dir'] as $dir) {
+                    if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
+                        if ($dir['attribs']['name'] == '/' ||
+                              !isset($this->_packageInfo['contents']['dir']['dir'])) {
+                            // always use nothing if the filelist has already been flattened
+                            $newdirs = '';
+                        } elseif ($dirs == '') {
+                            $newdirs = $dir['attribs']['name'];
+                        } else {
+                            $newdirs = $dirs . '/' . $dir['attribs']['name'];
+                        }
+                    } else {
+                        $newdirs = $dirs;
+                    }
+                    $this->_validateFilelist($dir, $allowignore, $newdirs);
+                }
+            }
+        }
+    }
+
+    function _validateRelease()
+    {
+        if (isset($this->_packageInfo['phprelease'])) {
+            $release = 'phprelease';
+            if (isset($this->_packageInfo['providesextension'])) {
+                $this->_cannotProvideExtension($release);
+            }
+            if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+                $this->_cannotHaveSrcpackage($release);
+            }
+            $releases = $this->_packageInfo['phprelease'];
+            if (!is_array($releases)) {
+                return true;
+            }
+            if (!isset($releases[0])) {
+                $releases = array($releases);
+            }
+            foreach ($releases as $rel) {
+                $this->_stupidSchemaValidate(array(
+                    '*installconditions',
+                    '*filelist',
+                ), $rel, '<phprelease>');
+            }
+        }
+        foreach (array('', 'zend') as $prefix) {
+            $releasetype = $prefix . 'extsrcrelease';
+            if (isset($this->_packageInfo[$releasetype])) {
+                $release = $releasetype;
+                if (!isset($this->_packageInfo['providesextension'])) {
+                    $this->_mustProvideExtension($release);
+                }
+                if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+                    $this->_cannotHaveSrcpackage($release);
+                }
+                $releases = $this->_packageInfo[$releasetype];
+                if (!is_array($releases)) {
+                    return true;
+                }
+                if (!isset($releases[0])) {
+                    $releases = array($releases);
+                }
+                foreach ($releases as $rel) {
+                    $this->_stupidSchemaValidate(array(
+                        '*installconditions',
+                        '*configureoption->name->prompt->?default',
+                        '*binarypackage',
+                        '*filelist',
+                    ), $rel, '<' . $releasetype . '>');
+                    if (isset($rel['binarypackage'])) {
+                        if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
+                            $rel['binarypackage'] = array($rel['binarypackage']);
+                        }
+                        foreach ($rel['binarypackage'] as $bin) {
+                            if (!is_string($bin)) {
+                                $this->_binaryPackageMustBePackagename();
+                            }
+                        }
+                    }
+                }
+            }
+            $releasetype = 'extbinrelease';
+            if (isset($this->_packageInfo[$releasetype])) {
+                $release = $releasetype;
+                if (!isset($this->_packageInfo['providesextension'])) {
+                    $this->_mustProvideExtension($release);
+                }
+                if (isset($this->_packageInfo['channel']) &&
+                      !isset($this->_packageInfo['srcpackage'])) {
+                    $this->_mustSrcPackage($release);
+                }
+                if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
+                    $this->_mustSrcuri($release);
+                }
+                $releases = $this->_packageInfo[$releasetype];
+                if (!is_array($releases)) {
+                    return true;
+                }
+                if (!isset($releases[0])) {
+                    $releases = array($releases);
+                }
+                foreach ($releases as $rel) {
+                    $this->_stupidSchemaValidate(array(
+                        '*installconditions',
+                        '*filelist',
+                    ), $rel, '<' . $releasetype . '>');
+                }
+            }
+        }
+        if (isset($this->_packageInfo['bundle'])) {
+            $release = 'bundle';
+            if (isset($this->_packageInfo['providesextension'])) {
+                $this->_cannotProvideExtension($release);
+            }
+            if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+                $this->_cannotHaveSrcpackage($release);
+            }
+            $releases = $this->_packageInfo['bundle'];
+            if (!is_array($releases) || !isset($releases[0])) {
+                $releases = array($releases);
+            }
+            foreach ($releases as $rel) {
+                $this->_stupidSchemaValidate(array(
+                    '*installconditions',
+                    '*filelist',
+                ), $rel, '<bundle>');
+            }
+        }
+        foreach ($releases as $rel) {
+            if (is_array($rel) && array_key_exists('installconditions', $rel)) {
+                $this->_validateInstallConditions($rel['installconditions'],
+                    "<$release><installconditions>");
+            }
+            if (is_array($rel) && array_key_exists('filelist', $rel)) {
+                if ($rel['filelist']) {
+
+                    $this->_validateFilelist($rel['filelist'], true);
+                }
+            }
+        }
+    }
+
+    /**
+     * This is here to allow role extension through plugins
+     * @param string
+     */
+    function _validateRole($role)
+    {
+        return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
+    }
+
+    function _pearVersionTooLow($version)
+    {
+        $this->_stack->push(__FUNCTION__, 'error',
+            array('version' => $version),
+            'This package.xml requires PEAR version %version% to parse properly, we are ' .
+            'version 1.9.4');
+    }
+
+    function _invalidTagOrder($oktags, $actual, $root)
+    {
+        $this->_stack->push(__FUNCTION__, 'error',
+            array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
+            'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
+    }
+
+    function _ignoreNotAllowed($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+            '<%type%> is not allowed inside global <contents>, only inside ' .
+            '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
+    }
+
+    function _fileNotAllowed($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+            '<%type%> is not allowed inside release <filelist>, only inside ' .
+            '<contents>, use <ignore> and <install> only');
+    }
+
+    function _oldStyleFileNotAllowed()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'Old-style <file>name</file> is not allowed.  Use' .
+            '<file name="name" role="role"/>');
+    }
+
+    function _tagMissingAttribute($tag, $attr, $context)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
+            'attribute' => $attr, 'context' => $context),
+            'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
+    }
+
+    function _tagHasNoAttribs($tag, $context)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
+            'context' => $context),
+            'tag <%tag%> has no attributes in context "%context%"');
+    }
+
+    function _invalidInternalStructure()
+    {
+        $this->_stack->push(__FUNCTION__, 'exception', array(),
+            'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
+    }
+
+    function _invalidFileRole($file, $dir, $role)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(
+            'file' => $file, 'dir' => $dir, 'role' => $role,
+            'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
+            'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
+    }
+
+    function _invalidFileName($file, $dir)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(
+            'file' => $file),
+            'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
+    }
+
+    function _invalidFileInstallAs($file, $as)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(
+            'file' => $file, 'as' => $as),
+            'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
+    }
+
+    function _invalidDirName($dir)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(
+            'dir' => $file),
+            'Directory "%dir%" cannot begin with "./" or contain ".."');
+    }
+
+    function _filelistCannotContainFile($filelist)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
+            '<%tag%> can only contain <dir>, contains <file>.  Use ' .
+            '<dir name="/"> as the first dir element');
+    }
+
+    function _filelistMustContainDir($filelist)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
+            '<%tag%> must contain <dir>.  Use <dir name="/"> as the ' .
+            'first dir element');
+    }
+
+    function _tagCannotBeEmpty($tag)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
+            '<%tag%> cannot be empty (<%tag%/>)');
+    }
+
+    function _UrlOrChannel($type, $name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+            'name' => $name),
+            'Required dependency <%type%> "%name%" can have either url OR ' .
+            'channel attributes, and not both');
+    }
+
+    function _NoChannel($type, $name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+            'name' => $name),
+            'Required dependency <%type%> "%name%" must have either url OR ' .
+            'channel attributes');
+    }
+
+    function _UrlOrChannelGroup($type, $name, $group)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+            'name' => $name, 'group' => $group),
+            'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
+            'channel attributes, and not both');
+    }
+
+    function _NoChannelGroup($type, $name, $group)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+            'name' => $name, 'group' => $group),
+            'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
+            'channel attributes');
+    }
+
+    function _unknownChannel($channel)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
+            'Unknown channel "%channel%"');
+    }
+
+    function _noPackageVersion()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'package.xml <package> tag has no version attribute, or version is not 2.0');
+    }
+
+    function _NoBundledPackages()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'No <bundledpackage> tag was found in <contents>, required for bundle packages');
+    }
+
+    function _AtLeast2BundledPackages()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'At least 2 packages must be bundled in a bundle package');
+    }
+
+    function _ChannelOrUri($name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+            'Bundled package "%name%" can have either a uri or a channel, not both');
+    }
+
+    function _noChildTag($child, $tag)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
+            'Tag <%tag%> is missing child tag <%child%>');
+    }
+
+    function _invalidVersion($type, $value)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
+            'Version type <%type%> is not a valid version (%value%)');
+    }
+
+    function _invalidState($type, $value)
+    {
+        $states = array('stable', 'beta', 'alpha', 'devel');
+        if ($type != 'api') {
+            $states[] = 'snapshot';
+        }
+        if (strtolower($value) == 'rc') {
+            $this->_stack->push(__FUNCTION__, 'error',
+                array('version' => $this->_packageInfo['version']['release']),
+                'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
+        }
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
+            'types' => $states),
+            'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
+            '%types%');
+    }
+
+    function _invalidTask($task, $ret, $file)
+    {
+        switch ($ret[0]) {
+            case PEAR_TASK_ERROR_MISSING_ATTRIB :
+                $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
+                $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
+            break;
+            case PEAR_TASK_ERROR_NOATTRIBS :
+                $info = array('task' => $task, 'file' => $file);
+                $msg = 'task <%task%> has no attributes in file %file%';
+            break;
+            case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
+                $info = array('attrib' => $ret[1], 'values' => $ret[3],
+                    'was' => $ret[2], 'task' => $task, 'file' => $file);
+                $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
+                    'in file %file%, expecting one of "%values%"';
+            break;
+            case PEAR_TASK_ERROR_INVALID :
+                $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
+                $msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
+            break;
+        }
+        $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
+    }
+
+    function _unknownTask($task, $file)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
+            'Unknown task "%task%" passed in file <file name="%file%">');
+    }
+
+    function _subpackageCannotProvideExtension($name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+            'Subpackage dependency "%name%" cannot use <providesextension>, ' .
+            'only package dependencies can use this tag');
+    }
+
+    function _subpackagesCannotConflict($name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+            'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
+            'only package dependencies can use this tag');
+    }
+
+    function _cannotProvideExtension($release)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+            '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
+    }
+
+    function _mustProvideExtension($release)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+            '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
+    }
+
+    function _cannotHaveSrcpackage($release)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+            '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
+    }
+
+    function _mustSrcPackage($release)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+            '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
+    }
+
+    function _mustSrcuri($release)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+            '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
+    }
+
+    function _uriDepsCannotHaveVersioning($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+            '%type%: dependencies with a <uri> tag cannot have any versioning information');
+    }
+
+    function _conflictingDepsCannotHaveVersioning($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+            '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
+            'exclude specific versions of a dependency');
+    }
+
+    function _DepchannelCannotBeUri($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+            '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
+            'dependencies only');
+    }
+
+    function _bundledPackagesMustBeFilename()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            '<bundledpackage> tags must contain only the filename of a package release ' .
+            'in the bundle');
+    }
+
+    function _binaryPackageMustBePackagename()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            '<binarypackage> tags must contain the name of a package that is ' .
+            'a compiled version of this extsrc/zendextsrc package');
+    }
+
+    function _fileNotFound($file)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+            'File "%file%" in package.xml does not exist');
+    }
+
+    function _notInContents($file, $tag)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
+            '<%tag% name="%file%"> is invalid, file is not in <contents>');
+    }
+
+    function _cannotValidateNoPathSet()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'Cannot validate files, no path to package file is set (use setPackageFile())');
+    }
+
+    function _usesroletaskMustHaveChannelOrUri($role, $tag)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
+            '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
+    }
+
+    function _usesroletaskMustHavePackage($role, $tag)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
+            '<%tag%> for role "%role%" must contain <package>');
+    }
+
+    function _usesroletaskMustHaveRoleTask($tag, $type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
+            '<%tag%> must contain <%type%> defining the %type% to be used');
+    }
+
+    function _cannotConflictWithAllOs($type)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
+            '%tag% cannot conflict with all OSes');
+    }
+
+    function _invalidDepGroupName($name)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+            'Invalid dependency group name "%name%"');
+    }
+
+    function _multipleToplevelDirNotAllowed()
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array(),
+            'Multiple top-level <dir> tags are not allowed.  Enclose them ' .
+                'in a <dir name="/">');
+    }
+
+    function _multipleInstallAs($file)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+            'Only one <install> tag is allowed for file "%file%"');
+    }
+
+    function _ignoreAndInstallAs($file)
+    {
+        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+            'Cannot have both <ignore> and <install> tags for file "%file%"');
+    }
+
+    function _analyzeBundledPackages()
+    {
+        if (!$this->_isValid) {
+            return false;
+        }
+        if (!$this->_pf->getPackageType() == 'bundle') {
+            return false;
+        }
+        if (!isset($this->_pf->_packageFile)) {
+            return false;
+        }
+        $dir_prefix = dirname($this->_pf->_packageFile);
+        $common = new PEAR_Common;
+        $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
+            array($common, 'log');
+        $info = $this->_pf->getContents();
+        $info = $info['bundledpackage'];
+        if (!is_array($info)) {
+            $info = array($info);
+        }
+        $pkg = &new PEAR_PackageFile($this->_pf->_config);
+        foreach ($info as $package) {
+            if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
+                $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
+                $this->_isValid = 0;
+                continue;
+            }
+            call_user_func_array($log, array(1, "Analyzing bundled package $package"));
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
+                PEAR_VALIDATE_NORMAL);
+            PEAR::popErrorHandling();
+            if (PEAR::isError($ret)) {
+                call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
+                    'package'));
+                $inf = $ret->getUserInfo();
+                if (is_array($inf)) {
+                    foreach ($inf as $err) {
+                        call_user_func_array($log, array(1, $err['message']));
+                    }
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function _analyzePhpFiles()
+    {
+        if (!$this->_isValid) {
+            return false;
+        }
+        if (!isset($this->_pf->_packageFile)) {
+            $this->_cannotValidateNoPathSet();
+            return false;
+        }
+        $dir_prefix = dirname($this->_pf->_packageFile);
+        $common = new PEAR_Common;
+        $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
+            array(&$common, 'log');
+        $info = $this->_pf->getContents();
+        if (!$info || !isset($info['dir']['file'])) {
+            $this->_tagCannotBeEmpty('contents><dir');
+            return false;
+        }
+        $info = $info['dir']['file'];
+        if (isset($info['attribs'])) {
+            $info = array($info);
+        }
+        $provides = array();
+        foreach ($info as $fa) {
+            $fa = $fa['attribs'];
+            $file = $fa['name'];
+            if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
+                $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
+                $this->_isValid = 0;
+                continue;
+            }
+            if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
+                call_user_func_array($log, array(1, "Analyzing $file"));
+                $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
+                if ($srcinfo) {
+                    $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
+                }
+            }
+        }
+        $this->_packageName = $pn = $this->_pf->getPackage();
+        $pnl = strlen($pn);
+        foreach ($provides as $key => $what) {
+            if (isset($what['explicit']) || !$what) {
+                // skip conformance checks if the provides entry is
+                // specified in the package.xml file
+                continue;
+            }
+            extract($what);
+            if ($type == 'class') {
+                if (!strncasecmp($name, $pn, $pnl)) {
+                    continue;
+                }
+                $this->_stack->push(__FUNCTION__, 'warning',
+                    array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
+                    'in %file%: %type% "%name%" not prefixed with package name "%package%"');
+            } elseif ($type == 'function') {
+                if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
+                    continue;
+                }
+                $this->_stack->push(__FUNCTION__, 'warning',
+                    array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
+                    'in %file%: %type% "%name%" not prefixed with package name "%package%"');
+            }
+        }
+        return $this->_isValid;
+    }
+
+    /**
+     * Analyze the source code of the given PHP file
+     *
+     * @param  string Filename of the PHP file
+     * @param  boolean whether to analyze $file as the file contents
+     * @return mixed
+     */
+    function analyzeSourceCode($file, $string = false)
+    {
+        if (!function_exists("token_get_all")) {
+            $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+                'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
+            return false;
+        }
+
+        if (!defined('T_DOC_COMMENT')) {
+            define('T_DOC_COMMENT', T_COMMENT);
+        }
+
+        if (!defined('T_INTERFACE')) {
+            define('T_INTERFACE', -1);
+        }
+
+        if (!defined('T_IMPLEMENTS')) {
+            define('T_IMPLEMENTS', -1);
+        }
+
+        if ($string) {
+            $contents = $file;
+        } else {
+            if (!$fp = @fopen($file, "r")) {
+                return false;
+            }
+            fclose($fp);
+            $contents = file_get_contents($file);
+        }
+
+        // Silence this function so we can catch PHP Warnings and show our own custom message
+        $tokens = @token_get_all($contents);
+        if (isset($php_errormsg)) {
+            if (isset($this->_stack)) {
+                $pn = $this->_pf->getPackage();
+                $this->_stack->push(__FUNCTION__, 'warning',
+                        array('file' => $file, 'package' => $pn),
+                        'in %file%: Could not process file for unkown reasons,' .
+                        ' possibly a PHP parse error in %file% from %package%');
+            }
+        }
+/*
+        for ($i = 0; $i < sizeof($tokens); $i++) {
+            @list($token, $data) = $tokens[$i];
+            if (is_string($token)) {
+                var_dump($token);
+            } else {
+                print token_name($token) . ' ';
+                var_dump(rtrim($data));
+            }
+        }
+*/
+        $look_for = 0;
+        $paren_level = 0;
+        $bracket_level = 0;
+        $brace_level = 0;
+        $lastphpdoc = '';
+        $current_class = '';
+        $current_interface = '';
+        $current_class_level = -1;
+        $current_function = '';
+        $current_function_level = -1;
+        $declared_classes = array();
+        $declared_interfaces = array();
+        $declared_functions = array();
+        $declared_methods = array();
+        $used_classes = array();
+        $used_functions = array();
+        $extends = array();
+        $implements = array();
+        $nodeps = array();
+        $inquote = false;
+        $interface = false;
+        for ($i = 0; $i < sizeof($tokens); $i++) {
+            if (is_array($tokens[$i])) {
+                list($token, $data) = $tokens[$i];
+            } else {
+                $token = $tokens[$i];
+                $data = '';
+            }
+
+            if ($inquote) {
+                if ($token != '"' && $token != T_END_HEREDOC) {
+                    continue;
+                } else {
+                    $inquote = false;
+                    continue;
+                }
+            }
+
+            switch ($token) {
+                case T_WHITESPACE :
+                    continue;
+                case ';':
+                    if ($interface) {
+                        $current_function = '';
+                        $current_function_level = -1;
+                    }
+                    break;
+                case '"':
+                case T_START_HEREDOC:
+                    $inquote = true;
+                    break;
+                case T_CURLY_OPEN:
+                case T_DOLLAR_OPEN_CURLY_BRACES:
+                case '{': $brace_level++; continue 2;
+                case '}':
+                    $brace_level--;
+                    if ($current_class_level == $brace_level) {
+                        $current_class = '';
+                        $current_class_level = -1;
+                    }
+                    if ($current_function_level == $brace_level) {
+                        $current_function = '';
+                        $current_function_level = -1;
+                    }
+                    continue 2;
+                case '[': $bracket_level++; continue 2;
+                case ']': $bracket_level--; continue 2;
+                case '(': $paren_level++;   continue 2;
+                case ')': $paren_level--;   continue 2;
+                case T_INTERFACE:
+                    $interface = true;
+                case T_CLASS:
+                    if (($current_class_level != -1) || ($current_function_level != -1)) {
+                        if (isset($this->_stack)) {
+                            $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+                            'Parser error: invalid PHP found in file "%file%"');
+                        } else {
+                            PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
+                                PEAR_COMMON_ERROR_INVALIDPHP);
+                        }
+
+                        return false;
+                    }
+                case T_FUNCTION:
+                case T_NEW:
+                case T_EXTENDS:
+                case T_IMPLEMENTS:
+                    $look_for = $token;
+                    continue 2;
+                case T_STRING:
+                    if (version_compare(zend_version(), '2.0', '<')) {
+                        if (in_array(strtolower($data),
+                            array('public', 'private', 'protected', 'abstract',
+                                  'interface', 'implements', 'throw')
+                                 )
+                        ) {
+                            if (isset($this->_stack)) {
+                                $this->_stack->push(__FUNCTION__, 'warning', array(
+                                    'file' => $file),
+                                    'Error, PHP5 token encountered in %file%,' .
+                                    ' analysis should be in PHP5');
+                            } else {
+                                PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
+                                    'packaging should be done in PHP 5');
+                                return false;
+                            }
+                        }
+                    }
+
+                    if ($look_for == T_CLASS) {
+                        $current_class = $data;
+                        $current_class_level = $brace_level;
+                        $declared_classes[] = $current_class;
+                    } elseif ($look_for == T_INTERFACE) {
+                        $current_interface = $data;
+                        $current_class_level = $brace_level;
+                        $declared_interfaces[] = $current_interface;
+                    } elseif ($look_for == T_IMPLEMENTS) {
+                        $implements[$current_class] = $data;
+                    } elseif ($look_for == T_EXTENDS) {
+                        $extends[$current_class] = $data;
+                    } elseif ($look_for == T_FUNCTION) {
+                        if ($current_class) {
+                            $current_function = "$current_class::$data";
+                            $declared_methods[$current_class][] = $data;
+                        } elseif ($current_interface) {
+                            $current_function = "$current_interface::$data";
+                            $declared_methods[$current_interface][] = $data;
+                        } else {
+                            $current_function = $data;
+                            $declared_functions[] = $current_function;
+                        }
+
+                        $current_function_level = $brace_level;
+                        $m = array();
+                    } elseif ($look_for == T_NEW) {
+                        $used_classes[$data] = true;
+                    }
+
+                    $look_for = 0;
+                    continue 2;
+                case T_VARIABLE:
+                    $look_for = 0;
+                    continue 2;
+                case T_DOC_COMMENT:
+                case T_COMMENT:
+                    if (preg_match('!^/\*\*\s!', $data)) {
+                        $lastphpdoc = $data;
+                        if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
+                            $nodeps = array_merge($nodeps, $m[1]);
+                        }
+                    }
+                    continue 2;
+                case T_DOUBLE_COLON:
+                    $token = $tokens[$i - 1][0];
+                    if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
+                        if (isset($this->_stack)) {
+                            $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
+                                'Parser error: invalid PHP found in file "%file%"');
+                        } else {
+                            PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
+                                PEAR_COMMON_ERROR_INVALIDPHP);
+                        }
+
+                        return false;
+                    }
+
+                    $class = $tokens[$i - 1][1];
+                    if (strtolower($class) != 'parent') {
+                        $used_classes[$class] = true;
+                    }
+
+                    continue 2;
+            }
+        }
+
+        return array(
+            "source_file" => $file,
+            "declared_classes" => $declared_classes,
+            "declared_interfaces" => $declared_interfaces,
+            "declared_methods" => $declared_methods,
+            "declared_functions" => $declared_functions,
+            "used_classes" => array_diff(array_keys($used_classes), $nodeps),
+            "inheritance" => $extends,
+            "implements" => $implements,
+        );
+    }
+
+    /**
+     * Build a "provides" array from data returned by
+     * analyzeSourceCode().  The format of the built array is like
+     * this:
+     *
+     *  array(
+     *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+     *    ...
+     *  )
+     *
+     *
+     * @param array $srcinfo array with information about a source file
+     * as returned by the analyzeSourceCode() method.
+     *
+     * @return void
+     *
+     * @access private
+     *
+     */
+    function _buildProvidesArray($srcinfo)
+    {
+        if (!$this->_isValid) {
+            return array();
+        }
+
+        $providesret = array();
+        $file        = basename($srcinfo['source_file']);
+        $pn          = isset($this->_pf) ? $this->_pf->getPackage() : '';
+        $pnl         = strlen($pn);
+        foreach ($srcinfo['declared_classes'] as $class) {
+            $key = "class;$class";
+            if (isset($providesret[$key])) {
+                continue;
+            }
+
+            $providesret[$key] =
+                array('file'=> $file, 'type' => 'class', 'name' => $class);
+            if (isset($srcinfo['inheritance'][$class])) {
+                $providesret[$key]['extends'] =
+                    $srcinfo['inheritance'][$class];
+            }
+        }
+
+        foreach ($srcinfo['declared_methods'] as $class => $methods) {
+            foreach ($methods as $method) {
+                $function = "$class::$method";
+                $key = "function;$function";
+                if ($method{0} == '_' || !strcasecmp($method, $class) ||
+                    isset($providesret[$key])) {
+                    continue;
+                }
+
+                $providesret[$key] =
+                    array('file'=> $file, 'type' => 'function', 'name' => $function);
+            }
+        }
+
+        foreach ($srcinfo['declared_functions'] as $function) {
+            $key = "function;$function";
+            if ($function{0} == '_' || isset($providesret[$key])) {
+                continue;
+            }
+
+            if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+                $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+            }
+
+            $providesret[$key] =
+                array('file'=> $file, 'type' => 'function', 'name' => $function);
+        }
+
+        return $providesret;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php
new file mode 100644 (file)
index 0000000..58f76c5
--- /dev/null
@@ -0,0 +1,1604 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a8
+ */
+/**
+ * For base class
+ */
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a8
+ */
+class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2
+{
+    /**
+     * @param string Extension name
+     * @return bool success of operation
+     */
+    function setProvidesExtension($extension)
+    {
+        if (in_array($this->getPackageType(),
+              array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+            if (!isset($this->_packageInfo['providesextension'])) {
+                // ensure that the channel tag is set up in the right location
+                $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                    array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease',
+                    'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                    'bundle', 'changelog'),
+                    $extension, 'providesextension');
+            }
+            $this->_packageInfo['providesextension'] = $extension;
+            return true;
+        }
+        return false;
+    }
+
+    function setPackage($package)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['attribs'])) {
+            $this->_packageInfo = array_merge(array('attribs' => array(
+                                 'version' => '2.0',
+                                 'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+                                 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+                                 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+                                 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+    http://pear.php.net/dtd/tasks-1.0.xsd
+    http://pear.php.net/dtd/package-2.0
+    http://pear.php.net/dtd/package-2.0.xsd',
+                             )), $this->_packageInfo);
+        }
+        if (!isset($this->_packageInfo['name'])) {
+            return $this->_packageInfo = array_merge(array('name' => $package),
+                $this->_packageInfo);
+        }
+        $this->_packageInfo['name'] = $package;
+    }
+
+    /**
+     * set this as a package.xml version 2.1
+     * @access private
+     */
+    function _setPackageVersion2_1()
+    {
+        $info = array(
+                                 'version' => '2.1',
+                                 'xmlns' => 'http://pear.php.net/dtd/package-2.1',
+                                 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+                                 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+                                 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+    http://pear.php.net/dtd/tasks-1.0.xsd
+    http://pear.php.net/dtd/package-2.1
+    http://pear.php.net/dtd/package-2.1.xsd',
+                             );
+        if (!isset($this->_packageInfo['attribs'])) {
+            $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo);
+        } else {
+            $this->_packageInfo['attribs'] = $info;
+        }
+    }
+
+    function setUri($uri)
+    {
+        unset($this->_packageInfo['channel']);
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['uri'])) {
+            // ensure that the uri tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('extends', 'summary', 'description', 'lead',
+                'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                'stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $uri, 'uri');
+        }
+        $this->_packageInfo['uri'] = $uri;
+    }
+
+    function setChannel($channel)
+    {
+        unset($this->_packageInfo['uri']);
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['channel'])) {
+            // ensure that the channel tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('extends', 'summary', 'description', 'lead',
+                'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                'stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $channel, 'channel');
+        }
+        $this->_packageInfo['channel'] = $channel;
+    }
+
+    function setExtends($extends)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['extends'])) {
+            // ensure that the extends tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('summary', 'description', 'lead',
+                'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                'stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $extends, 'extends');
+        }
+        $this->_packageInfo['extends'] = $extends;
+    }
+
+    function setSummary($summary)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['summary'])) {
+            // ensure that the summary tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('description', 'lead',
+                'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                'stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $summary, 'summary');
+        }
+        $this->_packageInfo['summary'] = $summary;
+    }
+
+    function setDescription($desc)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['description'])) {
+            // ensure that the description tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('lead',
+                'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                'stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $desc, 'description');
+        }
+        $this->_packageInfo['description'] = $desc;
+    }
+
+    /**
+     * Adds a new maintainer - no checking of duplicates is performed, use
+     * updatemaintainer for that purpose.
+     */
+    function addMaintainer($role, $handle, $name, $email, $active = 'yes')
+    {
+        if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) {
+            return false;
+        }
+        if (isset($this->_packageInfo[$role])) {
+            if (!isset($this->_packageInfo[$role][0])) {
+                $this->_packageInfo[$role] = array($this->_packageInfo[$role]);
+            }
+            $this->_packageInfo[$role][] =
+                array(
+                    'name' => $name,
+                    'user' => $handle,
+                    'email' => $email,
+                    'active' => $active,
+                );
+        } else {
+            $testarr = array('lead',
+                    'developer', 'contributor', 'helper', 'date', 'time', 'version',
+                    'stability', 'license', 'notes', 'contents', 'compatible',
+                    'dependencies', 'providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+                    'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog');
+            foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) {
+                array_shift($testarr);
+                if ($role == $testrole) {
+                    break;
+                }
+            }
+            if (!isset($this->_packageInfo[$role])) {
+                // ensure that the extends tag is set up in the right location
+                $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr,
+                    array(), $role);
+            }
+            $this->_packageInfo[$role] =
+                array(
+                    'name' => $name,
+                    'user' => $handle,
+                    'email' => $email,
+                    'active' => $active,
+                );
+        }
+        $this->_isValid = 0;
+    }
+
+    function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes')
+    {
+        $found = false;
+        foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+            if (!isset($this->_packageInfo[$role])) {
+                continue;
+            }
+            $info = $this->_packageInfo[$role];
+            if (!isset($info[0])) {
+                if ($info['user'] == $handle) {
+                    $found = true;
+                    break;
+                }
+            }
+            foreach ($info as $i => $maintainer) {
+                if ($maintainer['user'] == $handle) {
+                    $found = $i;
+                    break 2;
+                }
+            }
+        }
+        if ($found === false) {
+            return $this->addMaintainer($newrole, $handle, $name, $email, $active);
+        }
+        if ($found !== false) {
+            if ($found === true) {
+                unset($this->_packageInfo[$role]);
+            } else {
+                unset($this->_packageInfo[$role][$found]);
+                $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]);
+            }
+        }
+        $this->addMaintainer($newrole, $handle, $name, $email, $active);
+        $this->_isValid = 0;
+    }
+
+    function deleteMaintainer($handle)
+    {
+        $found = false;
+        foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+            if (!isset($this->_packageInfo[$role])) {
+                continue;
+            }
+            if (!isset($this->_packageInfo[$role][0])) {
+                $this->_packageInfo[$role] = array($this->_packageInfo[$role]);
+            }
+            foreach ($this->_packageInfo[$role] as $i => $maintainer) {
+                if ($maintainer['user'] == $handle) {
+                    $found = $i;
+                    break;
+                }
+            }
+            if ($found !== false) {
+                unset($this->_packageInfo[$role][$found]);
+                if (!count($this->_packageInfo[$role]) && $role == 'lead') {
+                    $this->_isValid = 0;
+                }
+                if (!count($this->_packageInfo[$role])) {
+                    unset($this->_packageInfo[$role]);
+                    return true;
+                }
+                $this->_packageInfo[$role] =
+                    array_values($this->_packageInfo[$role]);
+                if (count($this->_packageInfo[$role]) == 1) {
+                    $this->_packageInfo[$role] = $this->_packageInfo[$role][0];
+                }
+                return true;
+            }
+            if (count($this->_packageInfo[$role]) == 1) {
+                $this->_packageInfo[$role] = $this->_packageInfo[$role][0];
+            }
+        }
+        return false;
+    }
+
+    function setReleaseVersion($version)
+    {
+        if (isset($this->_packageInfo['version']) &&
+              isset($this->_packageInfo['version']['release'])) {
+            unset($this->_packageInfo['version']['release']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array(
+            'version' => array('stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'),
+            'release' => array('api')));
+        $this->_isValid = 0;
+    }
+
+    function setAPIVersion($version)
+    {
+        if (isset($this->_packageInfo['version']) &&
+              isset($this->_packageInfo['version']['api'])) {
+            unset($this->_packageInfo['version']['api']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array(
+            'version' => array('stability', 'license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'),
+            'api' => array()));
+        $this->_isValid = 0;
+    }
+
+    /**
+     * snapshot|devel|alpha|beta|stable
+     */
+    function setReleaseStability($state)
+    {
+        if (isset($this->_packageInfo['stability']) &&
+              isset($this->_packageInfo['stability']['release'])) {
+            unset($this->_packageInfo['stability']['release']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array(
+            'stability' => array('license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'),
+            'release' => array('api')));
+        $this->_isValid = 0;
+    }
+
+    /**
+     * @param devel|alpha|beta|stable
+     */
+    function setAPIStability($state)
+    {
+        if (isset($this->_packageInfo['stability']) &&
+              isset($this->_packageInfo['stability']['api'])) {
+            unset($this->_packageInfo['stability']['api']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array(
+            'stability' => array('license', 'notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'),
+            'api' => array()));
+        $this->_isValid = 0;
+    }
+
+    function setLicense($license, $uri = false, $filesource = false)
+    {
+        if (!isset($this->_packageInfo['license'])) {
+            // ensure that the license tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('notes', 'contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), 0, 'license');
+        }
+        if ($uri || $filesource) {
+            $attribs = array();
+            if ($uri) {
+                $attribs['uri'] = $uri;
+            }
+            $uri = true; // for test below
+            if ($filesource) {
+                $attribs['filesource'] = $filesource;
+            }
+        }
+        $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license;
+        $this->_packageInfo['license'] = $license;
+        $this->_isValid = 0;
+    }
+
+    function setNotes($notes)
+    {
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['notes'])) {
+            // ensure that the notes tag is set up in the right location
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('contents', 'compatible',
+                'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+                'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'extbinrelease', 'bundle', 'changelog'), $notes, 'notes');
+        }
+        $this->_packageInfo['notes'] = $notes;
+    }
+
+    /**
+     * This is only used at install-time, after all serialization
+     * is over.
+     * @param string file name
+     * @param string installed path
+     */
+    function setInstalledAs($file, $path)
+    {
+        if ($path) {
+            return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+        }
+        unset($this->_packageInfo['filelist'][$file]['installed_as']);
+    }
+
+    /**
+     * This is only used at install-time, after all serialization
+     * is over.
+     */
+    function installedFile($file, $atts)
+    {
+        if (isset($this->_packageInfo['filelist'][$file])) {
+            $this->_packageInfo['filelist'][$file] =
+                array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
+        } else {
+            $this->_packageInfo['filelist'][$file] = $atts['attribs'];
+        }
+    }
+
+    /**
+     * Reset the listing of package contents
+     * @param string base installation dir for the whole package, if any
+     */
+    function clearContents($baseinstall = false)
+    {
+        $this->_filesValid = false;
+        $this->_isValid = 0;
+        if (!isset($this->_packageInfo['contents'])) {
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('compatible',
+                    'dependencies', 'providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+                    'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                    'bundle', 'changelog'), array(), 'contents');
+        }
+        if ($this->getPackageType() != 'bundle') {
+            $this->_packageInfo['contents'] =
+                array('dir' => array('attribs' => array('name' => '/')));
+            if ($baseinstall) {
+                $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall;
+            }
+        } else {
+            $this->_packageInfo['contents'] = array('bundledpackage' => array());
+        }
+    }
+
+    /**
+     * @param string relative path of the bundled package.
+     */
+    function addBundledPackage($path)
+    {
+        if ($this->getPackageType() != 'bundle') {
+            return false;
+        }
+        $this->_filesValid = false;
+        $this->_isValid = 0;
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array(
+                'contents' => array('compatible', 'dependencies', 'providesextension',
+                'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease',
+                'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'bundle', 'changelog'),
+                'bundledpackage' => array()));
+    }
+
+    /**
+     * @param string file name
+     * @param PEAR_Task_Common a read/write task
+     */
+    function addTaskToFile($filename, $task)
+    {
+        if (!method_exists($task, 'getXml')) {
+            return false;
+        }
+        if (!method_exists($task, 'getName')) {
+            return false;
+        }
+        if (!method_exists($task, 'validate')) {
+            return false;
+        }
+        if (!$task->validate()) {
+            return false;
+        }
+        if (!isset($this->_packageInfo['contents']['dir']['file'])) {
+            return false;
+        }
+        $this->getTasksNs(); // discover the tasks namespace if not done already
+        $files = $this->_packageInfo['contents']['dir']['file'];
+        if (!isset($files[0])) {
+            $files = array($files);
+            $ind = false;
+        } else {
+            $ind = true;
+        }
+        foreach ($files as $i => $file) {
+            if (isset($file['attribs'])) {
+                if ($file['attribs']['name'] == $filename) {
+                    if ($ind) {
+                        $t = isset($this->_packageInfo['contents']['dir']['file'][$i]
+                              ['attribs'][$this->_tasksNs .
+                              ':' . $task->getName()]) ?
+                              $this->_packageInfo['contents']['dir']['file'][$i]
+                              ['attribs'][$this->_tasksNs .
+                              ':' . $task->getName()] : false;
+                        if ($t && !isset($t[0])) {
+                            $this->_packageInfo['contents']['dir']['file'][$i]
+                                [$this->_tasksNs . ':' . $task->getName()] = array($t);
+                        }
+                        $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs .
+                            ':' . $task->getName()][] = $task->getXml();
+                    } else {
+                        $t = isset($this->_packageInfo['contents']['dir']['file']
+                              ['attribs'][$this->_tasksNs .
+                              ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file']
+                              ['attribs'][$this->_tasksNs .
+                              ':' . $task->getName()] : false;
+                        if ($t && !isset($t[0])) {
+                            $this->_packageInfo['contents']['dir']['file']
+                                [$this->_tasksNs . ':' . $task->getName()] = array($t);
+                        }
+                        $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs .
+                            ':' . $task->getName()][] = $task->getXml();
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param string path to the file
+     * @param string filename
+     * @param array extra attributes
+     */
+    function addFile($dir, $file, $attrs)
+    {
+        if ($this->getPackageType() == 'bundle') {
+            return false;
+        }
+        $this->_filesValid = false;
+        $this->_isValid = 0;
+        $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir);
+        if ($dir == '/' || $dir == '') {
+            $dir = '';
+        } else {
+            $dir .= '/';
+        }
+        $attrs['name'] = $dir . $file;
+        if (!isset($this->_packageInfo['contents'])) {
+            // ensure that the contents tag is set up
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+                array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask',
+                'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+                'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'bundle', 'changelog'), array(), 'contents');
+        }
+        if (isset($this->_packageInfo['contents']['dir']['file'])) {
+            if (!isset($this->_packageInfo['contents']['dir']['file'][0])) {
+                $this->_packageInfo['contents']['dir']['file'] =
+                    array($this->_packageInfo['contents']['dir']['file']);
+            }
+            $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs;
+        } else {
+            $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs;
+        }
+    }
+
+    /**
+     * @param string Dependent package name
+     * @param string Dependent package's channel name
+     * @param string minimum version of specified package that this release is guaranteed to be
+     *               compatible with
+     * @param string maximum version of specified package that this release is guaranteed to be
+     *               compatible with
+     * @param string versions of specified package that this release is not compatible with
+     */
+    function addCompatiblePackage($name, $channel, $min, $max, $exclude = false)
+    {
+        $this->_isValid = 0;
+        $set = array(
+            'name' => $name,
+            'channel' => $channel,
+            'min' => $min,
+            'max' => $max,
+        );
+        if ($exclude) {
+            $set['exclude'] = $exclude;
+        }
+        $this->_isValid = 0;
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+                'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+            ));
+    }
+
+    /**
+     * Removes the <usesrole> tag entirely
+     */
+    function resetUsesrole()
+    {
+        if (isset($this->_packageInfo['usesrole'])) {
+            unset($this->_packageInfo['usesrole']);
+        }
+    }
+
+    /**
+     * @param string
+     * @param string package name or uri
+     * @param string channel name if non-uri
+     */
+    function addUsesrole($role, $packageOrUri, $channel = false) {
+        $set = array('role' => $role);
+        if ($channel) {
+            $set['package'] = $packageOrUri;
+            $set['channel'] = $channel;
+        } else {
+            $set['uri'] = $packageOrUri;
+        }
+        $this->_isValid = 0;
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+                'usesrole' => array('usestask', 'srcpackage', 'srcuri',
+                    'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+            ));
+    }
+
+    /**
+     * Removes the <usestask> tag entirely
+     */
+    function resetUsestask()
+    {
+        if (isset($this->_packageInfo['usestask'])) {
+            unset($this->_packageInfo['usestask']);
+        }
+    }
+
+
+    /**
+     * @param string
+     * @param string package name or uri
+     * @param string channel name if non-uri
+     */
+    function addUsestask($task, $packageOrUri, $channel = false) {
+        $set = array('task' => $task);
+        if ($channel) {
+            $set['package'] = $packageOrUri;
+            $set['channel'] = $channel;
+        } else {
+            $set['uri'] = $packageOrUri;
+        }
+        $this->_isValid = 0;
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+                'usestask' => array('srcpackage', 'srcuri',
+                    'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+            ));
+    }
+
+    /**
+     * Remove all compatible tags
+     */
+    function clearCompatible()
+    {
+        unset($this->_packageInfo['compatible']);
+    }
+
+    /**
+     * Reset dependencies prior to adding new ones
+     */
+    function clearDeps()
+    {
+        if (!isset($this->_packageInfo['dependencies'])) {
+            $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(),
+                array(
+                    'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                        'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                        'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')));
+        }
+        $this->_packageInfo['dependencies'] = array();
+    }
+
+    /**
+     * @param string minimum PHP version allowed
+     * @param string maximum PHP version allowed
+     * @param array $exclude incompatible PHP versions
+     */
+    function setPhpDep($min, $max = false, $exclude = false)
+    {
+        $this->_isValid = 0;
+        $dep =
+            array(
+                'min' => $min,
+            );
+        if ($max) {
+            $dep['max'] = $max;
+        }
+        if ($exclude) {
+            if (count($exclude) == 1) {
+                $exclude = $exclude[0];
+            }
+            $dep['exclude'] = $exclude;
+        }
+        if (isset($this->_packageInfo['dependencies']['required']['php'])) {
+            $this->_stack->push(__FUNCTION__, 'warning', array('dep' =>
+            $this->_packageInfo['dependencies']['required']['php']),
+                'warning: PHP dependency already exists, overwriting');
+            unset($this->_packageInfo['dependencies']['required']['php']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch')
+            ));
+        return true;
+    }
+
+    /**
+     * @param string minimum allowed PEAR installer version
+     * @param string maximum allowed PEAR installer version
+     * @param string recommended PEAR installer version
+     * @param array incompatible version of the PEAR installer
+     */
+    function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false)
+    {
+        $this->_isValid = 0;
+        $dep =
+            array(
+                'min' => $min,
+            );
+        if ($max) {
+            $dep['max'] = $max;
+        }
+        if ($recommended) {
+            $dep['recommended'] = $recommended;
+        }
+        if ($exclude) {
+            if (count($exclude) == 1) {
+                $exclude = $exclude[0];
+            }
+            $dep['exclude'] = $exclude;
+        }
+        if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) {
+            $this->_stack->push(__FUNCTION__, 'warning', array('dep' =>
+            $this->_packageInfo['dependencies']['required']['pearinstaller']),
+                'warning: PEAR Installer dependency already exists, overwriting');
+            unset($this->_packageInfo['dependencies']['required']['pearinstaller']);
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * Mark a package as conflicting with this package
+     * @param string package name
+     * @param string package channel
+     * @param string extension this package provides, if any
+     * @param string|false minimum version required
+     * @param string|false maximum version allowed
+     * @param array|false versions to exclude from installation
+     */
+    function addConflictingPackageDepWithChannel($name, $channel,
+                $providesextension = false, $min = false, $max = false, $exclude = false)
+    {
+        $this->_isValid = 0;
+        $dep = $this->_constructDep($name, $channel, false, $min, $max, false,
+            $exclude, $providesextension, false, true);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'package' => array('subpackage', 'extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * Mark a package as conflicting with this package
+     * @param string package name
+     * @param string package channel
+     * @param string extension this package provides, if any
+     */
+    function addConflictingPackageDepWithUri($name, $uri, $providesextension = false)
+    {
+        $this->_isValid = 0;
+        $dep =
+            array(
+                'name' => $name,
+                'uri' => $uri,
+                'conflicts' => '',
+            );
+        if ($providesextension) {
+            $dep['providesextension'] = $providesextension;
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'package' => array('subpackage', 'extension', 'os', 'arch')
+            ));
+    }
+
+    function addDependencyGroup($name, $hint)
+    {
+        $this->_isValid = 0;
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo,
+            array('attribs' => array('name' => $name, 'hint' => $hint)),
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'group' => array(),
+            ));
+    }
+
+    /**
+     * @param string package name
+     * @param string|false channel name, false if this is a uri
+     * @param string|false uri name, false if this is a channel
+     * @param string|false minimum version required
+     * @param string|false maximum version allowed
+     * @param string|false recommended installation version
+     * @param array|false versions to exclude from installation
+     * @param string extension this package provides, if any
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     * @param bool if true, tells the installer to negate this dependency (conflicts)
+     * @return array
+     * @access private
+     */
+    function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude,
+                           $providesextension = false, $nodefault = false,
+                           $conflicts = false)
+    {
+        $dep =
+            array(
+                'name' => $name,
+            );
+        if ($channel) {
+            $dep['channel'] = $channel;
+        } elseif ($uri) {
+            $dep['uri'] = $uri;
+        }
+        if ($min) {
+            $dep['min'] = $min;
+        }
+        if ($max) {
+            $dep['max'] = $max;
+        }
+        if ($recommended) {
+            $dep['recommended'] = $recommended;
+        }
+        if ($exclude) {
+            if (is_array($exclude) && count($exclude) == 1) {
+                $exclude = $exclude[0];
+            }
+            $dep['exclude'] = $exclude;
+        }
+        if ($conflicts) {
+            $dep['conflicts'] = '';
+        }
+        if ($nodefault) {
+            $dep['nodefault'] = '';
+        }
+        if ($providesextension) {
+            $dep['providesextension'] = $providesextension;
+        }
+        return $dep;
+    }
+
+    /**
+     * @param package|subpackage
+     * @param string group name
+     * @param string package name
+     * @param string package channel
+     * @param string minimum version
+     * @param string maximum version
+     * @param string recommended version
+     * @param array|false optional excluded versions
+     * @param string extension this package provides, if any
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     * @return bool false if the dependency group has not been initialized with
+     *              {@link addDependencyGroup()}, or a subpackage is added with
+     *              a providesextension
+     */
+    function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false,
+                                      $max = false, $recommended = false, $exclude = false,
+                                      $providesextension = false, $nodefault = false)
+    {
+        if ($type == 'subpackage' && $providesextension) {
+            return false; // subpackages must be php packages
+        }
+        $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+            $providesextension, $nodefault);
+        return $this->_addGroupDependency($type, $dep, $groupname);
+    }
+
+    /**
+     * @param package|subpackage
+     * @param string group name
+     * @param string package name
+     * @param string package uri
+     * @param string extension this package provides, if any
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     * @return bool false if the dependency group has not been initialized with
+     *              {@link addDependencyGroup()}
+     */
+    function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false,
+                                       $nodefault = false)
+    {
+        if ($type == 'subpackage' && $providesextension) {
+            return false; // subpackages must be php packages
+        }
+        $dep = $this->_constructDep($name, false, $uri, false, false, false, false,
+            $providesextension, $nodefault);
+        return $this->_addGroupDependency($type, $dep, $groupname);
+    }
+
+    /**
+     * @param string group name (must be pre-existing)
+     * @param string extension name
+     * @param string minimum version allowed
+     * @param string maximum version allowed
+     * @param string recommended version
+     * @param array incompatible versions
+     */
+    function addGroupExtensionDep($groupname, $name, $min = false, $max = false,
+                                         $recommended = false, $exclude = false)
+    {
+        $this->_isValid = 0;
+        $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+        return $this->_addGroupDependency('extension', $dep, $groupname);
+    }
+
+    /**
+     * @param package|subpackage|extension
+     * @param array dependency contents
+     * @param string name of the dependency group to add this to
+     * @return boolean
+     * @access private
+     */
+    function _addGroupDependency($type, $dep, $groupname)
+    {
+        $arr = array('subpackage', 'extension');
+        if ($type != 'package') {
+            array_shift($arr);
+        }
+        if ($type == 'extension') {
+            array_shift($arr);
+        }
+        if (!isset($this->_packageInfo['dependencies']['group'])) {
+            return false;
+        } else {
+            if (!isset($this->_packageInfo['dependencies']['group'][0])) {
+                if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) {
+                    $this->_packageInfo['dependencies']['group'] = $this->_mergeTag(
+                        $this->_packageInfo['dependencies']['group'], $dep,
+                        array(
+                            $type => $arr
+                        ));
+                    $this->_isValid = 0;
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) {
+                    if ($group['attribs']['name'] == $groupname) {
+                    $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag(
+                        $this->_packageInfo['dependencies']['group'][$i], $dep,
+                        array(
+                            $type => $arr
+                        ));
+                        $this->_isValid = 0;
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @param optional|required
+     * @param string package name
+     * @param string package channel
+     * @param string minimum version
+     * @param string maximum version
+     * @param string recommended version
+     * @param string extension this package provides, if any
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     * @param array|false optional excluded versions
+     */
+    function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false,
+                                      $recommended = false, $exclude = false,
+                                      $providesextension = false, $nodefault = false)
+    {
+        if (!in_array($type, array('optional', 'required'), true)) {
+            $type = 'required';
+        }
+        $this->_isValid = 0;
+        $arr = array('optional', 'group');
+        if ($type != 'required') {
+            array_shift($arr);
+        }
+        $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+            $providesextension, $nodefault);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                $type => $arr,
+                'package' => array('subpackage', 'extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * @param optional|required
+     * @param string name of the package
+     * @param string uri of the package
+     * @param string extension this package provides, if any
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     */
+    function addPackageDepWithUri($type, $name, $uri, $providesextension = false,
+                                  $nodefault = false)
+    {
+        $this->_isValid = 0;
+        $arr = array('optional', 'group');
+        if ($type != 'required') {
+            array_shift($arr);
+        }
+        $dep = $this->_constructDep($name, false, $uri, false, false, false, false,
+            $providesextension, $nodefault);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                $type => $arr,
+                'package' => array('subpackage', 'extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * @param optional|required optional, required
+     * @param string package name
+     * @param string package channel
+     * @param string minimum version
+     * @param string maximum version
+     * @param string recommended version
+     * @param array incompatible versions
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     */
+    function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false,
+                                         $recommended = false, $exclude = false,
+                                         $nodefault = false)
+    {
+        $this->_isValid = 0;
+        $arr = array('optional', 'group');
+        if ($type != 'required') {
+            array_shift($arr);
+        }
+        $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+            $nodefault);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                $type => $arr,
+                'subpackage' => array('extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * @param optional|required optional, required
+     * @param string package name
+     * @param string package uri for download
+     * @param bool if true, tells the installer to ignore the default optional dependency group
+     *             when installing this package
+     */
+    function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false)
+    {
+        $this->_isValid = 0;
+        $arr = array('optional', 'group');
+        if ($type != 'required') {
+            array_shift($arr);
+        }
+        $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                $type => $arr,
+                'subpackage' => array('extension', 'os', 'arch')
+            ));
+    }
+
+    /**
+     * @param optional|required optional, required
+     * @param string extension name
+     * @param string minimum version
+     * @param string maximum version
+     * @param string recommended version
+     * @param array incompatible versions
+     */
+    function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false,
+                             $exclude = false)
+    {
+        $this->_isValid = 0;
+        $arr = array('optional', 'group');
+        if ($type != 'required') {
+            array_shift($arr);
+        }
+        $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                $type => $arr,
+                'extension' => array('os', 'arch')
+            ));
+    }
+
+    /**
+     * @param string Operating system name
+     * @param boolean true if this package cannot be installed on this OS
+     */
+    function addOsDep($name, $conflicts = false)
+    {
+        $this->_isValid = 0;
+        $dep = array('name' => $name);
+        if ($conflicts) {
+            $dep['conflicts'] = '';
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'os' => array('arch')
+            ));
+    }
+
+    /**
+     * @param string Architecture matching pattern
+     * @param boolean true if this package cannot be installed on this architecture
+     */
+    function addArchDep($pattern, $conflicts = false)
+    {
+        $this->_isValid = 0;
+        $dep = array('pattern' => $pattern);
+        if ($conflicts) {
+            $dep['conflicts'] = '';
+        }
+        $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+            array(
+                'dependencies' => array('providesextension', 'usesrole', 'usestask',
+                    'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+                    'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+                'required' => array('optional', 'group'),
+                'arch' => array()
+            ));
+    }
+
+    /**
+     * Set the kind of package, and erase all release tags
+     *
+     * - a php package is a PEAR-style package
+     * - an extbin package is a PECL-style extension binary
+     * - an extsrc package is a PECL-style source for a binary
+     * - an zendextbin package is a PECL-style zend extension binary
+     * - an zendextsrc package is a PECL-style source for a zend extension binary
+     * - a bundle package is a collection of other pre-packaged packages
+     * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle
+     * @return bool success
+     */
+    function setPackageType($type)
+    {
+        $this->_isValid = 0;
+        if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc',
+                                   'zendextbin', 'bundle'))) {
+            return false;
+        }
+
+        if (in_array($type, array('zendextsrc', 'zendextbin'))) {
+            $this->_setPackageVersion2_1();
+        }
+
+        if ($type != 'bundle') {
+            $type .= 'release';
+        }
+
+        foreach (array('phprelease', 'extbinrelease', 'extsrcrelease',
+                       'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) {
+            unset($this->_packageInfo[$test]);
+        }
+
+        if (!isset($this->_packageInfo[$type])) {
+            // ensure that the release tag is set up
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'),
+                array(), $type);
+        }
+
+        $this->_packageInfo[$type] = array();
+        return true;
+    }
+
+    /**
+     * @return bool true if package type is set up
+     */
+    function addRelease()
+    {
+        if ($type = $this->getPackageType()) {
+            if ($type != 'bundle') {
+                $type .= 'release';
+            }
+            $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(),
+                array($type => array('changelog')));
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get the current release tag in order to add to it
+     * @param bool returns only releases that have installcondition if true
+     * @return array|null
+     */
+    function &_getCurrentRelease($strict = true)
+    {
+        if ($p = $this->getPackageType()) {
+            if ($strict) {
+                if ($p == 'extsrc' || $p == 'zendextsrc') {
+                    $a = null;
+                    return $a;
+                }
+            }
+            if ($p != 'bundle') {
+                $p .= 'release';
+            }
+            if (isset($this->_packageInfo[$p][0])) {
+                return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1];
+            } else {
+                return $this->_packageInfo[$p];
+            }
+        } else {
+            $a = null;
+            return $a;
+        }
+    }
+
+    /**
+     * Add a file to the current release that should be installed under a different name
+     * @param string <contents> path to file
+     * @param string name the file should be installed as
+     */
+    function addInstallAs($path, $as)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)),
+            array(
+                'filelist' => array(),
+                'install' => array('ignore')
+            ));
+    }
+
+    /**
+     * Add a file to the current release that should be ignored
+     * @param string <contents> path to file
+     * @return bool success of operation
+     */
+    function addIgnore($path)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)),
+            array(
+                'filelist' => array(),
+                'ignore' => array()
+            ));
+    }
+
+    /**
+     * Add an extension binary package for this extension source code release
+     *
+     * Note that the package must be from the same channel as the extension source package
+     * @param string
+     */
+    function addBinarypackage($package)
+    {
+        if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+            return false;
+        }
+        $r = &$this->_getCurrentRelease(false);
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        $r = $this->_mergeTag($r, $package,
+            array(
+                'binarypackage' => array('filelist'),
+            ));
+    }
+
+    /**
+     * Add a configureoption to an extension source package
+     * @param string
+     * @param string
+     * @param string
+     */
+    function addConfigureOption($name, $prompt, $default = null)
+    {
+        if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+            return false;
+        }
+
+        $r = &$this->_getCurrentRelease(false);
+        if ($r === null) {
+            return false;
+        }
+
+        $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt));
+        if ($default !== null) {
+            $opt['attribs']['default'] = $default;
+        }
+
+        $this->_isValid = 0;
+        $r = $this->_mergeTag($r, $opt,
+            array(
+                'configureoption' => array('binarypackage', 'filelist'),
+            ));
+    }
+
+    /**
+     * Set an installation condition based on php version for the current release set
+     * @param string minimum version
+     * @param string maximum version
+     * @param false|array incompatible versions of PHP
+     */
+    function setPhpInstallCondition($min, $max, $exclude = false)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        if (isset($r['installconditions']['php'])) {
+            unset($r['installconditions']['php']);
+        }
+        $dep = array('min' => $min, 'max' => $max);
+        if ($exclude) {
+            if (is_array($exclude) && count($exclude) == 1) {
+                $exclude = $exclude[0];
+            }
+            $dep['exclude'] = $exclude;
+        }
+        if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('configureoption', 'binarypackage',
+                        'filelist'),
+                    'php' => array('extension', 'os', 'arch')
+                ));
+        } else {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('filelist'),
+                    'php' => array('extension', 'os', 'arch')
+                ));
+        }
+    }
+
+    /**
+     * @param optional|required optional, required
+     * @param string extension name
+     * @param string minimum version
+     * @param string maximum version
+     * @param string recommended version
+     * @param array incompatible versions
+     */
+    function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false,
+                                          $exclude = false)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+        if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('configureoption', 'binarypackage',
+                        'filelist'),
+                    'extension' => array('os', 'arch')
+                ));
+        } else {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('filelist'),
+                    'extension' => array('os', 'arch')
+                ));
+        }
+    }
+
+    /**
+     * Set an installation condition based on operating system for the current release set
+     * @param string OS name
+     * @param bool whether this OS is incompatible with the current release
+     */
+    function setOsInstallCondition($name, $conflicts = false)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        if (isset($r['installconditions']['os'])) {
+            unset($r['installconditions']['os']);
+        }
+        $dep = array('name' => $name);
+        if ($conflicts) {
+            $dep['conflicts'] = '';
+        }
+        if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('configureoption', 'binarypackage',
+                        'filelist'),
+                    'os' => array('arch')
+                ));
+        } else {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('filelist'),
+                    'os' => array('arch')
+                ));
+        }
+    }
+
+    /**
+     * Set an installation condition based on architecture for the current release set
+     * @param string architecture pattern
+     * @param bool whether this arch is incompatible with the current release
+     */
+    function setArchInstallCondition($pattern, $conflicts = false)
+    {
+        $r = &$this->_getCurrentRelease();
+        if ($r === null) {
+            return false;
+        }
+        $this->_isValid = 0;
+        if (isset($r['installconditions']['arch'])) {
+            unset($r['installconditions']['arch']);
+        }
+        $dep = array('pattern' => $pattern);
+        if ($conflicts) {
+            $dep['conflicts'] = '';
+        }
+        if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('configureoption', 'binarypackage',
+                        'filelist'),
+                    'arch' => array()
+                ));
+        } else {
+            $r = $this->_mergeTag($r, $dep,
+                array(
+                    'installconditions' => array('filelist'),
+                    'arch' => array()
+                ));
+        }
+    }
+
+    /**
+     * For extension binary releases, this is used to specify either the
+     * static URI to a source package, or the package name and channel of the extsrc/zendextsrc
+     * package it is based on.
+     * @param string Package name, or full URI to source package (extsrc/zendextsrc type)
+     */
+    function setSourcePackage($packageOrUri)
+    {
+        $this->_isValid = 0;
+        if (isset($this->_packageInfo['channel'])) {
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease',
+                'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'bundle', 'changelog'),
+                $packageOrUri, 'srcpackage');
+        } else {
+            $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease',
+                'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+                'bundle', 'changelog'), $packageOrUri, 'srcuri');
+        }
+    }
+
+    /**
+     * Generate a valid change log entry from the current package.xml
+     * @param string|false
+     */
+    function generateChangeLogEntry($notes = false)
+    {
+        return array(
+            'version' =>
+                array(
+                    'release' => $this->getVersion('release'),
+                    'api' => $this->getVersion('api'),
+                    ),
+            'stability' =>
+                $this->getStability(),
+            'date' => $this->getDate(),
+            'license' => $this->getLicense(true),
+            'notes' => $notes ? $notes : $this->getNotes()
+            );
+    }
+
+    /**
+     * @param string release version to set change log notes for
+     * @param array output of {@link generateChangeLogEntry()}
+     */
+    function setChangelogEntry($releaseversion, $contents)
+    {
+        if (!isset($this->_packageInfo['changelog'])) {
+            $this->_packageInfo['changelog']['release'] = $contents;
+            return;
+        }
+        if (!isset($this->_packageInfo['changelog']['release'][0])) {
+            if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) {
+                $this->_packageInfo['changelog']['release'] = array(
+                    $this->_packageInfo['changelog']['release']);
+            } else {
+                $this->_packageInfo['changelog']['release'] = array(
+                    $this->_packageInfo['changelog']['release']);
+                return $this->_packageInfo['changelog']['release'][] = $contents;
+            }
+        }
+        foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) {
+            if (isset($changelog['version']) &&
+                  strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) {
+                $curlog = $index;
+            }
+        }
+        if (isset($curlog)) {
+            $this->_packageInfo['changelog']['release'][$curlog] = $contents;
+        } else {
+            $this->_packageInfo['changelog']['release'][] = $contents;
+        }
+    }
+
+    /**
+     * Remove the changelog entirely
+     */
+    function clearChangeLog()
+    {
+        unset($this->_packageInfo['changelog']);
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Packager.php b/WEB-INF/lib/pear/PEAR/Packager.php
new file mode 100644 (file)
index 0000000..8995a16
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+/**
+ * PEAR_Packager for generating releases
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Packager.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Common.php';
+require_once 'PEAR/PackageFile.php';
+require_once 'System.php';
+
+/**
+ * Administration class used to make a PEAR release tarball.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Packager extends PEAR_Common
+{
+    /**
+     * @var PEAR_Registry
+     */
+    var $_registry;
+
+    function package($pkgfile = null, $compress = true, $pkg2 = null)
+    {
+        // {{{ validate supplied package.xml file
+        if (empty($pkgfile)) {
+            $pkgfile = 'package.xml';
+        }
+
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $pkg  = &new PEAR_PackageFile($this->config, $this->debug);
+        $pf   = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL);
+        $main = &$pf;
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($pf)) {
+            if (is_array($pf->getUserInfo())) {
+                foreach ($pf->getUserInfo() as $error) {
+                    $this->log(0, 'Error: ' . $error['message']);
+                }
+            }
+
+            $this->log(0, $pf->getMessage());
+            return $this->raiseError("Cannot package, errors in package file");
+        }
+
+        foreach ($pf->getValidationWarnings() as $warning) {
+            $this->log(1, 'Warning: ' . $warning['message']);
+        }
+
+        // }}}
+        if ($pkg2) {
+            $this->log(0, 'Attempting to process the second package file');
+            PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+            $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL);
+            PEAR::staticPopErrorHandling();
+            if (PEAR::isError($pf2)) {
+                if (is_array($pf2->getUserInfo())) {
+                    foreach ($pf2->getUserInfo() as $error) {
+                        $this->log(0, 'Error: ' . $error['message']);
+                    }
+                }
+                $this->log(0, $pf2->getMessage());
+                return $this->raiseError("Cannot package, errors in second package file");
+            }
+
+            foreach ($pf2->getValidationWarnings() as $warning) {
+                $this->log(1, 'Warning: ' . $warning['message']);
+            }
+
+            if ($pf2->getPackagexmlVersion() == '2.0' ||
+                  $pf2->getPackagexmlVersion() == '2.1'
+            ) {
+                $main  = &$pf2;
+                $other = &$pf;
+            } else {
+                $main  = &$pf;
+                $other = &$pf2;
+            }
+
+            if ($main->getPackagexmlVersion() != '2.0' &&
+                  $main->getPackagexmlVersion() != '2.1') {
+                return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' .
+                    'only package together a package.xml 1.0 and package.xml 2.0');
+            }
+
+            if ($other->getPackagexmlVersion() != '1.0') {
+                return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' .
+                    'only package together a package.xml 1.0 and package.xml 2.0');
+            }
+        }
+
+        $main->setLogger($this);
+        if (!$main->validate(PEAR_VALIDATE_PACKAGING)) {
+            foreach ($main->getValidationWarnings() as $warning) {
+                $this->log(0, 'Error: ' . $warning['message']);
+            }
+            return $this->raiseError("Cannot package, errors in package");
+        }
+
+        foreach ($main->getValidationWarnings() as $warning) {
+            $this->log(1, 'Warning: ' . $warning['message']);
+        }
+
+        if ($pkg2) {
+            $other->setLogger($this);
+            $a = false;
+            if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) {
+                foreach ($other->getValidationWarnings() as $warning) {
+                    $this->log(0, 'Error: ' . $warning['message']);
+                }
+
+                foreach ($main->getValidationWarnings() as $warning) {
+                    $this->log(0, 'Error: ' . $warning['message']);
+                }
+
+                if ($a) {
+                    return $this->raiseError('The two package.xml files are not equivalent!');
+                }
+
+                return $this->raiseError("Cannot package, errors in package");
+            }
+
+            foreach ($other->getValidationWarnings() as $warning) {
+                $this->log(1, 'Warning: ' . $warning['message']);
+            }
+
+            $gen = &$main->getDefaultGenerator();
+            $tgzfile = $gen->toTgz2($this, $other, $compress);
+            if (PEAR::isError($tgzfile)) {
+                return $tgzfile;
+            }
+
+            $dest_package = basename($tgzfile);
+            $pkgdir       = dirname($pkgfile);
+
+            // TAR the Package -------------------------------------------
+            $this->log(1, "Package $dest_package done");
+            if (file_exists("$pkgdir/CVS/Root")) {
+                $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion());
+                $cvstag = "RELEASE_$cvsversion";
+                $this->log(1, 'Tag the released code with "pear cvstag ' .
+                    $main->getPackageFile() . '"');
+                $this->log(1, "(or set the CVS tag $cvstag by hand)");
+            } elseif (file_exists("$pkgdir/.svn")) {
+                $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion());
+                $svntag = $pf->getName() . "-$svnversion";
+                $this->log(1, 'Tag the released code with "pear svntag ' .
+                    $main->getPackageFile() . '"');
+                $this->log(1, "(or set the SVN tag $svntag by hand)");
+            }
+        } else { // this branch is executed for single packagefile packaging
+            $gen = &$pf->getDefaultGenerator();
+            $tgzfile = $gen->toTgz($this, $compress);
+            if (PEAR::isError($tgzfile)) {
+                $this->log(0, $tgzfile->getMessage());
+                return $this->raiseError("Cannot package, errors in package");
+            }
+
+            $dest_package = basename($tgzfile);
+            $pkgdir       = dirname($pkgfile);
+
+            // TAR the Package -------------------------------------------
+            $this->log(1, "Package $dest_package done");
+            if (file_exists("$pkgdir/CVS/Root")) {
+                $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion());
+                $cvstag = "RELEASE_$cvsversion";
+                $this->log(1, "Tag the released code with `pear cvstag $pkgfile'");
+                $this->log(1, "(or set the CVS tag $cvstag by hand)");
+            } elseif (file_exists("$pkgdir/.svn")) {
+                $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion());
+                $svntag = $pf->getName() . "-$svnversion";
+                $this->log(1, "Tag the released code with `pear svntag $pkgfile'");
+                $this->log(1, "(or set the SVN tag $svntag by hand)");
+            }
+        }
+
+        return $dest_package;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/REST.php b/WEB-INF/lib/pear/PEAR/REST.php
new file mode 100644 (file)
index 0000000..34a804f
--- /dev/null
@@ -0,0 +1,483 @@
+<?php
+/**
+ * PEAR_REST
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: REST.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * For downloading xml files
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/XMLParser.php';
+
+/**
+ * Intelligently retrieve data, following hyperlinks if necessary, and re-directing
+ * as well
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_REST
+{
+    var $config;
+    var $_options;
+
+    function PEAR_REST(&$config, $options = array())
+    {
+        $this->config   = &$config;
+        $this->_options = $options;
+    }
+
+    /**
+     * Retrieve REST data, but always retrieve the local cache if it is available.
+     *
+     * This is useful for elements that should never change, such as information on a particular
+     * release
+     * @param string full URL to this resource
+     * @param array|false contents of the accept-encoding header
+     * @param boolean     if true, xml will be returned as a string, otherwise, xml will be
+     *                    parsed using PEAR_XMLParser
+     * @return string|array
+     */
+    function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false)
+    {
+        $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+            md5($url) . 'rest.cachefile';
+
+        if (file_exists($cachefile)) {
+            return unserialize(implode('', file($cachefile)));
+        }
+
+        return $this->retrieveData($url, $accept, $forcestring, $channel);
+    }
+
+    /**
+     * Retrieve a remote REST resource
+     * @param string full URL to this resource
+     * @param array|false contents of the accept-encoding header
+     * @param boolean     if true, xml will be returned as a string, otherwise, xml will be
+     *                    parsed using PEAR_XMLParser
+     * @return string|array
+     */
+    function retrieveData($url, $accept = false, $forcestring = false, $channel = false)
+    {
+        $cacheId = $this->getCacheId($url);
+        if ($ret = $this->useLocalCache($url, $cacheId)) {
+            return $ret;
+        }
+
+        $file = $trieddownload = false;
+        if (!isset($this->_options['offline'])) {
+            $trieddownload = true;
+            $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel);
+        }
+
+        if (PEAR::isError($file)) {
+            if ($file->getCode() !== -9276) {
+                return $file;
+            }
+
+            $trieddownload = false;
+            $file = false; // use local copy if available on socket connect error
+        }
+
+        if (!$file) {
+            $ret = $this->getCache($url);
+            if (!PEAR::isError($ret) && $trieddownload) {
+                // reset the age of the cache if the server says it was unmodified
+                $result = $this->saveCache($url, $ret, null, true, $cacheId);
+                if (PEAR::isError($result)) {
+                    return PEAR::raiseError($result->getMessage());
+                }
+            }
+
+            return $ret;
+        }
+
+        if (is_array($file)) {
+            $headers      = $file[2];
+            $lastmodified = $file[1];
+            $content      = $file[0];
+        } else {
+            $headers      = array();
+            $lastmodified = false;
+            $content      = $file;
+        }
+
+        if ($forcestring) {
+            $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
+            if (PEAR::isError($result)) {
+                return PEAR::raiseError($result->getMessage());
+            }
+
+            return $content;
+        }
+
+        if (isset($headers['content-type'])) {
+            switch ($headers['content-type']) {
+                case 'text/xml' :
+                case 'application/xml' :
+                case 'text/plain' :
+                    if ($headers['content-type'] === 'text/plain') {
+                        $check = substr($content, 0, 5);
+                        if ($check !== '<?xml') {
+                            break;
+                        }
+                    }
+
+                    $parser = new PEAR_XMLParser;
+                    PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+                    $err = $parser->parse($content);
+                    PEAR::popErrorHandling();
+                    if (PEAR::isError($err)) {
+                        return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' .
+                            $err->getMessage());
+                    }
+                    $content = $parser->getData();
+                case 'text/html' :
+                default :
+                    // use it as a string
+            }
+        } else {
+            // assume XML
+            $parser = new PEAR_XMLParser;
+            $parser->parse($content);
+            $content = $parser->getData();
+        }
+
+        $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
+        if (PEAR::isError($result)) {
+            return PEAR::raiseError($result->getMessage());
+        }
+
+        return $content;
+    }
+
+    function useLocalCache($url, $cacheid = null)
+    {
+        if ($cacheid === null) {
+            $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+                md5($url) . 'rest.cacheid';
+            if (!file_exists($cacheidfile)) {
+                return false;
+            }
+
+            $cacheid = unserialize(implode('', file($cacheidfile)));
+        }
+
+        $cachettl = $this->config->get('cache_ttl');
+        // If cache is newer than $cachettl seconds, we use the cache!
+        if (time() - $cacheid['age'] < $cachettl) {
+            return $this->getCache($url);
+        }
+
+        return false;
+    }
+
+    function getCacheId($url)
+    {
+        $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+            md5($url) . 'rest.cacheid';
+
+        if (!file_exists($cacheidfile)) {
+            return false;
+        }
+
+        $ret = unserialize(implode('', file($cacheidfile)));
+        return $ret;
+    }
+
+    function getCache($url)
+    {
+        $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+            md5($url) . 'rest.cachefile';
+
+        if (!file_exists($cachefile)) {
+            return PEAR::raiseError('No cached content available for "' . $url . '"');
+        }
+
+        return unserialize(implode('', file($cachefile)));
+    }
+
+    /**
+     * @param string full URL to REST resource
+     * @param string original contents of the REST resource
+     * @param array  HTTP Last-Modified and ETag headers
+     * @param bool   if true, then the cache id file should be regenerated to
+     *               trigger a new time-to-live value
+     */
+    function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null)
+    {
+        $cache_dir   = $this->config->get('cache_dir');
+        $d           = $cache_dir . DIRECTORY_SEPARATOR . md5($url);
+        $cacheidfile = $d . 'rest.cacheid';
+        $cachefile   = $d . 'rest.cachefile';
+
+        if (!is_dir($cache_dir)) {
+            if (System::mkdir(array('-p', $cache_dir)) === false) {
+              return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed.");
+            }
+        }
+
+        if ($cacheid === null && $nochange) {
+            $cacheid = unserialize(implode('', file($cacheidfile)));
+        }
+
+        $idData = serialize(array(
+            'age'        => time(),
+            'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified),
+        ));
+
+        $result = $this->saveCacheFile($cacheidfile, $idData);
+        if (PEAR::isError($result)) {
+            return $result;
+        } elseif ($nochange) {
+            return true;
+        }
+
+        $result = $this->saveCacheFile($cachefile, serialize($contents));
+        if (PEAR::isError($result)) {
+            if (file_exists($cacheidfile)) {
+              @unlink($cacheidfile);
+            }
+
+            return $result;
+        }
+
+        return true;
+    }
+
+    function saveCacheFile($file, $contents)
+    {
+        $len = strlen($contents);
+
+        $cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode
+        if ($cachefile_fp !== false) { // create file
+            if (fwrite($cachefile_fp, $contents, $len) < $len) {
+                fclose($cachefile_fp);
+                return PEAR::raiseError("Could not write $file.");
+            }
+        } else { // update file
+            $cachefile_lstat = lstat($file);
+            $cachefile_fp = @fopen($file, 'wb');
+            if (!$cachefile_fp) {
+                return PEAR::raiseError("Could not open $file for writing.");
+            }
+
+            $cachefile_fstat = fstat($cachefile_fp);
+            if (
+              $cachefile_lstat['mode'] == $cachefile_fstat['mode'] &&
+              $cachefile_lstat['ino']  == $cachefile_fstat['ino'] &&
+              $cachefile_lstat['dev']  == $cachefile_fstat['dev'] &&
+              $cachefile_fstat['nlink'] === 1
+            ) {
+                if (fwrite($cachefile_fp, $contents, $len) < $len) {
+                    fclose($cachefile_fp);
+                    return PEAR::raiseError("Could not write $file.");
+                }
+            } else {
+                fclose($cachefile_fp);
+                $link = function_exists('readlink') ? readlink($file) : $file;
+                return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack');
+            }
+        }
+
+        fclose($cachefile_fp);
+        return true;
+    }
+
+    /**
+     * Efficiently Download a file through HTTP.  Returns downloaded file as a string in-memory
+     * This is best used for small files
+     *
+     * If an HTTP proxy has been configured (http_proxy PEAR_Config
+     * setting), the proxy will be used.
+     *
+     * @param string  $url       the URL to download
+     * @param string  $save_dir  directory to save file in
+     * @param false|string|array $lastmodified header values to check against for caching
+     *                           use false to return the header values from this download
+     * @param false|array $accept Accept headers to send
+     * @return string|array  Returns the contents of the downloaded file or a PEAR
+     *                       error on failure.  If the error is caused by
+     *                       socket-related errors, the error object will
+     *                       have the fsockopen error code available through
+     *                       getCode().  If caching is requested, then return the header
+     *                       values.
+     *
+     * @access public
+     */
+    function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false)
+    {
+        static $redirect = 0;
+        // always reset , so we are clean case of error
+        $wasredirect = $redirect;
+        $redirect = 0;
+
+        $info = parse_url($url);
+        if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
+            return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
+        }
+
+        if (!isset($info['host'])) {
+            return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
+        }
+
+        $host   = isset($info['host']) ? $info['host'] : null;
+        $port   = isset($info['port']) ? $info['port'] : null;
+        $path   = isset($info['path']) ? $info['path'] : null;
+        $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
+
+        $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
+        if ($this->config->get('http_proxy')&&
+              $proxy = parse_url($this->config->get('http_proxy'))
+        ) {
+            $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
+            if ($schema === 'https') {
+                $proxy_host = 'ssl://' . $proxy_host;
+            }
+
+            $proxy_port   = isset($proxy['port']) ? $proxy['port'] : 8080;
+            $proxy_user   = isset($proxy['user']) ? urldecode($proxy['user']) : null;
+            $proxy_pass   = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
+            $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http';
+        }
+
+        if (empty($port)) {
+            $port = (isset($info['scheme']) && $info['scheme'] == 'https')  ? 443 : 80;
+        }
+
+        if (isset($proxy['host'])) {
+            $request = "GET $url HTTP/1.1\r\n";
+        } else {
+            $request = "GET $path HTTP/1.1\r\n";
+        }
+
+        $request .= "Host: $host\r\n";
+        $ifmodifiedsince = '';
+        if (is_array($lastmodified)) {
+            if (isset($lastmodified['Last-Modified'])) {
+                $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
+            }
+
+            if (isset($lastmodified['ETag'])) {
+                $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
+            }
+        } else {
+            $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
+        }
+
+        $request .= $ifmodifiedsince .
+            "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n";
+
+        $username = $this->config->get('username', null, $channel);
+        $password = $this->config->get('password', null, $channel);
+
+        if ($username && $password) {
+            $tmp = base64_encode("$username:$password");
+            $request .= "Authorization: Basic $tmp\r\n";
+        }
+
+        if ($proxy_host != '' && $proxy_user != '') {
+            $request .= 'Proxy-Authorization: Basic ' .
+                base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
+        }
+
+        if ($accept) {
+            $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
+        }
+
+        $request .= "Accept-Encoding:\r\n";
+        $request .= "Connection: close\r\n";
+        $request .= "\r\n";
+
+        if ($proxy_host != '') {
+            $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15);
+            if (!$fp) {
+                return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276);
+            }
+        } else {
+            if ($schema === 'https') {
+                $host = 'ssl://' . $host;
+            }
+
+            $fp = @fsockopen($host, $port, $errno, $errstr);
+            if (!$fp) {
+                return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
+            }
+        }
+
+        fwrite($fp, $request);
+
+        $headers = array();
+        $reply   = 0;
+        while ($line = trim(fgets($fp, 1024))) {
+            if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
+                $headers[strtolower($matches[1])] = trim($matches[2]);
+            } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
+                $reply = (int)$matches[1];
+                if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
+                    return false;
+                }
+
+                if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
+                    return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)");
+                }
+            }
+        }
+
+        if ($reply != 200) {
+            if (!isset($headers['location'])) {
+                return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)");
+            }
+
+            if ($wasredirect > 4) {
+                return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)");
+            }
+
+            $redirect = $wasredirect + 1;
+            return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel);
+        }
+
+        $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
+
+        $data = '';
+        while ($chunk = @fread($fp, 8192)) {
+            $data .= $chunk;
+        }
+        fclose($fp);
+
+        if ($lastmodified === false || $lastmodified) {
+            if (isset($headers['etag'])) {
+                $lastmodified = array('ETag' => $headers['etag']);
+            }
+
+            if (isset($headers['last-modified'])) {
+                if (is_array($lastmodified)) {
+                    $lastmodified['Last-Modified'] = $headers['last-modified'];
+                } else {
+                    $lastmodified = $headers['last-modified'];
+                }
+            }
+
+            return array($data, $lastmodified, $headers);
+        }
+
+        return $data;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/REST/10.php b/WEB-INF/lib/pear/PEAR/REST/10.php
new file mode 100644 (file)
index 0000000..6ded7ae
--- /dev/null
@@ -0,0 +1,871 @@
+<?php
+/**
+ * PEAR_REST_10
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: 10.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a12
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+
+/**
+ * Implement REST 1.0
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a12
+ */
+class PEAR_REST_10
+{
+    /**
+     * @var PEAR_REST
+     */
+    var $_rest;
+    function PEAR_REST_10($config, $options = array())
+    {
+        $this->_rest = &new PEAR_REST($config, $options);
+    }
+
+    /**
+     * Retrieve information about a remote package to be downloaded from a REST server
+     *
+     * @param string $base The uri to prepend to all REST calls
+     * @param array $packageinfo an array of format:
+     * <pre>
+     *  array(
+     *   'package' => 'packagename',
+     *   'channel' => 'channelname',
+     *  ['state' => 'alpha' (or valid state),]
+     *  -or-
+     *  ['version' => '1.whatever']
+     * </pre>
+     * @param string $prefstate Current preferred_state config variable value
+     * @param bool $installed the installed version of this package to compare against
+     * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
+     */
+    function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
+    {
+        $states = $this->betterStates($prefstate, true);
+        if (!$states) {
+            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+        }
+
+        $channel  = $packageinfo['channel'];
+        $package  = $packageinfo['package'];
+        $state    = isset($packageinfo['state'])   ? $packageinfo['state']   : null;
+        $version  = isset($packageinfo['version']) ? $packageinfo['version'] : null;
+        $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
+
+        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+        if (PEAR::isError($info)) {
+            return PEAR::raiseError('No releases available for package "' .
+                $channel . '/' . $package . '"');
+        }
+
+        if (!isset($info['r'])) {
+            return false;
+        }
+
+        $release = $found = false;
+        if (!is_array($info['r']) || !isset($info['r'][0])) {
+            $info['r'] = array($info['r']);
+        }
+
+        foreach ($info['r'] as $release) {
+            if (!isset($this->_rest->_options['force']) && ($installed &&
+                  version_compare($release['v'], $installed, '<'))) {
+                continue;
+            }
+
+            if (isset($state)) {
+                // try our preferred state first
+                if ($release['s'] == $state) {
+                    $found = true;
+                    break;
+                }
+                // see if there is something newer and more stable
+                // bug #7221
+                if (in_array($release['s'], $this->betterStates($state), true)) {
+                    $found = true;
+                    break;
+                }
+            } elseif (isset($version)) {
+                if ($release['v'] == $version) {
+                    $found = true;
+                    break;
+                }
+            } else {
+                if (in_array($release['s'], $states)) {
+                    $found = true;
+                    break;
+                }
+            }
+        }
+
+        return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
+    }
+
+    function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
+                               $prefstate = 'stable', $installed = false, $channel = false)
+    {
+        $states = $this->betterStates($prefstate, true);
+        if (!$states) {
+            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+        }
+
+        $channel  = $dependency['channel'];
+        $package  = $dependency['name'];
+        $state    = isset($dependency['state'])   ? $dependency['state']   : null;
+        $version  = isset($dependency['version']) ? $dependency['version'] : null;
+        $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
+
+        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+        if (PEAR::isError($info)) {
+            return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
+                . '" dependency "' . $channel . '/' . $package . '" has no releases');
+        }
+
+        if (!is_array($info) || !isset($info['r'])) {
+            return false;
+        }
+
+        $exclude = array();
+        $min = $max = $recommended = false;
+        if ($xsdversion == '1.0') {
+            switch ($dependency['rel']) {
+                case 'ge' :
+                    $min = $dependency['version'];
+                break;
+                case 'gt' :
+                    $min = $dependency['version'];
+                    $exclude = array($dependency['version']);
+                break;
+                case 'eq' :
+                    $recommended = $dependency['version'];
+                break;
+                case 'lt' :
+                    $max = $dependency['version'];
+                    $exclude = array($dependency['version']);
+                break;
+                case 'le' :
+                    $max = $dependency['version'];
+                break;
+                case 'ne' :
+                    $exclude = array($dependency['version']);
+                break;
+            }
+        } else {
+            $min = isset($dependency['min']) ? $dependency['min'] : false;
+            $max = isset($dependency['max']) ? $dependency['max'] : false;
+            $recommended = isset($dependency['recommended']) ?
+                $dependency['recommended'] : false;
+            if (isset($dependency['exclude'])) {
+                if (!isset($dependency['exclude'][0])) {
+                    $exclude = array($dependency['exclude']);
+                }
+            }
+        }
+        $release = $found = false;
+        if (!is_array($info['r']) || !isset($info['r'][0])) {
+            $info['r'] = array($info['r']);
+        }
+        foreach ($info['r'] as $release) {
+            if (!isset($this->_rest->_options['force']) && ($installed &&
+                  version_compare($release['v'], $installed, '<'))) {
+                continue;
+            }
+            if (in_array($release['v'], $exclude)) { // skip excluded versions
+                continue;
+            }
+            // allow newer releases to say "I'm OK with the dependent package"
+            if ($xsdversion == '2.0' && isset($release['co'])) {
+                if (!is_array($release['co']) || !isset($release['co'][0])) {
+                    $release['co'] = array($release['co']);
+                }
+                foreach ($release['co'] as $entry) {
+                    if (isset($entry['x']) && !is_array($entry['x'])) {
+                        $entry['x'] = array($entry['x']);
+                    } elseif (!isset($entry['x'])) {
+                        $entry['x'] = array();
+                    }
+                    if ($entry['c'] == $deppackage['channel'] &&
+                          strtolower($entry['p']) == strtolower($deppackage['package']) &&
+                          version_compare($deppackage['version'], $entry['min'], '>=') &&
+                          version_compare($deppackage['version'], $entry['max'], '<=') &&
+                          !in_array($release['v'], $entry['x'])) {
+                        $recommended = $release['v'];
+                        break;
+                    }
+                }
+            }
+            if ($recommended) {
+                if ($release['v'] != $recommended) { // if we want a specific
+                    // version, then skip all others
+                    continue;
+                } else {
+                    if (!in_array($release['s'], $states)) {
+                        // the stability is too low, but we must return the
+                        // recommended version if possible
+                        return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
+                    }
+                }
+            }
+            if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
+                continue;
+            }
+            if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
+                continue;
+            }
+            if ($installed && version_compare($release['v'], $installed, '<')) {
+                continue;
+            }
+            if (in_array($release['s'], $states)) { // if in the preferred state...
+                $found = true; // ... then use it
+                break;
+            }
+        }
+        return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
+    }
+
+    /**
+     * Take raw data and return the array needed for processing a download URL
+     *
+     * @param string $base REST base uri
+     * @param string $package Package name
+     * @param array $release an array of format array('v' => version, 's' => state)
+     *                       describing the release to download
+     * @param array $info list of all releases as defined by allreleases.xml
+     * @param bool|null $found determines whether the release was found or this is the next
+     *                    best alternative.  If null, then versions were skipped because
+     *                    of PHP dependency
+     * @return array|PEAR_Error
+     * @access private
+     */
+    function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false)
+    {
+        if (!$found) {
+            $release = $info['r'][0];
+        }
+
+        $packageLower = strtolower($package);
+        $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' .
+            'info.xml', false, false, $channel);
+        if (PEAR::isError($pinfo)) {
+            return PEAR::raiseError('Package "' . $package .
+                '" does not have REST info xml available');
+        }
+
+        $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
+            $release['v'] . '.xml', false, false, $channel);
+        if (PEAR::isError($releaseinfo)) {
+            return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
+                '" does not have REST xml available');
+        }
+
+        $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
+            'deps.' . $release['v'] . '.txt', false, true, $channel);
+        if (PEAR::isError($packagexml)) {
+            return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
+                '" does not have REST dependency information available');
+        }
+
+        $packagexml = unserialize($packagexml);
+        if (!$packagexml) {
+            $packagexml = array();
+        }
+
+        $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower .
+            '/allreleases.xml', false, false, $channel);
+        if (PEAR::isError($allinfo)) {
+            return $allinfo;
+        }
+
+        if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) {
+            $allinfo['r'] = array($allinfo['r']);
+        }
+
+        $compatible = false;
+        foreach ($allinfo['r'] as $release) {
+            if ($release['v'] != $releaseinfo['v']) {
+                continue;
+            }
+
+            if (!isset($release['co'])) {
+                break;
+            }
+
+            $compatible = array();
+            if (!is_array($release['co']) || !isset($release['co'][0])) {
+                $release['co'] = array($release['co']);
+            }
+
+            foreach ($release['co'] as $entry) {
+                $comp = array();
+                $comp['name']    = $entry['p'];
+                $comp['channel'] = $entry['c'];
+                $comp['min']     = $entry['min'];
+                $comp['max']     = $entry['max'];
+                if (isset($entry['x']) && !is_array($entry['x'])) {
+                    $comp['exclude'] = $entry['x'];
+                }
+
+                $compatible[] = $comp;
+            }
+
+            if (count($compatible) == 1) {
+                $compatible = $compatible[0];
+            }
+
+            break;
+        }
+
+        $deprecated = false;
+        if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
+            if (is_array($pinfo['dp'])) {
+                $deprecated = array('channel' => (string) $pinfo['dc'],
+                                    'package' => trim($pinfo['dp']['_content']));
+            } else {
+                $deprecated = array('channel' => (string) $pinfo['dc'],
+                                    'package' => trim($pinfo['dp']));
+            }
+        }
+
+        $return = array(
+            'version'    => $releaseinfo['v'],
+            'info'       => $packagexml,
+            'package'    => $releaseinfo['p']['_content'],
+            'stability'  => $releaseinfo['st'],
+            'compatible' => $compatible,
+            'deprecated' => $deprecated,
+        );
+
+        if ($found) {
+            $return['url'] = $releaseinfo['g'];
+            return $return;
+        }
+
+        $return['php'] = $phpversion;
+        return $return;
+    }
+
+    function listPackages($base, $channel = false)
+    {
+        $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+
+        if (!is_array($packagelist) || !isset($packagelist['p'])) {
+            return array();
+        }
+
+        if (!is_array($packagelist['p'])) {
+            $packagelist['p'] = array($packagelist['p']);
+        }
+
+        return $packagelist['p'];
+    }
+
+    /**
+     * List all categories of a REST server
+     *
+     * @param string $base base URL of the server
+     * @return array of categorynames
+     */
+    function listCategories($base, $channel = false)
+    {
+        $categories = array();
+
+        // c/categories.xml does not exist;
+        // check for every package its category manually
+        // This is SLOOOWWWW : ///
+        $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+
+        if (!is_array($packagelist) || !isset($packagelist['p'])) {
+            $ret = array();
+            return $ret;
+        }
+
+        if (!is_array($packagelist['p'])) {
+            $packagelist['p'] = array($packagelist['p']);
+        }
+
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        foreach ($packagelist['p'] as $package) {
+                $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+                if (PEAR::isError($inf)) {
+                    PEAR::popErrorHandling();
+                    return $inf;
+                }
+                $cat = $inf['ca']['_content'];
+                if (!isset($categories[$cat])) {
+                    $categories[$cat] = $inf['ca'];
+                }
+        }
+
+        return array_values($categories);
+    }
+
+    /**
+     * List a category of a REST server
+     *
+     * @param string $base base URL of the server
+     * @param string $category name of the category
+     * @param boolean $info also download full package info
+     * @return array of packagenames
+     */
+    function listCategory($base, $category, $info = false, $channel = false)
+    {
+        // gives '404 Not Found' error when category doesn't exist
+        $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+
+        if (!is_array($packagelist) || !isset($packagelist['p'])) {
+            return array();
+        }
+
+        if (!is_array($packagelist['p']) ||
+            !isset($packagelist['p'][0])) { // only 1 pkg
+            $packagelist = array($packagelist['p']);
+        } else {
+            $packagelist = $packagelist['p'];
+        }
+
+        if ($info == true) {
+            // get individual package info
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            foreach ($packagelist as $i => $packageitem) {
+                $url = sprintf('%s'.'r/%s/latest.txt',
+                        $base,
+                        strtolower($packageitem['_content']));
+                $version = $this->_rest->retrieveData($url, false, false, $channel);
+                if (PEAR::isError($version)) {
+                    break; // skipit
+                }
+                $url = sprintf('%s'.'r/%s/%s.xml',
+                        $base,
+                        strtolower($packageitem['_content']),
+                        $version);
+                $info = $this->_rest->retrieveData($url, false, false, $channel);
+                if (PEAR::isError($info)) {
+                    break; // skipit
+                }
+                $packagelist[$i]['info'] = $info;
+            }
+            PEAR::popErrorHandling();
+        }
+
+        return $packagelist;
+    }
+
+
+    function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false)
+    {
+        $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+        if ($this->_rest->config->get('verbose') > 0) {
+            $ui = &PEAR_Frontend::singleton();
+            $ui->log('Retrieving data...0%', true);
+        }
+        $ret = array();
+        if (!is_array($packagelist) || !isset($packagelist['p'])) {
+            return $ret;
+        }
+        if (!is_array($packagelist['p'])) {
+            $packagelist['p'] = array($packagelist['p']);
+        }
+
+        // only search-packagename = quicksearch !
+        if ($searchpackage && (!$searchsummary || empty($searchpackage))) {
+            $newpackagelist = array();
+            foreach ($packagelist['p'] as $package) {
+                if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) {
+                    $newpackagelist[] = $package;
+                }
+            }
+            $packagelist['p'] = $newpackagelist;
+        }
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $next = .1;
+        foreach ($packagelist['p'] as $progress => $package) {
+            if ($this->_rest->config->get('verbose') > 0) {
+                if ($progress / count($packagelist['p']) >= $next) {
+                    if ($next == .5) {
+                        $ui->log('50%', false);
+                    } else {
+                        $ui->log('.', false);
+                    }
+                    $next += .1;
+                }
+            }
+
+            if ($basic) { // remote-list command
+                if ($dostable) {
+                    $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+                        '/stable.txt', false, false, $channel);
+                } else {
+                    $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+                        '/latest.txt', false, false, $channel);
+                }
+                if (PEAR::isError($latest)) {
+                    $latest = false;
+                }
+                $info = array('stable' => $latest);
+            } else { // list-all command
+                $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+                if (PEAR::isError($inf)) {
+                    PEAR::popErrorHandling();
+                    return $inf;
+                }
+                if ($searchpackage) {
+                    $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false);
+                    if (!$found && !(isset($searchsummary) && !empty($searchsummary)
+                        && (stristr($inf['s'], $searchsummary) !== false
+                            || stristr($inf['d'], $searchsummary) !== false)))
+                    {
+                        continue;
+                    };
+                }
+                $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+                    '/allreleases.xml', false, false, $channel);
+                if (PEAR::isError($releases)) {
+                    continue;
+                }
+                if (!isset($releases['r'][0])) {
+                    $releases['r'] = array($releases['r']);
+                }
+                unset($latest);
+                unset($unstable);
+                unset($stable);
+                unset($state);
+                foreach ($releases['r'] as $release) {
+                    if (!isset($latest)) {
+                        if ($dostable && $release['s'] == 'stable') {
+                            $latest = $release['v'];
+                            $state = 'stable';
+                        }
+                        if (!$dostable) {
+                            $latest = $release['v'];
+                            $state = $release['s'];
+                        }
+                    }
+                    if (!isset($stable) && $release['s'] == 'stable') {
+                        $stable = $release['v'];
+                        if (!isset($unstable)) {
+                            $unstable = $stable;
+                        }
+                    }
+                    if (!isset($unstable) && $release['s'] != 'stable') {
+                        $latest = $unstable = $release['v'];
+                        $state = $release['s'];
+                    }
+                    if (isset($latest) && !isset($state)) {
+                        $state = $release['s'];
+                    }
+                    if (isset($latest) && isset($stable) && isset($unstable)) {
+                        break;
+                    }
+                }
+                $deps = array();
+                if (!isset($unstable)) {
+                    $unstable = false;
+                    $state = 'stable';
+                    if (isset($stable)) {
+                        $latest = $unstable = $stable;
+                    }
+                } else {
+                    $latest = $unstable;
+                }
+                if (!isset($latest)) {
+                    $latest = false;
+                }
+                if ($latest) {
+                    $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
+                        $latest . '.txt', false, false, $channel);
+                    if (!PEAR::isError($d)) {
+                        $d = unserialize($d);
+                        if ($d) {
+                            if (isset($d['required'])) {
+                                if (!class_exists('PEAR_PackageFile_v2')) {
+                                    require_once 'PEAR/PackageFile/v2.php';
+                                }
+                                if (!isset($pf)) {
+                                    $pf = new PEAR_PackageFile_v2;
+                                }
+                                $pf->setDeps($d);
+                                $tdeps = $pf->getDeps();
+                            } else {
+                                $tdeps = $d;
+                            }
+                            foreach ($tdeps as $dep) {
+                                if ($dep['type'] !== 'pkg') {
+                                    continue;
+                                }
+                                $deps[] = $dep;
+                            }
+                        }
+                    }
+                }
+                if (!isset($stable)) {
+                    $stable = '-n/a-';
+                }
+                if (!$searchpackage) {
+                    $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' =>
+                        $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
+                        'unstable' => $unstable, 'state' => $state);
+                } else {
+                    $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' =>
+                        $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
+                        'unstable' => $unstable, 'state' => $state);
+                }
+            }
+            $ret[$package] = $info;
+        }
+        PEAR::popErrorHandling();
+        return $ret;
+    }
+
+    function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg)
+    {
+        $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+
+        $ret = array();
+        if (!is_array($packagelist) || !isset($packagelist['p'])) {
+            return $ret;
+        }
+
+        if (!is_array($packagelist['p'])) {
+            $packagelist['p'] = array($packagelist['p']);
+        }
+
+        foreach ($packagelist['p'] as $package) {
+            if (!isset($installed[strtolower($package)])) {
+                continue;
+            }
+
+            $inst_version = $reg->packageInfo($package, 'version', $channel);
+            $inst_state   = $reg->packageInfo($package, 'release_state', $channel);
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+                '/allreleases.xml', false, false, $channel);
+            PEAR::popErrorHandling();
+            if (PEAR::isError($info)) {
+                continue; // no remote releases
+            }
+
+            if (!isset($info['r'])) {
+                continue;
+            }
+
+            $release = $found = false;
+            if (!is_array($info['r']) || !isset($info['r'][0])) {
+                $info['r'] = array($info['r']);
+            }
+
+            // $info['r'] is sorted by version number
+            usort($info['r'], array($this, '_sortReleasesByVersionNumber'));
+            foreach ($info['r'] as $release) {
+                if ($inst_version && version_compare($release['v'], $inst_version, '<=')) {
+                    // not newer than the one installed
+                    break;
+                }
+
+                // new version > installed version
+                if (!$pref_state) {
+                    // every state is a good state
+                    $found = true;
+                    break;
+                } else {
+                    $new_state = $release['s'];
+                    // if new state >= installed state: go
+                    if (in_array($new_state, $this->betterStates($inst_state, true))) {
+                        $found = true;
+                        break;
+                    } else {
+                        // only allow to lower the state of package,
+                        // if new state >= preferred state: go
+                        if (in_array($new_state, $this->betterStates($pref_state, true))) {
+                            $found = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (!$found) {
+                continue;
+            }
+
+            $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' .
+                $release['v'] . '.xml', false, false, $channel);
+            if (PEAR::isError($relinfo)) {
+                return $relinfo;
+            }
+
+            $ret[$package] = array(
+                'version'  => $release['v'],
+                'state'    => $release['s'],
+                'filesize' => $relinfo['f'],
+            );
+        }
+
+        return $ret;
+    }
+
+    function packageInfo($base, $package, $channel = false)
+    {
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+        if (PEAR::isError($pinfo)) {
+            PEAR::popErrorHandling();
+            return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' .
+                $pinfo->getMessage());
+        }
+
+        $releases = array();
+        $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+            '/allreleases.xml', false, false, $channel);
+        if (!PEAR::isError($allreleases)) {
+            if (!class_exists('PEAR_PackageFile_v2')) {
+                require_once 'PEAR/PackageFile/v2.php';
+            }
+
+            if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) {
+                $allreleases['r'] = array($allreleases['r']);
+            }
+
+            $pf = new PEAR_PackageFile_v2;
+            foreach ($allreleases['r'] as $release) {
+                $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
+                    $release['v'] . '.txt', false, false, $channel);
+                if (PEAR::isError($ds)) {
+                    continue;
+                }
+
+                if (!isset($latest)) {
+                    $latest = $release['v'];
+                }
+
+                $pf->setDeps(unserialize($ds));
+                $ds = $pf->getDeps();
+                $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package)
+                    . '/' . $release['v'] . '.xml', false, false, $channel);
+
+                if (PEAR::isError($info)) {
+                    continue;
+                }
+
+                $releases[$release['v']] = array(
+                    'doneby' => $info['m'],
+                    'license' => $info['l'],
+                    'summary' => $info['s'],
+                    'description' => $info['d'],
+                    'releasedate' => $info['da'],
+                    'releasenotes' => $info['n'],
+                    'state' => $release['s'],
+                    'deps' => $ds ? $ds : array(),
+                );
+            }
+        } else {
+            $latest = '';
+        }
+
+        PEAR::popErrorHandling();
+        if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
+            if (is_array($pinfo['dp'])) {
+                $deprecated = array('channel' => (string) $pinfo['dc'],
+                                    'package' => trim($pinfo['dp']['_content']));
+            } else {
+                $deprecated = array('channel' => (string) $pinfo['dc'],
+                                    'package' => trim($pinfo['dp']));
+            }
+        } else {
+            $deprecated = false;
+        }
+
+        if (!isset($latest)) {
+            $latest = '';
+        }
+
+        return array(
+            'name' => $pinfo['n'],
+            'channel' => $pinfo['c'],
+            'category' => $pinfo['ca']['_content'],
+            'stable' => $latest,
+            'license' => $pinfo['l'],
+            'summary' => $pinfo['s'],
+            'description' => $pinfo['d'],
+            'releases' => $releases,
+            'deprecated' => $deprecated,
+            );
+    }
+
+    /**
+     * Return an array containing all of the states that are more stable than
+     * or equal to the passed in state
+     *
+     * @param string Release state
+     * @param boolean Determines whether to include $state in the list
+     * @return false|array False if $state is not a valid release state
+     */
+    function betterStates($state, $include = false)
+    {
+        static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+        $i = array_search($state, $states);
+        if ($i === false) {
+            return false;
+        }
+
+        if ($include) {
+            $i--;
+        }
+
+        return array_slice($states, $i + 1);
+    }
+
+    /**
+     * Sort releases by version number
+     *
+     * @access private
+     */
+    function _sortReleasesByVersionNumber($a, $b)
+    {
+        if (version_compare($a['v'], $b['v'], '=')) {
+            return 0;
+        }
+
+        if (version_compare($a['v'], $b['v'], '>')) {
+            return -1;
+        }
+
+        if (version_compare($a['v'], $b['v'], '<')) {
+            return 1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/REST/11.php b/WEB-INF/lib/pear/PEAR/REST/11.php
new file mode 100644 (file)
index 0000000..831cfcc
--- /dev/null
@@ -0,0 +1,341 @@
+<?php
+/**
+ * PEAR_REST_11 - implement faster list-all/remote-list command
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: 11.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.3
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+
+/**
+ * Implement REST 1.1
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.3
+ */
+class PEAR_REST_11
+{
+    /**
+     * @var PEAR_REST
+     */
+    var $_rest;
+
+    function PEAR_REST_11($config, $options = array())
+    {
+        $this->_rest = &new PEAR_REST($config, $options);
+    }
+
+    function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false)
+    {
+        $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel);
+        if (PEAR::isError($categorylist)) {
+            return $categorylist;
+        }
+
+        $ret = array();
+        if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) {
+            $categorylist['c'] = array($categorylist['c']);
+        }
+
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+
+        foreach ($categorylist['c'] as $progress => $category) {
+            $category = $category['_content'];
+            $packagesinfo = $this->_rest->retrieveData($base .
+                'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel);
+
+            if (PEAR::isError($packagesinfo)) {
+                continue;
+            }
+
+            if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) {
+                continue;
+            }
+
+            if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) {
+                $packagesinfo['pi'] = array($packagesinfo['pi']);
+            }
+
+            foreach ($packagesinfo['pi'] as $packageinfo) {
+                if (empty($packageinfo)) {
+                    continue;
+                }
+
+                $info     = $packageinfo['p'];
+                $package  = $info['n'];
+                $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false;
+                unset($latest);
+                unset($unstable);
+                unset($stable);
+                unset($state);
+
+                if ($releases) {
+                    if (!isset($releases['r'][0])) {
+                        $releases['r'] = array($releases['r']);
+                    }
+
+                    foreach ($releases['r'] as $release) {
+                        if (!isset($latest)) {
+                            if ($dostable && $release['s'] == 'stable') {
+                                $latest = $release['v'];
+                                $state = 'stable';
+                            }
+                            if (!$dostable) {
+                                $latest = $release['v'];
+                                $state = $release['s'];
+                            }
+                        }
+
+                        if (!isset($stable) && $release['s'] == 'stable') {
+                            $stable = $release['v'];
+                            if (!isset($unstable)) {
+                                $unstable = $stable;
+                            }
+                        }
+
+                        if (!isset($unstable) && $release['s'] != 'stable') {
+                            $unstable = $release['v'];
+                            $state = $release['s'];
+                        }
+
+                        if (isset($latest) && !isset($state)) {
+                            $state = $release['s'];
+                        }
+
+                        if (isset($latest) && isset($stable) && isset($unstable)) {
+                            break;
+                        }
+                    }
+                }
+
+                if ($basic) { // remote-list command
+                    if (!isset($latest)) {
+                        $latest = false;
+                    }
+
+                    if ($dostable) {
+                        // $state is not set if there are no releases
+                        if (isset($state) && $state == 'stable') {
+                            $ret[$package] = array('stable' => $latest);
+                        } else {
+                            $ret[$package] = array('stable' => '-n/a-');
+                        }
+                    } else {
+                        $ret[$package] = array('stable' => $latest);
+                    }
+
+                    continue;
+                }
+
+                // list-all command
+                if (!isset($unstable)) {
+                    $unstable = false;
+                    $state = 'stable';
+                    if (isset($stable)) {
+                        $latest = $unstable = $stable;
+                    }
+                } else {
+                    $latest = $unstable;
+                }
+
+                if (!isset($latest)) {
+                    $latest = false;
+                }
+
+                $deps = array();
+                if ($latest && isset($packageinfo['deps'])) {
+                    if (!is_array($packageinfo['deps']) ||
+                          !isset($packageinfo['deps'][0])
+                    ) {
+                        $packageinfo['deps'] = array($packageinfo['deps']);
+                    }
+
+                    $d = false;
+                    foreach ($packageinfo['deps'] as $dep) {
+                        if ($dep['v'] == $latest) {
+                            $d = unserialize($dep['d']);
+                        }
+                    }
+
+                    if ($d) {
+                        if (isset($d['required'])) {
+                            if (!class_exists('PEAR_PackageFile_v2')) {
+                                require_once 'PEAR/PackageFile/v2.php';
+                            }
+
+                            if (!isset($pf)) {
+                                $pf = new PEAR_PackageFile_v2;
+                            }
+
+                            $pf->setDeps($d);
+                            $tdeps = $pf->getDeps();
+                        } else {
+                            $tdeps = $d;
+                        }
+
+                        foreach ($tdeps as $dep) {
+                            if ($dep['type'] !== 'pkg') {
+                                continue;
+                            }
+
+                            $deps[] = $dep;
+                        }
+                    }
+                }
+
+                $info = array(
+                    'stable'      => $latest,
+                    'summary'     => $info['s'],
+                    'description' => $info['d'],
+                    'deps'        => $deps,
+                    'category'    => $info['ca']['_content'],
+                    'unstable'    => $unstable,
+                    'state'       => $state
+                );
+                $ret[$package] = $info;
+            }
+        }
+
+        PEAR::popErrorHandling();
+        return $ret;
+    }
+
+    /**
+     * List all categories of a REST server
+     *
+     * @param string $base base URL of the server
+     * @return array of categorynames
+     */
+    function listCategories($base, $channel = false)
+    {
+        $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel);
+        if (PEAR::isError($categorylist)) {
+            return $categorylist;
+        }
+
+        if (!is_array($categorylist) || !isset($categorylist['c'])) {
+            return array();
+        }
+
+        if (isset($categorylist['c']['_content'])) {
+            // only 1 category
+            $categorylist['c'] = array($categorylist['c']);
+        }
+
+        return $categorylist['c'];
+    }
+
+    /**
+     * List packages in a category of a REST server
+     *
+     * @param string $base base URL of the server
+     * @param string $category name of the category
+     * @param boolean $info also download full package info
+     * @return array of packagenames
+     */
+    function listCategory($base, $category, $info = false, $channel = false)
+    {
+        if ($info == false) {
+            $url = '%s'.'c/%s/packages.xml';
+        } else {
+            $url = '%s'.'c/%s/packagesinfo.xml';
+        }
+        $url = sprintf($url,
+                    $base,
+                    urlencode($category));
+
+        // gives '404 Not Found' error when category doesn't exist
+        $packagelist = $this->_rest->retrieveData($url, false, false, $channel);
+        if (PEAR::isError($packagelist)) {
+            return $packagelist;
+        }
+        if (!is_array($packagelist)) {
+            return array();
+        }
+
+        if ($info == false) {
+            if (!isset($packagelist['p'])) {
+                return array();
+            }
+            if (!is_array($packagelist['p']) ||
+                !isset($packagelist['p'][0])) { // only 1 pkg
+                $packagelist = array($packagelist['p']);
+            } else {
+                $packagelist = $packagelist['p'];
+            }
+            return $packagelist;
+        }
+
+        // info == true
+        if (!isset($packagelist['pi'])) {
+            return array();
+        }
+
+        if (!is_array($packagelist['pi']) ||
+            !isset($packagelist['pi'][0])) { // only 1 pkg
+            $packagelist_pre = array($packagelist['pi']);
+        } else {
+            $packagelist_pre = $packagelist['pi'];
+        }
+
+        $packagelist = array();
+        foreach ($packagelist_pre as $i => $item) {
+            // compatibility with r/<latest.txt>.xml
+            if (isset($item['a']['r'][0])) {
+                // multiple releases
+                $item['p']['v'] = $item['a']['r'][0]['v'];
+                $item['p']['st'] = $item['a']['r'][0]['s'];
+            } elseif (isset($item['a'])) {
+                // first and only release
+                $item['p']['v'] = $item['a']['r']['v'];
+                $item['p']['st'] = $item['a']['r']['s'];
+            }
+
+            $packagelist[$i] = array('attribs' => $item['p']['r'],
+                                     '_content' => $item['p']['n'],
+                                     'info' => $item['p']);
+        }
+
+        return $packagelist;
+    }
+
+    /**
+     * Return an array containing all of the states that are more stable than
+     * or equal to the passed in state
+     *
+     * @param string Release state
+     * @param boolean Determines whether to include $state in the list
+     * @return false|array False if $state is not a valid release state
+     */
+    function betterStates($state, $include = false)
+    {
+        static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+        $i = array_search($state, $states);
+        if ($i === false) {
+            return false;
+        }
+        if ($include) {
+            $i--;
+        }
+        return array_slice($states, $i + 1);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/REST/13.php b/WEB-INF/lib/pear/PEAR/REST/13.php
new file mode 100644 (file)
index 0000000..722ae0d
--- /dev/null
@@ -0,0 +1,299 @@
+<?php
+/**
+ * PEAR_REST_13
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: 13.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a12
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+require_once 'PEAR/REST/10.php';
+
+/**
+ * Implement REST 1.3
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a12
+ */
+class PEAR_REST_13 extends PEAR_REST_10
+{
+    /**
+     * Retrieve information about a remote package to be downloaded from a REST server
+     *
+     * This is smart enough to resolve the minimum PHP version dependency prior to download
+     * @param string $base The uri to prepend to all REST calls
+     * @param array $packageinfo an array of format:
+     * <pre>
+     *  array(
+     *   'package' => 'packagename',
+     *   'channel' => 'channelname',
+     *  ['state' => 'alpha' (or valid state),]
+     *  -or-
+     *  ['version' => '1.whatever']
+     * </pre>
+     * @param string $prefstate Current preferred_state config variable value
+     * @param bool $installed the installed version of this package to compare against
+     * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
+     */
+    function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
+    {
+        $states = $this->betterStates($prefstate, true);
+        if (!$states) {
+            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+        }
+
+        $channel  = $packageinfo['channel'];
+        $package  = $packageinfo['package'];
+        $state    = isset($packageinfo['state'])   ? $packageinfo['state']   : null;
+        $version  = isset($packageinfo['version']) ? $packageinfo['version'] : null;
+        $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml';
+
+        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+        if (PEAR::isError($info)) {
+            return PEAR::raiseError('No releases available for package "' .
+                $channel . '/' . $package . '"');
+        }
+
+        if (!isset($info['r'])) {
+            return false;
+        }
+
+        $release = $found = false;
+        if (!is_array($info['r']) || !isset($info['r'][0])) {
+            $info['r'] = array($info['r']);
+        }
+
+        $skippedphp = false;
+        foreach ($info['r'] as $release) {
+            if (!isset($this->_rest->_options['force']) && ($installed &&
+                  version_compare($release['v'], $installed, '<'))) {
+                continue;
+            }
+
+            if (isset($state)) {
+                // try our preferred state first
+                if ($release['s'] == $state) {
+                    if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
+                        // skip releases that require a PHP version newer than our PHP version
+                        $skippedphp = $release;
+                        continue;
+                    }
+                    $found = true;
+                    break;
+                }
+
+                // see if there is something newer and more stable
+                // bug #7221
+                if (in_array($release['s'], $this->betterStates($state), true)) {
+                    if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
+                        // skip releases that require a PHP version newer than our PHP version
+                        $skippedphp = $release;
+                        continue;
+                    }
+                    $found = true;
+                    break;
+                }
+            } elseif (isset($version)) {
+                if ($release['v'] == $version) {
+                    if (!isset($this->_rest->_options['force']) &&
+                          !isset($version) &&
+                          version_compare($release['m'], phpversion(), '>')) {
+                        // skip releases that require a PHP version newer than our PHP version
+                        $skippedphp = $release;
+                        continue;
+                    }
+                    $found = true;
+                    break;
+                }
+            } else {
+                if (in_array($release['s'], $states)) {
+                    if (version_compare($release['m'], phpversion(), '>')) {
+                        // skip releases that require a PHP version newer than our PHP version
+                        $skippedphp = $release;
+                        continue;
+                    }
+                    $found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!$found && $skippedphp) {
+            $found = null;
+        }
+
+        return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
+    }
+
+    function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
+                               $prefstate = 'stable', $installed = false, $channel = false)
+    {
+        $states = $this->betterStates($prefstate, true);
+        if (!$states) {
+            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+        }
+
+        $channel  = $dependency['channel'];
+        $package  = $dependency['name'];
+        $state    = isset($dependency['state'])   ? $dependency['state']   : null;
+        $version  = isset($dependency['version']) ? $dependency['version'] : null;
+        $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml';
+
+        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+        if (PEAR::isError($info)) {
+            return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
+                . '" dependency "' . $channel . '/' . $package . '" has no releases');
+        }
+
+        if (!is_array($info) || !isset($info['r'])) {
+            return false;
+        }
+
+        $exclude = array();
+        $min = $max = $recommended = false;
+        if ($xsdversion == '1.0') {
+            $pinfo['package'] = $dependency['name'];
+            $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this
+            switch ($dependency['rel']) {
+                case 'ge' :
+                    $min = $dependency['version'];
+                break;
+                case 'gt' :
+                    $min = $dependency['version'];
+                    $exclude = array($dependency['version']);
+                break;
+                case 'eq' :
+                    $recommended = $dependency['version'];
+                break;
+                case 'lt' :
+                    $max = $dependency['version'];
+                    $exclude = array($dependency['version']);
+                break;
+                case 'le' :
+                    $max = $dependency['version'];
+                break;
+                case 'ne' :
+                    $exclude = array($dependency['version']);
+                break;
+            }
+        } else {
+            $pinfo['package'] = $dependency['name'];
+            $min = isset($dependency['min']) ? $dependency['min'] : false;
+            $max = isset($dependency['max']) ? $dependency['max'] : false;
+            $recommended = isset($dependency['recommended']) ?
+                $dependency['recommended'] : false;
+            if (isset($dependency['exclude'])) {
+                if (!isset($dependency['exclude'][0])) {
+                    $exclude = array($dependency['exclude']);
+                }
+            }
+        }
+
+        $skippedphp = $found = $release = false;
+        if (!is_array($info['r']) || !isset($info['r'][0])) {
+            $info['r'] = array($info['r']);
+        }
+
+        foreach ($info['r'] as $release) {
+            if (!isset($this->_rest->_options['force']) && ($installed &&
+                  version_compare($release['v'], $installed, '<'))) {
+                continue;
+            }
+
+            if (in_array($release['v'], $exclude)) { // skip excluded versions
+                continue;
+            }
+
+            // allow newer releases to say "I'm OK with the dependent package"
+            if ($xsdversion == '2.0' && isset($release['co'])) {
+                if (!is_array($release['co']) || !isset($release['co'][0])) {
+                    $release['co'] = array($release['co']);
+                }
+
+                foreach ($release['co'] as $entry) {
+                    if (isset($entry['x']) && !is_array($entry['x'])) {
+                        $entry['x'] = array($entry['x']);
+                    } elseif (!isset($entry['x'])) {
+                        $entry['x'] = array();
+                    }
+
+                    if ($entry['c'] == $deppackage['channel'] &&
+                          strtolower($entry['p']) == strtolower($deppackage['package']) &&
+                          version_compare($deppackage['version'], $entry['min'], '>=') &&
+                          version_compare($deppackage['version'], $entry['max'], '<=') &&
+                          !in_array($release['v'], $entry['x'])) {
+                        if (version_compare($release['m'], phpversion(), '>')) {
+                            // skip dependency releases that require a PHP version
+                            // newer than our PHP version
+                            $skippedphp = $release;
+                            continue;
+                        }
+
+                        $recommended = $release['v'];
+                        break;
+                    }
+                }
+            }
+
+            if ($recommended) {
+                if ($release['v'] != $recommended) { // if we want a specific
+                    // version, then skip all others
+                    continue;
+                }
+
+                if (!in_array($release['s'], $states)) {
+                    // the stability is too low, but we must return the
+                    // recommended version if possible
+                    return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
+                }
+            }
+
+            if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
+                continue;
+            }
+
+            if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
+                continue;
+            }
+
+            if ($installed && version_compare($release['v'], $installed, '<')) {
+                continue;
+            }
+
+            if (in_array($release['s'], $states)) { // if in the preferred state...
+                if (version_compare($release['m'], phpversion(), '>')) {
+                    // skip dependency releases that require a PHP version
+                    // newer than our PHP version
+                    $skippedphp = $release;
+                    continue;
+                }
+
+                $found = true; // ... then use it
+                break;
+            }
+        }
+
+        if (!$found && $skippedphp) {
+            $found = null;
+        }
+
+        return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Registry.php b/WEB-INF/lib/pear/PEAR/Registry.php
new file mode 100644 (file)
index 0000000..35e17db
--- /dev/null
@@ -0,0 +1,2395 @@
+<?php
+/**
+ * PEAR_Registry
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * for PEAR_Error
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/DependencyDB.php';
+
+define('PEAR_REGISTRY_ERROR_LOCK',         -2);
+define('PEAR_REGISTRY_ERROR_FORMAT',       -3);
+define('PEAR_REGISTRY_ERROR_FILE',         -4);
+define('PEAR_REGISTRY_ERROR_CONFLICT',     -5);
+define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6);
+
+/**
+ * Administration class used to maintain the installed package database.
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Registry extends PEAR
+{
+    /**
+     * File containing all channel information.
+     * @var string
+     */
+    var $channels = '';
+
+    /** Directory where registry files are stored.
+     * @var string
+     */
+    var $statedir = '';
+
+    /** File where the file map is stored
+     * @var string
+     */
+    var $filemap = '';
+
+    /** Directory where registry files for channels are stored.
+     * @var string
+     */
+    var $channelsdir = '';
+
+    /** Name of file used for locking the registry
+     * @var string
+     */
+    var $lockfile = '';
+
+    /** File descriptor used during locking
+     * @var resource
+     */
+    var $lock_fp = null;
+
+    /** Mode used during locking
+     * @var int
+     */
+    var $lock_mode = 0; // XXX UNUSED
+
+    /** Cache of package information.  Structure:
+     * array(
+     *   'package' => array('id' => ... ),
+     *   ... )
+     * @var array
+     */
+    var $pkginfo_cache = array();
+
+    /** Cache of file map.  Structure:
+     * array( '/path/to/file' => 'package', ... )
+     * @var array
+     */
+    var $filemap_cache = array();
+
+    /**
+     * @var false|PEAR_ChannelFile
+     */
+    var $_pearChannel;
+
+    /**
+     * @var false|PEAR_ChannelFile
+     */
+    var $_peclChannel;
+
+    /**
+     * @var false|PEAR_ChannelFile
+     */
+    var $_docChannel;
+
+    /**
+     * @var PEAR_DependencyDB
+     */
+    var $_dependencyDB;
+
+    /**
+     * @var PEAR_Config
+     */
+    var $_config;
+
+    /**
+     * PEAR_Registry constructor.
+     *
+     * @param string (optional) PEAR install directory (for .php files)
+     * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if
+     *        default values are not desired.  Only used the very first time a PEAR
+     *        repository is initialized
+     * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if
+     *        default values are not desired.  Only used the very first time a PEAR
+     *        repository is initialized
+     *
+     * @access public
+     */
+    function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false,
+                           $pecl_channel = false)
+    {
+        parent::PEAR();
+        $this->setInstallDir($pear_install_dir);
+        $this->_pearChannel = $pear_channel;
+        $this->_peclChannel = $pecl_channel;
+        $this->_config      = false;
+    }
+
+    function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR)
+    {
+        $ds = DIRECTORY_SEPARATOR;
+        $this->install_dir = $pear_install_dir;
+        $this->channelsdir = $pear_install_dir.$ds.'.channels';
+        $this->statedir    = $pear_install_dir.$ds.'.registry';
+        $this->filemap     = $pear_install_dir.$ds.'.filemap';
+        $this->lockfile    = $pear_install_dir.$ds.'.lock';
+    }
+
+    function hasWriteAccess()
+    {
+        if (!file_exists($this->install_dir)) {
+            $dir = $this->install_dir;
+            while ($dir && $dir != '.') {
+                $olddir = $dir;
+                $dir    = dirname($dir);
+                if ($dir != '.' && file_exists($dir)) {
+                    if (is_writeable($dir)) {
+                        return true;
+                    }
+
+                    return false;
+                }
+
+                if ($dir == $olddir) { // this can happen in safe mode
+                    return @is_writable($dir);
+                }
+            }
+
+            return false;
+        }
+
+        return is_writeable($this->install_dir);
+    }
+
+    function setConfig(&$config, $resetInstallDir = true)
+    {
+        $this->_config = &$config;
+        if ($resetInstallDir) {
+            $this->setInstallDir($config->get('php_dir'));
+        }
+    }
+
+    function _initializeChannelDirs()
+    {
+        static $running = false;
+        if (!$running) {
+            $running = true;
+            $ds = DIRECTORY_SEPARATOR;
+            if (!is_dir($this->channelsdir) ||
+                  !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . '__uri.reg')) {
+                if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+                    $pear_channel = $this->_pearChannel;
+                    if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) {
+                        if (!class_exists('PEAR_ChannelFile')) {
+                            require_once 'PEAR/ChannelFile.php';
+                        }
+
+                        $pear_channel = new PEAR_ChannelFile;
+                        $pear_channel->setAlias('pear');
+                        $pear_channel->setServer('pear.php.net');
+                        $pear_channel->setSummary('PHP Extension and Application Repository');
+                        $pear_channel->setDefaultPEARProtocols();
+                        $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/');
+                        $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/');
+                        $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/');
+                        //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/');
+                    } else {
+                        $pear_channel->setServer('pear.php.net');
+                        $pear_channel->setAlias('pear');
+                    }
+
+                    $pear_channel->validate();
+                    $this->_addChannel($pear_channel);
+                }
+
+                if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) {
+                    $pecl_channel = $this->_peclChannel;
+                    if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) {
+                        if (!class_exists('PEAR_ChannelFile')) {
+                            require_once 'PEAR/ChannelFile.php';
+                        }
+
+                        $pecl_channel = new PEAR_ChannelFile;
+                        $pecl_channel->setAlias('pecl');
+                        $pecl_channel->setServer('pecl.php.net');
+                        $pecl_channel->setSummary('PHP Extension Community Library');
+                        $pecl_channel->setDefaultPEARProtocols();
+                        $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/');
+                        $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/');
+                        $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0');
+                    } else {
+                        $pecl_channel->setServer('pecl.php.net');
+                        $pecl_channel->setAlias('pecl');
+                    }
+
+                    $pecl_channel->validate();
+                    $this->_addChannel($pecl_channel);
+                }
+
+                if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) {
+                    $doc_channel = $this->_docChannel;
+                    if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) {
+                        if (!class_exists('PEAR_ChannelFile')) {
+                            require_once 'PEAR/ChannelFile.php';
+                        }
+
+                        $doc_channel = new PEAR_ChannelFile;
+                        $doc_channel->setAlias('phpdocs');
+                        $doc_channel->setServer('doc.php.net');
+                        $doc_channel->setSummary('PHP Documentation Team');
+                        $doc_channel->setDefaultPEARProtocols();
+                        $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/');
+                        $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/');
+                        $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/');
+                    } else {
+                        $doc_channel->setServer('doc.php.net');
+                        $doc_channel->setAlias('doc');
+                    }
+
+                    $doc_channel->validate();
+                    $this->_addChannel($doc_channel);
+                }
+
+                if (!file_exists($this->channelsdir . $ds . '__uri.reg')) {
+                    if (!class_exists('PEAR_ChannelFile')) {
+                        require_once 'PEAR/ChannelFile.php';
+                    }
+
+                    $private = new PEAR_ChannelFile;
+                    $private->setName('__uri');
+                    $private->setDefaultPEARProtocols();
+                    $private->setBaseURL('REST1.0', '****');
+                    $private->setSummary('Pseudo-channel for static packages');
+                    $this->_addChannel($private);
+                }
+                $this->_rebuildFileMap();
+            }
+
+            $running = false;
+        }
+    }
+
+    function _initializeDirs()
+    {
+        $ds = DIRECTORY_SEPARATOR;
+        // XXX Compatibility code should be removed in the future
+        // rename all registry files if any to lowercase
+        if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) &&
+              $handle = opendir($this->statedir)) {
+            $dest = $this->statedir . $ds;
+            while (false !== ($file = readdir($handle))) {
+                if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) {
+                    rename($dest . $file, $dest . strtolower($file));
+                }
+            }
+            closedir($handle);
+        }
+
+        $this->_initializeChannelDirs();
+        if (!file_exists($this->filemap)) {
+            $this->_rebuildFileMap();
+        }
+        $this->_initializeDepDB();
+    }
+
+    function _initializeDepDB()
+    {
+        if (!isset($this->_dependencyDB)) {
+            static $initializing = false;
+            if (!$initializing) {
+                $initializing = true;
+                if (!$this->_config) { // never used?
+                    $file = OS_WINDOWS ? 'pear.ini' : '.pearrc';
+                    $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR .
+                        $file);
+                    $this->_config->setRegistry($this);
+                    $this->_config->set('php_dir', $this->install_dir);
+                }
+
+                $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config);
+                if (PEAR::isError($this->_dependencyDB)) {
+                    // attempt to recover by removing the dep db
+                    if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') .
+                        DIRECTORY_SEPARATOR . '.depdb')) {
+                        @unlink($this->_config->get('php_dir', null, 'pear.php.net') .
+                            DIRECTORY_SEPARATOR . '.depdb');
+                    }
+
+                    $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config);
+                    if (PEAR::isError($this->_dependencyDB)) {
+                        echo $this->_dependencyDB->getMessage();
+                        echo 'Unrecoverable error';
+                        exit(1);
+                    }
+                }
+
+                $initializing = false;
+            }
+        }
+    }
+
+    /**
+     * PEAR_Registry destructor.  Makes sure no locks are forgotten.
+     *
+     * @access private
+     */
+    function _PEAR_Registry()
+    {
+        parent::_PEAR();
+        if (is_resource($this->lock_fp)) {
+            $this->_unlock();
+        }
+    }
+
+    /**
+     * Make sure the directory where we keep registry files exists.
+     *
+     * @return bool TRUE if directory exists, FALSE if it could not be
+     * created
+     *
+     * @access private
+     */
+    function _assertStateDir($channel = false)
+    {
+        if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+            return $this->_assertChannelStateDir($channel);
+        }
+
+        static $init = false;
+        if (!file_exists($this->statedir)) {
+            if (!$this->hasWriteAccess()) {
+                return false;
+            }
+
+            require_once 'System.php';
+            if (!System::mkdir(array('-p', $this->statedir))) {
+                return $this->raiseError("could not create directory '{$this->statedir}'");
+            }
+            $init = true;
+        } elseif (!is_dir($this->statedir)) {
+            return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' .
+                'it already exists and is not a directory');
+        }
+
+        $ds = DIRECTORY_SEPARATOR;
+        if (!file_exists($this->channelsdir)) {
+            if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') ||
+                  !file_exists($this->channelsdir . $ds . '__uri.reg')) {
+                $init = true;
+            }
+        } elseif (!is_dir($this->channelsdir)) {
+            return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' .
+                'it already exists and is not a directory');
+        }
+
+        if ($init) {
+            static $running = false;
+            if (!$running) {
+                $running = true;
+                $this->_initializeDirs();
+                $running = false;
+                $init = false;
+            }
+        } else {
+            $this->_initializeDepDB();
+        }
+
+        return true;
+    }
+
+    /**
+     * Make sure the directory where we keep registry files exists for a non-standard channel.
+     *
+     * @param string channel name
+     * @return bool TRUE if directory exists, FALSE if it could not be
+     * created
+     *
+     * @access private
+     */
+    function _assertChannelStateDir($channel)
+    {
+        $ds = DIRECTORY_SEPARATOR;
+        if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+            if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+                $this->_initializeChannelDirs();
+            }
+            return $this->_assertStateDir($channel);
+        }
+
+        $channelDir = $this->_channelDirectoryName($channel);
+        if (!is_dir($this->channelsdir) ||
+              !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+            $this->_initializeChannelDirs();
+        }
+
+        if (!file_exists($channelDir)) {
+            if (!$this->hasWriteAccess()) {
+                return false;
+            }
+
+            require_once 'System.php';
+            if (!System::mkdir(array('-p', $channelDir))) {
+                return $this->raiseError("could not create directory '" . $channelDir .
+                    "'");
+            }
+        } elseif (!is_dir($channelDir)) {
+            return $this->raiseError("could not create directory '" . $channelDir .
+                "', already exists and is not a directory");
+        }
+
+        return true;
+    }
+
+    /**
+     * Make sure the directory where we keep registry files for channels exists
+     *
+     * @return bool TRUE if directory exists, FALSE if it could not be
+     * created
+     *
+     * @access private
+     */
+    function _assertChannelDir()
+    {
+        if (!file_exists($this->channelsdir)) {
+            if (!$this->hasWriteAccess()) {
+                return false;
+            }
+
+            require_once 'System.php';
+            if (!System::mkdir(array('-p', $this->channelsdir))) {
+                return $this->raiseError("could not create directory '{$this->channelsdir}'");
+            }
+        } elseif (!is_dir($this->channelsdir)) {
+            return $this->raiseError("could not create directory '{$this->channelsdir}" .
+                "', it already exists and is not a directory");
+        }
+
+        if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) {
+            if (!$this->hasWriteAccess()) {
+                return false;
+            }
+
+            require_once 'System.php';
+            if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) {
+                return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'");
+            }
+        } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) {
+            return $this->raiseError("could not create directory '{$this->channelsdir}" .
+                "/.alias', it already exists and is not a directory");
+        }
+
+        return true;
+    }
+
+    /**
+     * Get the name of the file where data for a given package is stored.
+     *
+     * @param string channel name, or false if this is a PEAR package
+     * @param string package name
+     *
+     * @return string registry file name
+     *
+     * @access public
+     */
+    function _packageFileName($package, $channel = false)
+    {
+        if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+            return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR .
+                strtolower($package) . '.reg';
+        }
+
+        return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg';
+    }
+
+    /**
+     * Get the name of the file where data for a given channel is stored.
+     * @param string channel name
+     * @return string registry file name
+     */
+    function _channelFileName($channel, $noaliases = false)
+    {
+        if (!$noaliases) {
+            if (file_exists($this->_getChannelAliasFileName($channel))) {
+                $channel = implode('', file($this->_getChannelAliasFileName($channel)));
+            }
+        }
+        return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_',
+            strtolower($channel)) . '.reg';
+    }
+
+    /**
+     * @param string
+     * @return string
+     */
+    function _getChannelAliasFileName($alias)
+    {
+        return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' .
+              DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt';
+    }
+
+    /**
+     * Get the name of a channel from its alias
+     */
+    function _getChannelFromAlias($channel)
+    {
+        if (!$this->_channelExists($channel)) {
+            if ($channel == 'pear.php.net') {
+                return 'pear.php.net';
+            }
+
+            if ($channel == 'pecl.php.net') {
+                return 'pecl.php.net';
+            }
+
+            if ($channel == 'doc.php.net') {
+                return 'doc.php.net';
+            }
+
+            if ($channel == '__uri') {
+                return '__uri';
+            }
+
+            return false;
+        }
+
+        $channel = strtolower($channel);
+        if (file_exists($this->_getChannelAliasFileName($channel))) {
+            // translate an alias to an actual channel
+            return implode('', file($this->_getChannelAliasFileName($channel)));
+        }
+
+        return $channel;
+    }
+
+    /**
+     * Get the alias of a channel from its alias or its name
+     */
+    function _getAlias($channel)
+    {
+        if (!$this->_channelExists($channel)) {
+            if ($channel == 'pear.php.net') {
+                return 'pear';
+            }
+
+            if ($channel == 'pecl.php.net') {
+                return 'pecl';
+            }
+
+            if ($channel == 'doc.php.net') {
+                return 'phpdocs';
+            }
+
+            return false;
+        }
+
+        $channel = $this->_getChannel($channel);
+        if (PEAR::isError($channel)) {
+            return $channel;
+        }
+
+        return $channel->getAlias();
+    }
+
+    /**
+     * Get the name of the file where data for a given package is stored.
+     *
+     * @param string channel name, or false if this is a PEAR package
+     * @param string package name
+     *
+     * @return string registry file name
+     *
+     * @access public
+     */
+    function _channelDirectoryName($channel)
+    {
+        if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+            return $this->statedir;
+        }
+
+        $ch = $this->_getChannelFromAlias($channel);
+        if (!$ch) {
+            $ch = $channel;
+        }
+
+        return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' .
+            str_replace('/', '_', $ch));
+    }
+
+    function _openPackageFile($package, $mode, $channel = false)
+    {
+        if (!$this->_assertStateDir($channel)) {
+            return null;
+        }
+
+        if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) {
+            return null;
+        }
+
+        $file = $this->_packageFileName($package, $channel);
+        if (!file_exists($file) && $mode == 'r' || $mode == 'rb') {
+            return null;
+        }
+
+        $fp = @fopen($file, $mode);
+        if (!$fp) {
+            return null;
+        }
+
+        return $fp;
+    }
+
+    function _closePackageFile($fp)
+    {
+        fclose($fp);
+    }
+
+    function _openChannelFile($channel, $mode)
+    {
+        if (!$this->_assertChannelDir()) {
+            return null;
+        }
+
+        if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) {
+            return null;
+        }
+
+        $file = $this->_channelFileName($channel);
+        if (!file_exists($file) && $mode == 'r' || $mode == 'rb') {
+            return null;
+        }
+
+        $fp = @fopen($file, $mode);
+        if (!$fp) {
+            return null;
+        }
+
+        return $fp;
+    }
+
+    function _closeChannelFile($fp)
+    {
+        fclose($fp);
+    }
+
+    function _rebuildFileMap()
+    {
+        if (!class_exists('PEAR_Installer_Role')) {
+            require_once 'PEAR/Installer/Role.php';
+        }
+
+        $channels = $this->_listAllPackages();
+        $files = array();
+        foreach ($channels as $channel => $packages) {
+            foreach ($packages as $package) {
+                $version = $this->_packageInfo($package, 'version', $channel);
+                $filelist = $this->_packageInfo($package, 'filelist', $channel);
+                if (!is_array($filelist)) {
+                    continue;
+                }
+
+                foreach ($filelist as $name => $attrs) {
+                    if (isset($attrs['attribs'])) {
+                        $attrs = $attrs['attribs'];
+                    }
+
+                    // it is possible for conflicting packages in different channels to
+                    // conflict with data files/doc files
+                    if ($name == 'dirtree') {
+                        continue;
+                    }
+
+                    if (isset($attrs['role']) && !in_array($attrs['role'],
+                          PEAR_Installer_Role::getInstallableRoles())) {
+                        // these are not installed
+                        continue;
+                    }
+
+                    if (isset($attrs['role']) && !in_array($attrs['role'],
+                          PEAR_Installer_Role::getBaseinstallRoles())) {
+                        $attrs['baseinstalldir'] = $package;
+                    }
+
+                    if (isset($attrs['baseinstalldir'])) {
+                        $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name;
+                    } else {
+                        $file = $name;
+                    }
+
+                    $file = preg_replace(',^/+,', '', $file);
+                    if ($channel != 'pear.php.net') {
+                        if (!isset($files[$attrs['role']])) {
+                            $files[$attrs['role']] = array();
+                        }
+                        $files[$attrs['role']][$file] = array(strtolower($channel),
+                            strtolower($package));
+                    } else {
+                        if (!isset($files[$attrs['role']])) {
+                            $files[$attrs['role']] = array();
+                        }
+                        $files[$attrs['role']][$file] = strtolower($package);
+                    }
+                }
+            }
+        }
+
+
+        $this->_assertStateDir();
+        if (!$this->hasWriteAccess()) {
+            return false;
+        }
+
+        $fp = @fopen($this->filemap, 'wb');
+        if (!$fp) {
+            return false;
+        }
+
+        $this->filemap_cache = $files;
+        fwrite($fp, serialize($files));
+        fclose($fp);
+        return true;
+    }
+
+    function _readFileMap()
+    {
+        if (!file_exists($this->filemap)) {
+            return array();
+        }
+
+        $fp = @fopen($this->filemap, 'r');
+        if (!$fp) {
+            return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg);
+        }
+
+        clearstatcache();
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        $fsize = filesize($this->filemap);
+        fclose($fp);
+        $data = file_get_contents($this->filemap);
+        set_magic_quotes_runtime($rt);
+        $tmp = unserialize($data);
+        if (!$tmp && $fsize > 7) {
+            return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data);
+        }
+
+        $this->filemap_cache = $tmp;
+        return true;
+    }
+
+    /**
+     * Lock the registry.
+     *
+     * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN.
+     *                See flock manual for more information.
+     *
+     * @return bool TRUE on success, FALSE if locking failed, or a
+     *              PEAR error if some other error occurs (such as the
+     *              lock file not being writable).
+     *
+     * @access private
+     */
+    function _lock($mode = LOCK_EX)
+    {
+        if (stristr(php_uname(), 'Windows 9')) {
+            return true;
+        }
+
+        if ($mode != LOCK_UN && is_resource($this->lock_fp)) {
+            // XXX does not check type of lock (LOCK_SH/LOCK_EX)
+            return true;
+        }
+
+        if (!$this->_assertStateDir()) {
+            if ($mode == LOCK_EX) {
+                return $this->raiseError('Registry directory is not writeable by the current user');
+            }
+
+            return true;
+        }
+
+        $open_mode = 'w';
+        // XXX People reported problems with LOCK_SH and 'w'
+        if ($mode === LOCK_SH || $mode === LOCK_UN) {
+            if (!file_exists($this->lockfile)) {
+                touch($this->lockfile);
+            }
+            $open_mode = 'r';
+        }
+
+        if (!is_resource($this->lock_fp)) {
+            $this->lock_fp = @fopen($this->lockfile, $open_mode);
+        }
+
+        if (!is_resource($this->lock_fp)) {
+            $this->lock_fp = null;
+            return $this->raiseError("could not create lock file" .
+                                     (isset($php_errormsg) ? ": " . $php_errormsg : ""));
+        }
+
+        if (!(int)flock($this->lock_fp, $mode)) {
+            switch ($mode) {
+                case LOCK_SH: $str = 'shared';    break;
+                case LOCK_EX: $str = 'exclusive'; break;
+                case LOCK_UN: $str = 'unlock';    break;
+                default:      $str = 'unknown';   break;
+            }
+
+            //is resource at this point, close it on error.
+            fclose($this->lock_fp);
+            $this->lock_fp = null;
+            return $this->raiseError("could not acquire $str lock ($this->lockfile)",
+                                     PEAR_REGISTRY_ERROR_LOCK);
+        }
+
+        return true;
+    }
+
+    function _unlock()
+    {
+        $ret = $this->_lock(LOCK_UN);
+        if (is_resource($this->lock_fp)) {
+            fclose($this->lock_fp);
+        }
+
+        $this->lock_fp = null;
+        return $ret;
+    }
+
+    function _packageExists($package, $channel = false)
+    {
+        return file_exists($this->_packageFileName($package, $channel));
+    }
+
+    /**
+     * Determine whether a channel exists in the registry
+     *
+     * @param string Channel name
+     * @param bool if true, then aliases will be ignored
+     * @return boolean
+     */
+    function _channelExists($channel, $noaliases = false)
+    {
+        $a = file_exists($this->_channelFileName($channel, $noaliases));
+        if (!$a && $channel == 'pear.php.net') {
+            return true;
+        }
+
+        if (!$a && $channel == 'pecl.php.net') {
+            return true;
+        }
+
+        if (!$a && $channel == 'doc.php.net') {
+            return true;
+        }
+
+        return $a;
+    }
+
+    /**
+     * Determine whether a mirror exists within the deafult channel in the registry
+     *
+     * @param string Channel name
+     * @param string Mirror name
+     *
+     * @return boolean
+     */
+    function _mirrorExists($channel, $mirror)
+    {
+        $data = $this->_channelInfo($channel);
+        if (!isset($data['servers']['mirror'])) {
+            return false;
+        }
+
+        foreach ($data['servers']['mirror'] as $m) {
+            if ($m['attribs']['host'] == $mirror) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param PEAR_ChannelFile Channel object
+     * @param donotuse
+     * @param string Last-Modified HTTP tag from remote request
+     * @return boolean|PEAR_Error True on creation, false if it already exists
+     */
+    function _addChannel($channel, $update = false, $lastmodified = false)
+    {
+        if (!is_a($channel, 'PEAR_ChannelFile')) {
+            return false;
+        }
+
+        if (!$channel->validate()) {
+            return false;
+        }
+
+        if (file_exists($this->_channelFileName($channel->getName()))) {
+            if (!$update) {
+                return false;
+            }
+
+            $checker = $this->_getChannel($channel->getName());
+            if (PEAR::isError($checker)) {
+                return $checker;
+            }
+
+            if ($channel->getAlias() != $checker->getAlias()) {
+                if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) {
+                    @unlink($this->_getChannelAliasFileName($checker->getAlias()));
+                }
+            }
+        } else {
+            if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) {
+                return false;
+            }
+        }
+
+        $ret = $this->_assertChannelDir();
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        $ret = $this->_assertChannelStateDir($channel->getName());
+        if (PEAR::isError($ret)) {
+            return $ret;
+        }
+
+        if ($channel->getAlias() != $channel->getName()) {
+            if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) &&
+                  $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) {
+                $channel->setAlias($channel->getName());
+            }
+
+            if (!$this->hasWriteAccess()) {
+                return false;
+            }
+
+            $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w');
+            if (!$fp) {
+                return false;
+            }
+
+            fwrite($fp, $channel->getName());
+            fclose($fp);
+        }
+
+        if (!$this->hasWriteAccess()) {
+            return false;
+        }
+
+        $fp = @fopen($this->_channelFileName($channel->getName()), 'wb');
+        if (!$fp) {
+            return false;
+        }
+
+        $info = $channel->toArray();
+        if ($lastmodified) {
+            $info['_lastmodified'] = $lastmodified;
+        } else {
+            $info['_lastmodified'] = date('r');
+        }
+
+        fwrite($fp, serialize($info));
+        fclose($fp);
+        return true;
+    }
+
+    /**
+     * Deletion fails if there are any packages installed from the channel
+     * @param string|PEAR_ChannelFile channel name
+     * @return boolean|PEAR_Error True on deletion, false if it doesn't exist
+     */
+    function _deleteChannel($channel)
+    {
+        if (!is_string($channel)) {
+            if (!is_a($channel, 'PEAR_ChannelFile')) {
+                return false;
+            }
+
+            if (!$channel->validate()) {
+                return false;
+            }
+            $channel = $channel->getName();
+        }
+
+        if ($this->_getChannelFromAlias($channel) == '__uri') {
+            return false;
+        }
+
+        if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') {
+            return false;
+        }
+
+        if ($this->_getChannelFromAlias($channel) == 'doc.php.net') {
+            return false;
+        }
+
+        if (!$this->_channelExists($channel)) {
+            return false;
+        }
+
+        if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+            return false;
+        }
+
+        $channel = $this->_getChannelFromAlias($channel);
+        if ($channel == 'pear.php.net') {
+            return false;
+        }
+
+        $test = $this->_listChannelPackages($channel);
+        if (count($test)) {
+            return false;
+        }
+
+        $test = @rmdir($this->_channelDirectoryName($channel));
+        if (!$test) {
+            return false;
+        }
+
+        $file = $this->_getChannelAliasFileName($this->_getAlias($channel));
+        if (file_exists($file)) {
+            $test = @unlink($file);
+            if (!$test) {
+                return false;
+            }
+        }
+
+        $file = $this->_channelFileName($channel);
+        $ret = true;
+        if (file_exists($file)) {
+            $ret = @unlink($file);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Determine whether a channel exists in the registry
+     * @param string Channel Alias
+     * @return boolean
+     */
+    function _isChannelAlias($alias)
+    {
+        return file_exists($this->_getChannelAliasFileName($alias));
+    }
+
+    /**
+     * @param string|null
+     * @param string|null
+     * @param string|null
+     * @return array|null
+     * @access private
+     */
+    function _packageInfo($package = null, $key = null, $channel = 'pear.php.net')
+    {
+        if ($package === null) {
+            if ($channel === null) {
+                $channels = $this->_listChannels();
+                $ret = array();
+                foreach ($channels as $channel) {
+                    $channel = strtolower($channel);
+                    $ret[$channel] = array();
+                    $packages = $this->_listPackages($channel);
+                    foreach ($packages as $package) {
+                        $ret[$channel][] = $this->_packageInfo($package, null, $channel);
+                    }
+                }
+
+                return $ret;
+            }
+
+            $ps = $this->_listPackages($channel);
+            if (!count($ps)) {
+                return array();
+            }
+            return array_map(array(&$this, '_packageInfo'),
+                             $ps, array_fill(0, count($ps), null),
+                             array_fill(0, count($ps), $channel));
+        }
+
+        $fp = $this->_openPackageFile($package, 'r', $channel);
+        if ($fp === null) {
+            return null;
+        }
+
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        clearstatcache();
+        $this->_closePackageFile($fp);
+        $data = file_get_contents($this->_packageFileName($package, $channel));
+        set_magic_quotes_runtime($rt);
+        $data = unserialize($data);
+        if ($key === null) {
+            return $data;
+        }
+
+        // compatibility for package.xml version 2.0
+        if (isset($data['old'][$key])) {
+            return $data['old'][$key];
+        }
+
+        if (isset($data[$key])) {
+            return $data[$key];
+        }
+
+        return null;
+    }
+
+    /**
+     * @param string Channel name
+     * @param bool whether to strictly retrieve info of channels, not just aliases
+     * @return array|null
+     */
+    function _channelInfo($channel, $noaliases = false)
+    {
+        if (!$this->_channelExists($channel, $noaliases)) {
+            return null;
+        }
+
+        $fp = $this->_openChannelFile($channel, 'r');
+        if ($fp === null) {
+            return null;
+        }
+
+        $rt = get_magic_quotes_runtime();
+        set_magic_quotes_runtime(0);
+        clearstatcache();
+        $this->_closeChannelFile($fp);
+        $data = file_get_contents($this->_channelFileName($channel));
+        set_magic_quotes_runtime($rt);
+        $data = unserialize($data);
+        return $data;
+    }
+
+    function _listChannels()
+    {
+        $channellist = array();
+        if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) {
+            return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri');
+        }
+
+        $dp = opendir($this->channelsdir);
+        while ($ent = readdir($dp)) {
+            if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+                continue;
+            }
+
+            if ($ent == '__uri.reg') {
+                $channellist[] = '__uri';
+                continue;
+            }
+
+            $channellist[] = str_replace('_', '/', substr($ent, 0, -4));
+        }
+
+        closedir($dp);
+        if (!in_array('pear.php.net', $channellist)) {
+            $channellist[] = 'pear.php.net';
+        }
+
+        if (!in_array('pecl.php.net', $channellist)) {
+            $channellist[] = 'pecl.php.net';
+        }
+
+        if (!in_array('doc.php.net', $channellist)) {
+            $channellist[] = 'doc.php.net';
+        }
+
+
+        if (!in_array('__uri', $channellist)) {
+            $channellist[] = '__uri';
+        }
+
+        natsort($channellist);
+        return $channellist;
+    }
+
+    function _listPackages($channel = false)
+    {
+        if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+            return $this->_listChannelPackages($channel);
+        }
+
+        if (!file_exists($this->statedir) || !is_dir($this->statedir)) {
+            return array();
+        }
+
+        $pkglist = array();
+        $dp = opendir($this->statedir);
+        if (!$dp) {
+            return $pkglist;
+        }
+
+        while ($ent = readdir($dp)) {
+            if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+                continue;
+            }
+
+            $pkglist[] = substr($ent, 0, -4);
+        }
+        closedir($dp);
+        return $pkglist;
+    }
+
+    function _listChannelPackages($channel)
+    {
+        $pkglist = array();
+        if (!file_exists($this->_channelDirectoryName($channel)) ||
+              !is_dir($this->_channelDirectoryName($channel))) {
+            return array();
+        }
+
+        $dp = opendir($this->_channelDirectoryName($channel));
+        if (!$dp) {
+            return $pkglist;
+        }
+
+        while ($ent = readdir($dp)) {
+            if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+                continue;
+            }
+            $pkglist[] = substr($ent, 0, -4);
+        }
+
+        closedir($dp);
+        return $pkglist;
+    }
+
+    function _listAllPackages()
+    {
+        $ret = array();
+        foreach ($this->_listChannels() as $channel) {
+            $ret[$channel] = $this->_listPackages($channel);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Add an installed package to the registry
+     * @param string package name
+     * @param array package info (parsed by PEAR_Common::infoFrom*() methods)
+     * @return bool success of saving
+     * @access private
+     */
+    function _addPackage($package, $info)
+    {
+        if ($this->_packageExists($package)) {
+            return false;
+        }
+
+        $fp = $this->_openPackageFile($package, 'wb');
+        if ($fp === null) {
+            return false;
+        }
+
+        $info['_lastmodified'] = time();
+        fwrite($fp, serialize($info));
+        $this->_closePackageFile($fp);
+        if (isset($info['filelist'])) {
+            $this->_rebuildFileMap();
+        }
+
+        return true;
+    }
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @return bool
+     * @access private
+     */
+    function _addPackage2($info)
+    {
+        if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) {
+            return false;
+        }
+
+        if (!$info->validate()) {
+            if (class_exists('PEAR_Common')) {
+                $ui = PEAR_Frontend::singleton();
+                if ($ui) {
+                    foreach ($info->getValidationWarnings() as $err) {
+                        $ui->log($err['message'], true);
+                    }
+                }
+            }
+            return false;
+        }
+
+        $channel = $info->getChannel();
+        $package = $info->getPackage();
+        $save = $info;
+        if ($this->_packageExists($package, $channel)) {
+            return false;
+        }
+
+        if (!$this->_channelExists($channel, true)) {
+            return false;
+        }
+
+        $info = $info->toArray(true);
+        if (!$info) {
+            return false;
+        }
+
+        $fp = $this->_openPackageFile($package, 'wb', $channel);
+        if ($fp === null) {
+            return false;
+        }
+
+        $info['_lastmodified'] = time();
+        fwrite($fp, serialize($info));
+        $this->_closePackageFile($fp);
+        $this->_rebuildFileMap();
+        return true;
+    }
+
+    /**
+     * @param string Package name
+     * @param array parsed package.xml 1.0
+     * @param bool this parameter is only here for BC.  Don't use it.
+     * @access private
+     */
+    function _updatePackage($package, $info, $merge = true)
+    {
+        $oldinfo = $this->_packageInfo($package);
+        if (empty($oldinfo)) {
+            return false;
+        }
+
+        $fp = $this->_openPackageFile($package, 'w');
+        if ($fp === null) {
+            return false;
+        }
+
+        if (is_object($info)) {
+            $info = $info->toArray();
+        }
+        $info['_lastmodified'] = time();
+
+        $newinfo = $info;
+        if ($merge) {
+            $info = array_merge($oldinfo, $info);
+        } else {
+            $diff = $info;
+        }
+
+        fwrite($fp, serialize($info));
+        $this->_closePackageFile($fp);
+        if (isset($newinfo['filelist'])) {
+            $this->_rebuildFileMap();
+        }
+
+        return true;
+    }
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @return bool
+     * @access private
+     */
+    function _updatePackage2($info)
+    {
+        if (!$this->_packageExists($info->getPackage(), $info->getChannel())) {
+            return false;
+        }
+
+        $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel());
+        if ($fp === null) {
+            return false;
+        }
+
+        $save = $info;
+        $info = $save->getArray(true);
+        $info['_lastmodified'] = time();
+        fwrite($fp, serialize($info));
+        $this->_closePackageFile($fp);
+        $this->_rebuildFileMap();
+        return true;
+    }
+
+    /**
+     * @param string Package name
+     * @param string Channel name
+     * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null
+     * @access private
+     */
+    function &_getPackage($package, $channel = 'pear.php.net')
+    {
+        $info = $this->_packageInfo($package, null, $channel);
+        if ($info === null) {
+            return $info;
+        }
+
+        $a = $this->_config;
+        if (!$a) {
+            $this->_config = &new PEAR_Config;
+            $this->_config->set('php_dir', $this->statedir);
+        }
+
+        if (!class_exists('PEAR_PackageFile')) {
+            require_once 'PEAR/PackageFile.php';
+        }
+
+        $pkg = &new PEAR_PackageFile($this->_config);
+        $pf = &$pkg->fromArray($info);
+        return $pf;
+    }
+
+    /**
+     * @param string channel name
+     * @param bool whether to strictly retrieve channel names
+     * @return PEAR_ChannelFile|PEAR_Error
+     * @access private
+     */
+    function &_getChannel($channel, $noaliases = false)
+    {
+        $ch = false;
+        if ($this->_channelExists($channel, $noaliases)) {
+            $chinfo = $this->_channelInfo($channel, $noaliases);
+            if ($chinfo) {
+                if (!class_exists('PEAR_ChannelFile')) {
+                    require_once 'PEAR/ChannelFile.php';
+                }
+
+                $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo);
+            }
+        }
+
+        if ($ch) {
+            if ($ch->validate()) {
+                return $ch;
+            }
+
+            foreach ($ch->getErrors(true) as $err) {
+                $message = $err['message'] . "\n";
+            }
+
+            $ch = PEAR::raiseError($message);
+            return $ch;
+        }
+
+        if ($this->_getChannelFromAlias($channel) == 'pear.php.net') {
+            // the registry is not properly set up, so use defaults
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $pear_channel = new PEAR_ChannelFile;
+            $pear_channel->setServer('pear.php.net');
+            $pear_channel->setAlias('pear');
+            $pear_channel->setSummary('PHP Extension and Application Repository');
+            $pear_channel->setDefaultPEARProtocols();
+            $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/');
+            $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/');
+            $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/');
+            return $pear_channel;
+        }
+
+        if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') {
+            // the registry is not properly set up, so use defaults
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+            $pear_channel = new PEAR_ChannelFile;
+            $pear_channel->setServer('pecl.php.net');
+            $pear_channel->setAlias('pecl');
+            $pear_channel->setSummary('PHP Extension Community Library');
+            $pear_channel->setDefaultPEARProtocols();
+            $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/');
+            $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/');
+            $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0');
+            return $pear_channel;
+        }
+
+        if ($this->_getChannelFromAlias($channel) == 'doc.php.net') {
+            // the registry is not properly set up, so use defaults
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $doc_channel = new PEAR_ChannelFile;
+            $doc_channel->setServer('doc.php.net');
+            $doc_channel->setAlias('phpdocs');
+            $doc_channel->setSummary('PHP Documentation Team');
+            $doc_channel->setDefaultPEARProtocols();
+            $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/');
+            $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/');
+            $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/');
+            return $doc_channel;
+        }
+
+
+        if ($this->_getChannelFromAlias($channel) == '__uri') {
+            // the registry is not properly set up, so use defaults
+            if (!class_exists('PEAR_ChannelFile')) {
+                require_once 'PEAR/ChannelFile.php';
+            }
+
+            $private = new PEAR_ChannelFile;
+            $private->setName('__uri');
+            $private->setDefaultPEARProtocols();
+            $private->setBaseURL('REST1.0', '****');
+            $private->setSummary('Pseudo-channel for static packages');
+            return $private;
+        }
+
+        return $ch;
+    }
+
+    /**
+     * @param string Package name
+     * @param string Channel name
+     * @return bool
+     */
+    function packageExists($package, $channel = 'pear.php.net')
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_packageExists($package, $channel);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+
+    // {{{ channelExists()
+
+    /**
+     * @param string channel name
+     * @param bool if true, then aliases will be ignored
+     * @return bool
+     */
+    function channelExists($channel, $noaliases = false)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_channelExists($channel, $noaliases);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+
+    /**
+     * @param string channel name mirror is in
+     * @param string mirror name
+     *
+     * @return bool
+     */
+    function mirrorExists($channel, $mirror)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+
+        $ret = $this->_mirrorExists($channel, $mirror);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // {{{ isAlias()
+
+    /**
+     * Determines whether the parameter is an alias of a channel
+     * @param string
+     * @return bool
+     */
+    function isAlias($alias)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_isChannelAlias($alias);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ packageInfo()
+
+    /**
+     * @param string|null
+     * @param string|null
+     * @param string
+     * @return array|null
+     */
+    function packageInfo($package = null, $key = null, $channel = 'pear.php.net')
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_packageInfo($package, $key, $channel);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ channelInfo()
+
+    /**
+     * Retrieve a raw array of channel data.
+     *
+     * Do not use this, instead use {@link getChannel()} for normal
+     * operations.  Array structure is undefined in this method
+     * @param string channel name
+     * @param bool whether to strictly retrieve information only on non-aliases
+     * @return array|null|PEAR_Error
+     */
+    function channelInfo($channel = null, $noaliases = false)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_channelInfo($channel, $noaliases);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+
+    /**
+     * @param string
+     */
+    function channelName($channel)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_getChannelFromAlias($channel);
+        $this->_unlock();
+        return $ret;
+    }
+
+    /**
+     * @param string
+     */
+    function channelAlias($channel)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_getAlias($channel);
+        $this->_unlock();
+        return $ret;
+    }
+    // {{{ listPackages()
+
+    function listPackages($channel = false)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_listPackages($channel);
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ listAllPackages()
+
+    function listAllPackages()
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_listAllPackages();
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ listChannel()
+
+    function listChannels()
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = $this->_listChannels();
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ addPackage()
+
+    /**
+     * Add an installed package to the registry
+     * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object
+     *               that will be passed to {@link addPackage2()}
+     * @param array package info (parsed by PEAR_Common::infoFrom*() methods)
+     * @return bool success of saving
+     */
+    function addPackage($package, $info)
+    {
+        if (is_object($info)) {
+            return $this->addPackage2($info);
+        }
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+        $ret = $this->_addPackage($package, $info);
+        $this->_unlock();
+        if ($ret) {
+            if (!class_exists('PEAR_PackageFile_v1')) {
+                require_once 'PEAR/PackageFile/v1.php';
+            }
+            $pf = new PEAR_PackageFile_v1;
+            $pf->setConfig($this->_config);
+            $pf->fromArray($info);
+            $this->_dependencyDB->uninstallPackage($pf);
+            $this->_dependencyDB->installPackage($pf);
+        }
+        return $ret;
+    }
+
+    // }}}
+    // {{{ addPackage2()
+
+    function addPackage2($info)
+    {
+        if (!is_object($info)) {
+            return $this->addPackage($info['package'], $info);
+        }
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+        $ret = $this->_addPackage2($info);
+        $this->_unlock();
+        if ($ret) {
+            $this->_dependencyDB->uninstallPackage($info);
+            $this->_dependencyDB->installPackage($info);
+        }
+        return $ret;
+    }
+
+    // }}}
+    // {{{ updateChannel()
+
+    /**
+     * For future expandibility purposes, separate this
+     * @param PEAR_ChannelFile
+     */
+    function updateChannel($channel, $lastmodified = null)
+    {
+        if ($channel->getName() == '__uri') {
+            return false;
+        }
+        return $this->addChannel($channel, $lastmodified, true);
+    }
+
+    // }}}
+    // {{{ deleteChannel()
+
+    /**
+     * Deletion fails if there are any packages installed from the channel
+     * @param string|PEAR_ChannelFile channel name
+     * @return boolean|PEAR_Error True on deletion, false if it doesn't exist
+     */
+    function deleteChannel($channel)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+
+        $ret = $this->_deleteChannel($channel);
+        $this->_unlock();
+        if ($ret && is_a($this->_config, 'PEAR_Config')) {
+            $this->_config->setChannels($this->listChannels());
+        }
+
+        return $ret;
+    }
+
+    // }}}
+    // {{{ addChannel()
+
+    /**
+     * @param PEAR_ChannelFile Channel object
+     * @param string Last-Modified header from HTTP for caching
+     * @return boolean|PEAR_Error True on creation, false if it already exists
+     */
+    function addChannel($channel, $lastmodified = false, $update = false)
+    {
+        if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) {
+            return false;
+        }
+
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+
+        $ret = $this->_addChannel($channel, $update, $lastmodified);
+        $this->_unlock();
+        if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) {
+            $this->_config->setChannels($this->listChannels());
+        }
+
+        return $ret;
+    }
+
+    // }}}
+    // {{{ deletePackage()
+
+    function deletePackage($package, $channel = 'pear.php.net')
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+
+        $file = $this->_packageFileName($package, $channel);
+        $ret  = file_exists($file) ? @unlink($file) : false;
+        $this->_rebuildFileMap();
+        $this->_unlock();
+        $p = array('channel' => $channel, 'package' => $package);
+        $this->_dependencyDB->uninstallPackage($p);
+        return $ret;
+    }
+
+    // }}}
+    // {{{ updatePackage()
+
+    function updatePackage($package, $info, $merge = true)
+    {
+        if (is_object($info)) {
+            return $this->updatePackage2($info, $merge);
+        }
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+        $ret = $this->_updatePackage($package, $info, $merge);
+        $this->_unlock();
+        if ($ret) {
+            if (!class_exists('PEAR_PackageFile_v1')) {
+                require_once 'PEAR/PackageFile/v1.php';
+            }
+            $pf = new PEAR_PackageFile_v1;
+            $pf->setConfig($this->_config);
+            $pf->fromArray($this->packageInfo($package));
+            $this->_dependencyDB->uninstallPackage($pf);
+            $this->_dependencyDB->installPackage($pf);
+        }
+        return $ret;
+    }
+
+    // }}}
+    // {{{ updatePackage2()
+
+    function updatePackage2($info)
+    {
+
+        if (!is_object($info)) {
+            return $this->updatePackage($info['package'], $info, $merge);
+        }
+
+        if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) {
+            return false;
+        }
+
+        if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+            return $e;
+        }
+
+        $ret = $this->_updatePackage2($info);
+        $this->_unlock();
+        if ($ret) {
+            $this->_dependencyDB->uninstallPackage($info);
+            $this->_dependencyDB->installPackage($info);
+        }
+
+        return $ret;
+    }
+
+    // }}}
+    // {{{ getChannel()
+    /**
+     * @param string channel name
+     * @param bool whether to strictly return raw channels (no aliases)
+     * @return PEAR_ChannelFile|PEAR_Error
+     */
+    function &getChannel($channel, $noaliases = false)
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $ret = &$this->_getChannel($channel, $noaliases);
+        $this->_unlock();
+        if (!$ret) {
+            return PEAR::raiseError('Unknown channel: ' . $channel);
+        }
+        return $ret;
+    }
+
+    // }}}
+    // {{{ getPackage()
+    /**
+     * @param string package name
+     * @param string channel name
+     * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null
+     */
+    function &getPackage($package, $channel = 'pear.php.net')
+    {
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        $pf = &$this->_getPackage($package, $channel);
+        $this->_unlock();
+        return $pf;
+    }
+
+    // }}}
+
+    /**
+     * Get PEAR_PackageFile_v[1/2] objects representing the contents of
+     * a dependency group that are installed.
+     *
+     * This is used at uninstall-time
+     * @param array
+     * @return array|false
+     */
+    function getInstalledGroup($group)
+    {
+        $ret = array();
+        if (isset($group['package'])) {
+            if (!isset($group['package'][0])) {
+                $group['package'] = array($group['package']);
+            }
+            foreach ($group['package'] as $package) {
+                $depchannel = isset($package['channel']) ? $package['channel'] : '__uri';
+                $p = &$this->getPackage($package['name'], $depchannel);
+                if ($p) {
+                    $save = &$p;
+                    $ret[] = &$save;
+                }
+            }
+        }
+        if (isset($group['subpackage'])) {
+            if (!isset($group['subpackage'][0])) {
+                $group['subpackage'] = array($group['subpackage']);
+            }
+            foreach ($group['subpackage'] as $package) {
+                $depchannel = isset($package['channel']) ? $package['channel'] : '__uri';
+                $p = &$this->getPackage($package['name'], $depchannel);
+                if ($p) {
+                    $save = &$p;
+                    $ret[] = &$save;
+                }
+            }
+        }
+        if (!count($ret)) {
+            return false;
+        }
+        return $ret;
+    }
+
+    // {{{ getChannelValidator()
+    /**
+     * @param string channel name
+     * @return PEAR_Validate|false
+     */
+    function &getChannelValidator($channel)
+    {
+        $chan = $this->getChannel($channel);
+        if (PEAR::isError($chan)) {
+            return $chan;
+        }
+        $val = $chan->getValidationObject();
+        return $val;
+    }
+    // }}}
+    // {{{ getChannels()
+    /**
+     * @param string channel name
+     * @return array an array of PEAR_ChannelFile objects representing every installed channel
+     */
+    function &getChannels()
+    {
+        $ret = array();
+        if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+            return $e;
+        }
+        foreach ($this->_listChannels() as $channel) {
+            $e = &$this->_getChannel($channel);
+            if (!$e || PEAR::isError($e)) {
+                continue;
+            }
+            $ret[] = $e;
+        }
+        $this->_unlock();
+        return $ret;
+    }
+
+    // }}}
+    // {{{ checkFileMap()
+
+    /**
+     * Test whether a file or set of files belongs to a package.
+     *
+     * If an array is passed in
+     * @param string|array file path, absolute or relative to the pear
+     *                     install dir
+     * @param string|array name of PEAR package or array('package' => name, 'channel' =>
+     *                     channel) of a package that will be ignored
+     * @param string API version - 1.1 will exclude any files belonging to a package
+     * @param array private recursion variable
+     * @return array|false which package and channel the file belongs to, or an empty
+     *                     string if the file does not belong to an installed package,
+     *                     or belongs to the second parameter's package
+     */
+    function checkFileMap($path, $package = false, $api = '1.0', $attrs = false)
+    {
+        if (is_array($path)) {
+            static $notempty;
+            if (empty($notempty)) {
+                if (!class_exists('PEAR_Installer_Role')) {
+                    require_once 'PEAR/Installer/Role.php';
+                }
+                $notempty = create_function('$a','return !empty($a);');
+            }
+            $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1]))
+                : strtolower($package);
+            $pkgs = array();
+            foreach ($path as $name => $attrs) {
+                if (is_array($attrs)) {
+                    if (isset($attrs['install-as'])) {
+                        $name = $attrs['install-as'];
+                    }
+                    if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) {
+                        // these are not installed
+                        continue;
+                    }
+                    if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) {
+                        $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package;
+                    }
+                    if (isset($attrs['baseinstalldir'])) {
+                        $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name;
+                    }
+                }
+                $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs);
+                if (PEAR::isError($pkgs[$name])) {
+                    return $pkgs[$name];
+                }
+            }
+            return array_filter($pkgs, $notempty);
+        }
+        if (empty($this->filemap_cache)) {
+            if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+                return $e;
+            }
+            $err = $this->_readFileMap();
+            $this->_unlock();
+            if (PEAR::isError($err)) {
+                return $err;
+            }
+        }
+        if (!$attrs) {
+            $attrs = array('role' => 'php'); // any old call would be for PHP role only
+        }
+        if (isset($this->filemap_cache[$attrs['role']][$path])) {
+            if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) {
+                return false;
+            }
+            return $this->filemap_cache[$attrs['role']][$path];
+        }
+        $l = strlen($this->install_dir);
+        if (substr($path, 0, $l) == $this->install_dir) {
+            $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l));
+        }
+        if (isset($this->filemap_cache[$attrs['role']][$path])) {
+            if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) {
+                return false;
+            }
+            return $this->filemap_cache[$attrs['role']][$path];
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ flush()
+    /**
+     * Force a reload of the filemap
+     * @since 1.5.0RC3
+     */
+    function flushFileMap()
+    {
+        $this->filemap_cache = null;
+        clearstatcache(); // ensure that the next read gets the full, current filemap
+    }
+
+    // }}}
+    // {{{ apiVersion()
+    /**
+     * Get the expected API version.  Channels API is version 1.1, as it is backwards
+     * compatible with 1.0
+     * @return string
+     */
+    function apiVersion()
+    {
+        return '1.1';
+    }
+    // }}}
+
+
+    /**
+     * Parse a package name, or validate a parsed package name array
+     * @param string|array pass in an array of format
+     *                     array(
+     *                      'package' => 'pname',
+     *                     ['channel' => 'channame',]
+     *                     ['version' => 'version',]
+     *                     ['state' => 'state',]
+     *                     ['group' => 'groupname'])
+     *                     or a string of format
+     *                     [channel://][channame/]pname[-version|-state][/group=groupname]
+     * @return array|PEAR_Error
+     */
+    function parsePackageName($param, $defaultchannel = 'pear.php.net')
+    {
+        $saveparam = $param;
+        if (is_array($param)) {
+            // convert to string for error messages
+            $saveparam = $this->parsedPackageNameToString($param);
+            // process the array
+            if (!isset($param['package'])) {
+                return PEAR::raiseError('parsePackageName(): array $param ' .
+                    'must contain a valid package name in index "param"',
+                    'package', null, null, $param);
+            }
+            if (!isset($param['uri'])) {
+                if (!isset($param['channel'])) {
+                    $param['channel'] = $defaultchannel;
+                }
+            } else {
+                $param['channel'] = '__uri';
+            }
+        } else {
+            $components = @parse_url((string) $param);
+            if (isset($components['scheme'])) {
+                if ($components['scheme'] == 'http') {
+                    // uri package
+                    $param = array('uri' => $param, 'channel' => '__uri');
+                } elseif($components['scheme'] != 'channel') {
+                    return PEAR::raiseError('parsePackageName(): only channel:// uris may ' .
+                        'be downloaded, not "' . $param . '"', 'invalid', null, null, $param);
+                }
+            }
+            if (!isset($components['path'])) {
+                return PEAR::raiseError('parsePackageName(): array $param ' .
+                    'must contain a valid package name in "' . $param . '"',
+                    'package', null, null, $param);
+            }
+            if (isset($components['host'])) {
+                // remove the leading "/"
+                $components['path'] = substr($components['path'], 1);
+            }
+            if (!isset($components['scheme'])) {
+                if (strpos($components['path'], '/') !== false) {
+                    if ($components['path']{0} == '/') {
+                        return PEAR::raiseError('parsePackageName(): this is not ' .
+                            'a package name, it begins with "/" in "' . $param . '"',
+                            'invalid', null, null, $param);
+                    }
+                    $parts = explode('/', $components['path']);
+                    $components['host'] = array_shift($parts);
+                    if (count($parts) > 1) {
+                        $components['path'] = array_pop($parts);
+                        $components['host'] .= '/' . implode('/', $parts);
+                    } else {
+                        $components['path'] = implode('/', $parts);
+                    }
+                } else {
+                    $components['host'] = $defaultchannel;
+                }
+            } else {
+                if (strpos($components['path'], '/')) {
+                    $parts = explode('/', $components['path']);
+                    $components['path'] = array_pop($parts);
+                    $components['host'] .= '/' . implode('/', $parts);
+                }
+            }
+
+            if (is_array($param)) {
+                $param['package'] = $components['path'];
+            } else {
+                $param = array(
+                    'package' => $components['path']
+                    );
+                if (isset($components['host'])) {
+                    $param['channel'] = $components['host'];
+                }
+            }
+            if (isset($components['fragment'])) {
+                $param['group'] = $components['fragment'];
+            }
+            if (isset($components['user'])) {
+                $param['user'] = $components['user'];
+            }
+            if (isset($components['pass'])) {
+                $param['pass'] = $components['pass'];
+            }
+            if (isset($components['query'])) {
+                parse_str($components['query'], $param['opts']);
+            }
+            // check for extension
+            $pathinfo = pathinfo($param['package']);
+            if (isset($pathinfo['extension']) &&
+                  in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) {
+                $param['extension'] = $pathinfo['extension'];
+                $param['package'] = substr($pathinfo['basename'], 0,
+                    strlen($pathinfo['basename']) - 4);
+            }
+            // check for version
+            if (strpos($param['package'], '-')) {
+                $test = explode('-', $param['package']);
+                if (count($test) != 2) {
+                    return PEAR::raiseError('parsePackageName(): only one version/state ' .
+                        'delimiter "-" is allowed in "' . $saveparam . '"',
+                        'version', null, null, $param);
+                }
+                list($param['package'], $param['version']) = $test;
+            }
+        }
+        // validation
+        $info = $this->channelExists($param['channel']);
+        if (PEAR::isError($info)) {
+            return $info;
+        }
+        if (!$info) {
+            return PEAR::raiseError('unknown channel "' . $param['channel'] .
+                '" in "' . $saveparam . '"', 'channel', null, null, $param);
+        }
+        $chan = $this->getChannel($param['channel']);
+        if (PEAR::isError($chan)) {
+            return $chan;
+        }
+        if (!$chan) {
+            return PEAR::raiseError("Exception: corrupt registry, could not " .
+                "retrieve channel " . $param['channel'] . " information",
+                'registry', null, null, $param);
+        }
+        $param['channel'] = $chan->getName();
+        $validate = $chan->getValidationObject();
+        $vpackage = $chan->getValidationPackage();
+        // validate package name
+        if (!$validate->validPackageName($param['package'], $vpackage['_content'])) {
+            return PEAR::raiseError('parsePackageName(): invalid package name "' .
+                $param['package'] . '" in "' . $saveparam . '"',
+                'package', null, null, $param);
+        }
+        if (isset($param['group'])) {
+            if (!PEAR_Validate::validGroupName($param['group'])) {
+                return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] .
+                    '" is not a valid group name in "' . $saveparam . '"', 'group', null, null,
+                    $param);
+            }
+        }
+        if (isset($param['state'])) {
+            if (!in_array(strtolower($param['state']), $validate->getValidStates())) {
+                return PEAR::raiseError('parsePackageName(): state "' . $param['state']
+                    . '" is not a valid state in "' . $saveparam . '"',
+                    'state', null, null, $param);
+            }
+        }
+        if (isset($param['version'])) {
+            if (isset($param['state'])) {
+                return PEAR::raiseError('parsePackageName(): cannot contain both ' .
+                    'a version and a stability (state) in "' . $saveparam . '"',
+                    'version/state', null, null, $param);
+            }
+            // check whether version is actually a state
+            if (in_array(strtolower($param['version']), $validate->getValidStates())) {
+                $param['state'] = strtolower($param['version']);
+                unset($param['version']);
+            } else {
+                if (!$validate->validVersion($param['version'])) {
+                    return PEAR::raiseError('parsePackageName(): "' . $param['version'] .
+                        '" is neither a valid version nor a valid state in "' .
+                        $saveparam . '"', 'version/state', null, null, $param);
+                }
+            }
+        }
+        return $param;
+    }
+
+    /**
+     * @param array
+     * @return string
+     */
+    function parsedPackageNameToString($parsed, $brief = false)
+    {
+        if (is_string($parsed)) {
+            return $parsed;
+        }
+        if (is_object($parsed)) {
+            $p = $parsed;
+            $parsed = array(
+                'package' => $p->getPackage(),
+                'channel' => $p->getChannel(),
+                'version' => $p->getVersion(),
+            );
+        }
+        if (isset($parsed['uri'])) {
+            return $parsed['uri'];
+        }
+        if ($brief) {
+            if ($channel = $this->channelAlias($parsed['channel'])) {
+                return $channel . '/' . $parsed['package'];
+            }
+        }
+        $upass = '';
+        if (isset($parsed['user'])) {
+            $upass = $parsed['user'];
+            if (isset($parsed['pass'])) {
+                $upass .= ':' . $parsed['pass'];
+            }
+            $upass = "$upass@";
+        }
+        $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package'];
+        if (isset($parsed['version']) || isset($parsed['state'])) {
+            $ver = isset($parsed['version']) ? $parsed['version'] : '';
+            $ver .= isset($parsed['state']) ? $parsed['state'] : '';
+            $ret .= '-' . $ver;
+        }
+        if (isset($parsed['extension'])) {
+            $ret .= '.' . $parsed['extension'];
+        }
+        if (isset($parsed['opts'])) {
+            $ret .= '?';
+            foreach ($parsed['opts'] as $name => $value) {
+                $parsed['opts'][$name] = "$name=$value";
+            }
+            $ret .= implode('&', $parsed['opts']);
+        }
+        if (isset($parsed['group'])) {
+            $ret .= '#' . $parsed['group'];
+        }
+        return $ret;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/RunTest.php b/WEB-INF/lib/pear/PEAR/RunTest.php
new file mode 100644 (file)
index 0000000..5182490
--- /dev/null
@@ -0,0 +1,968 @@
+<?php
+/**
+ * PEAR_RunTest
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: RunTest.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.3.3
+ */
+
+/**
+ * for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Config.php';
+
+define('DETAILED', 1);
+putenv("PHP_PEAR_RUNTESTS=1");
+
+/**
+ * Simplified version of PHP's test suite
+ *
+ * Try it with:
+ *
+ * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);'
+ *
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.3.3
+ */
+class PEAR_RunTest
+{
+    var $_headers = array();
+    var $_logger;
+    var $_options;
+    var $_php;
+    var $tests_count;
+    var $xdebug_loaded;
+    /**
+     * Saved value of php executable, used to reset $_php when we
+     * have a test that uses cgi
+     *
+     * @var unknown_type
+     */
+    var $_savephp;
+    var $ini_overwrites = array(
+        'output_handler=',
+        'open_basedir=',
+        'safe_mode=0',
+        'disable_functions=',
+        'output_buffering=Off',
+        'display_errors=1',
+        'log_errors=0',
+        'html_errors=0',
+        'track_errors=1',
+        'report_memleaks=0',
+        'report_zend_debug=0',
+        'docref_root=',
+        'docref_ext=.html',
+        'error_prepend_string=',
+        'error_append_string=',
+        'auto_prepend_file=',
+        'auto_append_file=',
+        'magic_quotes_runtime=0',
+        'xdebug.default_enable=0',
+        'allow_url_fopen=1',
+    );
+
+    /**
+     * An object that supports the PEAR_Common->log() signature, or null
+     * @param PEAR_Common|null
+     */
+    function PEAR_RunTest($logger = null, $options = array())
+    {
+        if (!defined('E_DEPRECATED')) {
+            define('E_DEPRECATED', 0);
+        }
+        if (!defined('E_STRICT')) {
+            define('E_STRICT', 0);
+        }
+        $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT));
+        if (is_null($logger)) {
+            require_once 'PEAR/Common.php';
+            $logger = new PEAR_Common;
+        }
+        $this->_logger  = $logger;
+        $this->_options = $options;
+
+        $conf = &PEAR_Config::singleton();
+        $this->_php = $conf->get('php_bin');
+    }
+
+    /**
+     * Taken from php-src/run-tests.php
+     *
+     * @param string $commandline command name
+     * @param array $env
+     * @param string $stdin standard input to pass to the command
+     * @return unknown
+     */
+    function system_with_timeout($commandline, $env = null, $stdin = null)
+    {
+        $data = '';
+        if (version_compare(phpversion(), '5.0.0', '<')) {
+            $proc = proc_open($commandline, array(
+                0 => array('pipe', 'r'),
+                1 => array('pipe', 'w'),
+                2 => array('pipe', 'w')
+                ), $pipes);
+        } else {
+            $proc = proc_open($commandline, array(
+                0 => array('pipe', 'r'),
+                1 => array('pipe', 'w'),
+                2 => array('pipe', 'w')
+                ), $pipes, null, $env, array('suppress_errors' => true));
+        }
+
+        if (!$proc) {
+            return false;
+        }
+
+        if (is_string($stdin)) {
+            fwrite($pipes[0], $stdin);
+        }
+        fclose($pipes[0]);
+
+        while (true) {
+            /* hide errors from interrupted syscalls */
+            $r = $pipes;
+            $e = $w = null;
+            $n = @stream_select($r, $w, $e, 60);
+
+            if ($n === 0) {
+                /* timed out */
+                $data .= "\n ** ERROR: process timed out **\n";
+                proc_terminate($proc);
+                return array(1234567890, $data);
+            } else if ($n > 0) {
+                $line = fread($pipes[1], 8192);
+                if (strlen($line) == 0) {
+                    /* EOF */
+                    break;
+                }
+                $data .= $line;
+            }
+        }
+        if (function_exists('proc_get_status')) {
+            $stat = proc_get_status($proc);
+            if ($stat['signaled']) {
+                $data .= "\nTermsig=".$stat['stopsig'];
+            }
+        }
+        $code = proc_close($proc);
+        if (function_exists('proc_get_status')) {
+            $code = $stat['exitcode'];
+        }
+        return array($code, $data);
+    }
+
+    /**
+     * Turns a PHP INI string into an array
+     *
+     * Turns -d "include_path=/foo/bar" into this:
+     * array(
+     *   'include_path' => array(
+     *          'operator' => '-d',
+     *          'value'    => '/foo/bar',
+     *   )
+     * )
+     * Works both with quotes and without
+     *
+     * @param string an PHP INI string, -d "include_path=/foo/bar"
+     * @return array
+     */
+    function iniString2array($ini_string)
+    {
+        if (!$ini_string) {
+            return array();
+        }
+        $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY);
+        $key   = $split[1][0] == '"'                     ? substr($split[1], 1)     : $split[1];
+        $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2];
+        // FIXME review if this is really the struct to go with
+        $array = array($key => array('operator' => $split[0], 'value' => $value));
+        return $array;
+    }
+
+    function settings2array($settings, $ini_settings)
+    {
+        foreach ($settings as $setting) {
+            if (strpos($setting, '=') !== false) {
+                $setting = explode('=', $setting, 2);
+                $name  = trim(strtolower($setting[0]));
+                $value = trim($setting[1]);
+                $ini_settings[$name] = $value;
+            }
+        }
+        return $ini_settings;
+    }
+
+    function settings2params($ini_settings)
+    {
+        $settings = '';
+        foreach ($ini_settings as $name => $value) {
+            if (is_array($value)) {
+                $operator = $value['operator'];
+                $value    = $value['value'];
+            } else {
+                $operator = '-d';
+            }
+            $value = addslashes($value);
+            $settings .= " $operator \"$name=$value\"";
+        }
+        return $settings;
+    }
+
+    function _preparePhpBin($php, $file, $ini_settings)
+    {
+        $file = escapeshellarg($file);
+        // This was fixed in php 5.3 and is not needed after that
+        if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) {
+            $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"';
+        } else {
+            $cmd = $php . $ini_settings . ' -f ' . $file;
+        }
+
+        return $cmd;
+    }
+
+    function runPHPUnit($file, $ini_settings = '')
+    {
+        if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) {
+            $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file);
+        } elseif (file_exists($file)) {
+            $file = realpath($file);
+        }
+
+        $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings);
+        if (isset($this->_logger)) {
+            $this->_logger->log(2, 'Running command "' . $cmd . '"');
+        }
+
+        $savedir = getcwd(); // in case the test moves us around
+        chdir(dirname($file));
+        echo `$cmd`;
+        chdir($savedir);
+        return 'PASSED'; // we have no way of knowing this information so assume passing
+    }
+
+    /**
+     * Runs an individual test case.
+     *
+     * @param string       The filename of the test
+     * @param array|string INI settings to be applied to the test run
+     * @param integer      Number what the current running test is of the
+     *                     whole test suite being runned.
+     *
+     * @return string|object Returns PASSED, WARNED, FAILED depending on how the
+     *                       test came out.
+     *                       PEAR Error when the tester it self fails
+     */
+    function run($file, $ini_settings = array(), $test_number = 1)
+    {
+        if (isset($this->_savephp)) {
+            $this->_php = $this->_savephp;
+            unset($this->_savephp);
+        }
+        if (empty($this->_options['cgi'])) {
+            // try to see if php-cgi is in the path
+            $res = $this->system_with_timeout('php-cgi -v');
+            if (false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) {
+                $this->_options['cgi'] = 'php-cgi';
+            }
+        }
+        if (1 < $len = strlen($this->tests_count)) {
+            $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT);
+            $test_nr = "[$test_number/$this->tests_count] ";
+        } else {
+            $test_nr = '';
+        }
+
+        $file = realpath($file);
+        $section_text = $this->_readFile($file);
+        if (PEAR::isError($section_text)) {
+            return $section_text;
+        }
+
+        if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) {
+            return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file");
+        }
+
+        $cwd = getcwd();
+
+        $pass_options = '';
+        if (!empty($this->_options['ini'])) {
+            $pass_options = $this->_options['ini'];
+        }
+
+        if (is_string($ini_settings)) {
+            $ini_settings = $this->iniString2array($ini_settings);
+        }
+
+        $ini_settings = $this->settings2array($this->ini_overwrites, $ini_settings);
+        if ($section_text['INI']) {
+            if (strpos($section_text['INI'], '{PWD}') !== false) {
+                $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
+            }
+            $ini = preg_split( "/[\n\r]+/", $section_text['INI']);
+            $ini_settings = $this->settings2array($ini, $ini_settings);
+        }
+        $ini_settings = $this->settings2params($ini_settings);
+        $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file);
+
+        $tested = trim($section_text['TEST']);
+        $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' ';
+
+        if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) ||
+              !empty($section_text['UPLOAD']) || !empty($section_text['GET']) ||
+              !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) {
+            if (empty($this->_options['cgi'])) {
+                if (!isset($this->_options['quiet'])) {
+                    $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')");
+                }
+                if (isset($this->_options['tapoutput'])) {
+                    return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info');
+                }
+                return 'SKIPPED';
+            }
+            $this->_savephp = $this->_php;
+            $this->_php = $this->_options['cgi'];
+        }
+
+        $temp_dir = realpath(dirname($file));
+        $main_file_name = basename($file, 'phpt');
+        $diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff';
+        $log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log';
+        $exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp';
+        $output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out';
+        $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem';
+        $temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php';
+        $temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php';
+        $temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php';
+        $tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
+
+        // unlink old test results
+        $this->_cleanupOldFiles($file);
+
+        // Check if test should be skipped.
+        $res  = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings);
+        if (count($res) != 2) {
+            return $res;
+        }
+        $info = $res['info'];
+        $warn = $res['warn'];
+
+        // We've satisfied the preconditions - run the test!
+        if (isset($this->_options['coverage']) && $this->xdebug_loaded) {
+            $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug';
+            $text = "\n" . 'function coverage_shutdown() {' .
+                    "\n" . '    $xdebug = var_export(xdebug_get_code_coverage(), true);';
+            if (!function_exists('file_put_contents')) {
+                $text .= "\n" . '    $fh = fopen(\'' . $xdebug_file . '\', "wb");' .
+                        "\n" . '    if ($fh !== false) {' .
+                        "\n" . '        fwrite($fh, $xdebug);' .
+                        "\n" . '        fclose($fh);' .
+                        "\n" . '    }';
+            } else {
+                $text .= "\n" . '    file_put_contents(\'' . $xdebug_file . '\', $xdebug);';
+            }
+
+            // Workaround for http://pear.php.net/bugs/bug.php?id=17292
+            $lines             = explode("\n", $section_text['FILE']);
+            $numLines          = count($lines);
+            $namespace         = '';
+            $coverage_shutdown = 'coverage_shutdown';
+
+            if (
+                substr($lines[0], 0, 2) == '<?' ||
+                substr($lines[0], 0, 5) == '<?php'
+            ) {
+                unset($lines[0]);
+            }
+
+
+            for ($i = 0; $i < $numLines; $i++) {
+                if (isset($lines[$i]) && substr($lines[$i], 0, 9) == 'namespace') {
+                    $namespace         = substr($lines[$i], 10, -1);
+                    $coverage_shutdown = $namespace . '\\coverage_shutdown';
+                    $namespace         = "namespace " . $namespace . ";\n";
+
+                    unset($lines[$i]);
+                    break;
+                }
+            }
+
+            $text .= "\n    xdebug_stop_code_coverage();" .
+                "\n" . '} // end coverage_shutdown()' .
+                "\n\n" . 'register_shutdown_function("' . $coverage_shutdown . '");';
+            $text .= "\n" . 'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' . "\n";
+
+            $this->save_text($temp_file, "<?php\n" . $namespace . $text  . "\n" . implode("\n", $lines));
+        } else {
+            $this->save_text($temp_file, $section_text['FILE']);
+        }
+
+        $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : '';
+        $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings);
+        $cmd.= "$args 2>&1";
+        if (isset($this->_logger)) {
+            $this->_logger->log(2, 'Running command "' . $cmd . '"');
+        }
+
+        // Reset environment from any previous test.
+        $env = $this->_resetEnv($section_text, $temp_file);
+
+        $section_text = $this->_processUpload($section_text, $file);
+        if (PEAR::isError($section_text)) {
+            return $section_text;
+        }
+
+        if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
+            $post = trim($section_text['POST_RAW']);
+            $raw_lines = explode("\n", $post);
+
+            $request = '';
+            $started = false;
+            foreach ($raw_lines as $i => $line) {
+                if (empty($env['CONTENT_TYPE']) &&
+                    preg_match('/^Content-Type:(.*)/i', $line, $res)) {
+                    $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
+                    continue;
+                }
+                if ($started) {
+                    $request .= "\n";
+                }
+                $started = true;
+                $request .= $line;
+            }
+
+            $env['CONTENT_LENGTH'] = strlen($request);
+            $env['REQUEST_METHOD'] = 'POST';
+
+            $this->save_text($tmp_post, $request);
+            $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
+        } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
+            $post = trim($section_text['POST']);
+            $this->save_text($tmp_post, $post);
+            $content_length = strlen($post);
+
+            $env['REQUEST_METHOD'] = 'POST';
+            $env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
+            $env['CONTENT_LENGTH'] = $content_length;
+
+            $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
+        } else {
+            $env['REQUEST_METHOD'] = 'GET';
+            $env['CONTENT_TYPE']   = '';
+            $env['CONTENT_LENGTH'] = '';
+        }
+
+        if (OS_WINDOWS && isset($section_text['RETURNS'])) {
+            ob_start();
+            system($cmd, $return_value);
+            $out = ob_get_contents();
+            ob_end_clean();
+            $section_text['RETURNS'] = (int) trim($section_text['RETURNS']);
+            $returnfail = ($return_value != $section_text['RETURNS']);
+        } else {
+            $returnfail = false;
+            $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null;
+            $out = $this->system_with_timeout($cmd, $env, $stdin);
+            $return_value = $out[0];
+            $out = $out[1];
+        }
+
+        $output = preg_replace('/\r\n/', "\n", trim($out));
+
+        if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) {
+            @unlink(realpath($tmp_post));
+        }
+        chdir($cwd); // in case the test moves us around
+
+        $this->_testCleanup($section_text, $temp_clean);
+
+        /* when using CGI, strip the headers from the output */
+        $output = $this->_stripHeadersCGI($output);
+
+        if (isset($section_text['EXPECTHEADERS'])) {
+            $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']);
+            $missing = array_diff_assoc($testheaders, $this->_headers);
+            $changed = '';
+            foreach ($missing as $header => $value) {
+                if (isset($this->_headers[$header])) {
+                    $changed .= "-$header: $value\n+$header: ";
+                    $changed .= $this->_headers[$header];
+                } else {
+                    $changed .= "-$header: $value\n";
+                }
+            }
+            if ($missing) {
+                // tack on failed headers to output:
+                $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed";
+            }
+        }
+        // Does the output match what is expected?
+        do {
+            if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
+                if (isset($section_text['EXPECTF'])) {
+                    $wanted = trim($section_text['EXPECTF']);
+                } else {
+                    $wanted = trim($section_text['EXPECTREGEX']);
+                }
+                $wanted_re = preg_replace('/\r\n/', "\n", $wanted);
+                if (isset($section_text['EXPECTF'])) {
+                    $wanted_re = preg_quote($wanted_re, '/');
+                    // Stick to basics
+                    $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy
+                    $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re);
+                    $wanted_re = str_replace("%d", "[0-9]+", $wanted_re);
+                    $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re);
+                    $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re);
+                    $wanted_re = str_replace("%c", ".", $wanted_re);
+                    // %f allows two points "-.0.0" but that is the best *simple* expression
+                }
+
+    /* DEBUG YOUR REGEX HERE
+            var_dump($wanted_re);
+            print(str_repeat('=', 80) . "\n");
+            var_dump($output);
+    */
+                if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) {
+                    if (file_exists($temp_file)) {
+                        unlink($temp_file);
+                    }
+                    if (array_key_exists('FAIL', $section_text)) {
+                        break;
+                    }
+                    if (!isset($this->_options['quiet'])) {
+                        $this->_logger->log(0, "PASS $test_nr$tested$info");
+                    }
+                    if (isset($this->_options['tapoutput'])) {
+                        return array('ok', ' - ' . $tested);
+                    }
+                    return 'PASSED';
+                }
+            } else {
+                if (isset($section_text['EXPECTFILE'])) {
+                    $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']);
+                    if (!($fp = @fopen($f, 'rb'))) {
+                        return PEAR::raiseError('--EXPECTFILE-- section file ' .
+                            $f . ' not found');
+                    }
+                    fclose($fp);
+                    $section_text['EXPECT'] = file_get_contents($f);
+                }
+
+                if (isset($section_text['EXPECT'])) {
+                    $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT']));
+                } else {
+                    $wanted = '';
+                }
+
+                // compare and leave on success
+                if (!$returnfail && 0 == strcmp($output, $wanted)) {
+                    if (file_exists($temp_file)) {
+                        unlink($temp_file);
+                    }
+                    if (array_key_exists('FAIL', $section_text)) {
+                        break;
+                    }
+                    if (!isset($this->_options['quiet'])) {
+                        $this->_logger->log(0, "PASS $test_nr$tested$info");
+                    }
+                    if (isset($this->_options['tapoutput'])) {
+                        return array('ok', ' - ' . $tested);
+                    }
+                    return 'PASSED';
+                }
+            }
+        } while (false);
+
+        if (array_key_exists('FAIL', $section_text)) {
+            // we expect a particular failure
+            // this is only used for testing PEAR_RunTest
+            $expectf  = isset($section_text['EXPECTF']) ? $wanted_re : null;
+            $faildiff = $this->generate_diff($wanted, $output, null, $expectf);
+            $faildiff = preg_replace('/\r/', '', $faildiff);
+            $wanted   = preg_replace('/\r/', '', trim($section_text['FAIL']));
+            if ($faildiff == $wanted) {
+                if (!isset($this->_options['quiet'])) {
+                    $this->_logger->log(0, "PASS $test_nr$tested$info");
+                }
+                if (isset($this->_options['tapoutput'])) {
+                    return array('ok', ' - ' . $tested);
+                }
+                return 'PASSED';
+            }
+            unset($section_text['EXPECTF']);
+            $output = $faildiff;
+            if (isset($section_text['RETURNS'])) {
+                return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' .
+                    $file);
+            }
+        }
+
+        // Test failed so we need to report details.
+        $txt = $warn ? 'WARN ' : 'FAIL ';
+        $this->_logger->log(0, $txt . $test_nr . $tested . $info);
+
+        // write .exp
+        $res = $this->_writeLog($exp_filename, $wanted);
+        if (PEAR::isError($res)) {
+            return $res;
+        }
+
+        // write .out
+        $res = $this->_writeLog($output_filename, $output);
+        if (PEAR::isError($res)) {
+            return $res;
+        }
+
+        // write .diff
+        $returns = isset($section_text['RETURNS']) ?
+                        array(trim($section_text['RETURNS']), $return_value) : null;
+        $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null;
+        $data = $this->generate_diff($wanted, $output, $returns, $expectf);
+        $res  = $this->_writeLog($diff_filename, $data);
+        if (PEAR::isError($res)) {
+            return $res;
+        }
+
+        // write .log
+        $data = "
+---- EXPECTED OUTPUT
+$wanted
+---- ACTUAL OUTPUT
+$output
+---- FAILED
+";
+
+        if ($returnfail) {
+            $data .= "
+---- EXPECTED RETURN
+$section_text[RETURNS]
+---- ACTUAL RETURN
+$return_value
+";
+        }
+
+        $res = $this->_writeLog($log_filename, $data);
+        if (PEAR::isError($res)) {
+            return $res;
+        }
+
+        if (isset($this->_options['tapoutput'])) {
+            $wanted = explode("\n", $wanted);
+            $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted);
+            $output = explode("\n", $output);
+            $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output);
+            return array($wanted . $output . 'not ok', ' - ' . $tested);
+        }
+        return $warn ? 'WARNED' : 'FAILED';
+    }
+
+    function generate_diff($wanted, $output, $rvalue, $wanted_re)
+    {
+        $w  = explode("\n", $wanted);
+        $o  = explode("\n", $output);
+        $wr = explode("\n", $wanted_re);
+        $w1 = array_diff_assoc($w, $o);
+        $o1 = array_diff_assoc($o, $w);
+        $o2 = $w2 = array();
+        foreach ($w1 as $idx => $val) {
+            if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) ||
+                  !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) {
+                $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val;
+            }
+        }
+        foreach ($o1 as $idx => $val) {
+            if (!$wanted_re || !isset($wr[$idx]) ||
+                  !preg_match('/^' . $wr[$idx] . '\\z/', $val)) {
+                $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val;
+            }
+        }
+        $diff = array_merge($w2, $o2);
+        ksort($diff);
+        $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : '';
+        return implode("\r\n", $diff) . $extra;
+    }
+
+    //  Write the given text to a temporary file, and return the filename.
+    function save_text($filename, $text)
+    {
+        if (!$fp = fopen($filename, 'w')) {
+            return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)");
+        }
+        fwrite($fp, $text);
+        fclose($fp);
+    if (1 < DETAILED) echo "
+FILE $filename {{{
+$text
+}}}
+";
+    }
+
+    function _cleanupOldFiles($file)
+    {
+        $temp_dir = realpath(dirname($file));
+        $mainFileName = basename($file, 'phpt');
+        $diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff';
+        $log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log';
+        $exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp';
+        $output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out';
+        $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem';
+        $temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php';
+        $temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php';
+        $temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php';
+        $tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
+
+        // unlink old test results
+        @unlink($diff_filename);
+        @unlink($log_filename);
+        @unlink($exp_filename);
+        @unlink($output_filename);
+        @unlink($memcheck_filename);
+        @unlink($temp_file);
+        @unlink($temp_skipif);
+        @unlink($tmp_post);
+        @unlink($temp_clean);
+    }
+
+    function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings)
+    {
+        $info = '';
+        $warn = false;
+        if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) {
+            $this->save_text($temp_skipif, $section_text['SKIPIF']);
+            $output = $this->system_with_timeout("$this->_php$ini_settings -f \"$temp_skipif\"");
+            $output = $output[1];
+            $loutput = ltrim($output);
+            unlink($temp_skipif);
+            if (!strncasecmp('skip', $loutput, 4)) {
+                $skipreason = "SKIP $tested";
+                if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) {
+                    $skipreason .= '(reason: ' . $m[1] . ')';
+                }
+                if (!isset($this->_options['quiet'])) {
+                    $this->_logger->log(0, $skipreason);
+                }
+                if (isset($this->_options['tapoutput'])) {
+                    return array('ok', ' # skip ' . $reason);
+                }
+                return 'SKIPPED';
+            }
+
+            if (!strncasecmp('info', $loutput, 4)
+                && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) {
+                $info = " (info: $m[1])";
+            }
+
+            if (!strncasecmp('warn', $loutput, 4)
+                && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) {
+                $warn = true; /* only if there is a reason */
+                $info = " (warn: $m[1])";
+            }
+        }
+
+        return array('warn' => $warn, 'info' => $info);
+    }
+
+    function _stripHeadersCGI($output)
+    {
+        $this->headers = array();
+        if (!empty($this->_options['cgi']) &&
+              $this->_php == $this->_options['cgi'] &&
+              preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) {
+            $output = isset($match[2]) ? trim($match[2]) : '';
+            $this->_headers = $this->_processHeaders($match[1]);
+        }
+
+        return $output;
+    }
+
+    /**
+     * Return an array that can be used with array_diff() to compare headers
+     *
+     * @param string $text
+     */
+    function _processHeaders($text)
+    {
+        $headers = array();
+        $rh = preg_split("/[\n\r]+/", $text);
+        foreach ($rh as $line) {
+            if (strpos($line, ':')!== false) {
+                $line = explode(':', $line, 2);
+                $headers[trim($line[0])] = trim($line[1]);
+            }
+        }
+        return $headers;
+    }
+
+    function _readFile($file)
+    {
+        // Load the sections of the test file.
+        $section_text = array(
+            'TEST'   => '(unnamed test)',
+            'SKIPIF' => '',
+            'GET'    => '',
+            'COOKIE' => '',
+            'POST'   => '',
+            'ARGS'   => '',
+            'INI'    => '',
+            'CLEAN'  => '',
+        );
+
+        if (!is_file($file) || !$fp = fopen($file, "r")) {
+            return PEAR::raiseError("Cannot open test file: $file");
+        }
+
+        $section = '';
+        while (!feof($fp)) {
+            $line = fgets($fp);
+
+            // Match the beginning of a section.
+            if (preg_match('/^--([_A-Z]+)--/', $line, $r)) {
+                $section = $r[1];
+                $section_text[$section] = '';
+                continue;
+            } elseif (empty($section)) {
+                fclose($fp);
+                return PEAR::raiseError("Invalid sections formats in test file: $file");
+            }
+
+            // Add to the section text.
+            $section_text[$section] .= $line;
+        }
+        fclose($fp);
+
+        return $section_text;
+    }
+
+    function _writeLog($logname, $data)
+    {
+        if (!$log = fopen($logname, 'w')) {
+            return PEAR::raiseError("Cannot create test log - $logname");
+        }
+        fwrite($log, $data);
+        fclose($log);
+    }
+
+    function _resetEnv($section_text, $temp_file)
+    {
+        $env = $_ENV;
+        $env['REDIRECT_STATUS'] = '';
+        $env['QUERY_STRING']    = '';
+        $env['PATH_TRANSLATED'] = '';
+        $env['SCRIPT_FILENAME'] = '';
+        $env['REQUEST_METHOD']  = '';
+        $env['CONTENT_TYPE']    = '';
+        $env['CONTENT_LENGTH']  = '';
+        if (!empty($section_text['ENV'])) {
+            if (strpos($section_text['ENV'], '{PWD}') !== false) {
+                $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']);
+            }
+            foreach (explode("\n", trim($section_text['ENV'])) as $e) {
+                $e = explode('=', trim($e), 2);
+                if (!empty($e[0]) && isset($e[1])) {
+                    $env[$e[0]] = $e[1];
+                }
+            }
+        }
+        if (array_key_exists('GET', $section_text)) {
+            $env['QUERY_STRING'] = trim($section_text['GET']);
+        } else {
+            $env['QUERY_STRING'] = '';
+        }
+        if (array_key_exists('COOKIE', $section_text)) {
+            $env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
+        } else {
+            $env['HTTP_COOKIE'] = '';
+        }
+        $env['REDIRECT_STATUS'] = '1';
+        $env['PATH_TRANSLATED'] = $temp_file;
+        $env['SCRIPT_FILENAME'] = $temp_file;
+
+        return $env;
+    }
+
+    function _processUpload($section_text, $file)
+    {
+        if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) {
+            $upload_files = trim($section_text['UPLOAD']);
+            $upload_files = explode("\n", $upload_files);
+
+            $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" .
+                       "-----------------------------20896060251896012921717172737\n";
+            foreach ($upload_files as $fileinfo) {
+                $fileinfo = explode('=', $fileinfo);
+                if (count($fileinfo) != 2) {
+                    return PEAR::raiseError("Invalid UPLOAD section in test file: $file");
+                }
+                if (!realpath(dirname($file) . '/' . $fileinfo[1])) {
+                    return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " .
+                        "in test file: $file");
+                }
+                $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]);
+                $fileinfo[1] = basename($fileinfo[1]);
+                $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n";
+                $request .= "Content-Type: text/plain\n\n";
+                $request .= $file_contents . "\n" .
+                    "-----------------------------20896060251896012921717172737\n";
+            }
+
+            if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
+                // encode POST raw
+                $post = trim($section_text['POST']);
+                $post = explode('&', $post);
+                foreach ($post as $i => $post_info) {
+                    $post_info = explode('=', $post_info);
+                    if (count($post_info) != 2) {
+                        return PEAR::raiseError("Invalid POST data in test file: $file");
+                    }
+                    $post_info[0] = rawurldecode($post_info[0]);
+                    $post_info[1] = rawurldecode($post_info[1]);
+                    $post[$i] = $post_info;
+                }
+                foreach ($post as $post_info) {
+                    $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n";
+                    $request .= $post_info[1] . "\n" .
+                        "-----------------------------20896060251896012921717172737\n";
+                }
+                unset($section_text['POST']);
+            }
+            $section_text['POST_RAW'] = $request;
+        }
+
+        return $section_text;
+    }
+
+    function _testCleanup($section_text, $temp_clean)
+    {
+        if ($section_text['CLEAN']) {
+            // perform test cleanup
+            $this->save_text($temp_clean, $section_text['CLEAN']);
+            $output = $this->system_with_timeout("$this->_php $temp_clean  2>&1");
+            if (strlen($output[1])) {
+                echo "BORKED --CLEAN-- section! output:\n", $output[1];
+            }
+            if (file_exists($temp_clean)) {
+                unlink($temp_clean);
+            }
+        }
+    }
+}
diff --git a/WEB-INF/lib/pear/PEAR/Task/Common.php b/WEB-INF/lib/pear/PEAR/Task/Common.php
new file mode 100644 (file)
index 0000000..5b99c2e
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+/**
+ * PEAR_Task_Common, base class for installer tasks
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**#@+
+ * Error codes for task validation routines
+ */
+define('PEAR_TASK_ERROR_NOATTRIBS', 1);
+define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2);
+define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3);
+define('PEAR_TASK_ERROR_INVALID', 4);
+/**#@-*/
+define('PEAR_TASK_PACKAGE', 1);
+define('PEAR_TASK_INSTALL', 2);
+define('PEAR_TASK_PACKAGEANDINSTALL', 3);
+/**
+ * A task is an operation that manipulates the contents of a file.
+ *
+ * Simple tasks operate on 1 file.  Multiple tasks are executed after all files have been
+ * processed and installed, and are designed to operate on all files containing the task.
+ * The Post-install script task simply takes advantage of the fact that it will be run
+ * after installation, replace is a simple task.
+ *
+ * Combining tasks is possible, but ordering is significant.
+ *
+ * <file name="test.php" role="php">
+ *  <tasks:replace from="@data-dir@" to="data_dir" type="pear-config"/>
+ *  <tasks:postinstallscript/>
+ * </file>
+ *
+ * This will first replace any instance of @data-dir@ in the test.php file
+ * with the path to the current data directory.  Then, it will include the
+ * test.php file and run the script it contains to configure the package post-installation.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ * @abstract
+ */
+class PEAR_Task_Common
+{
+    /**
+     * Valid types for this version are 'simple' and 'multiple'
+     *
+     * - simple tasks operate on the contents of a file and write out changes to disk
+     * - multiple tasks operate on the contents of many files and write out the
+     *   changes directly to disk
+     *
+     * Child task classes must override this property.
+     * @access protected
+     */
+    var $type = 'simple';
+    /**
+     * Determines which install phase this task is executed under
+     */
+    var $phase = PEAR_TASK_INSTALL;
+    /**
+     * @access protected
+     */
+    var $config;
+    /**
+     * @access protected
+     */
+    var $registry;
+    /**
+     * @access protected
+     */
+    var $logger;
+    /**
+     * @access protected
+     */
+    var $installphase;
+    /**
+     * @param PEAR_Config
+     * @param PEAR_Common
+     */
+    function PEAR_Task_Common(&$config, &$logger, $phase)
+    {
+        $this->config = &$config;
+        $this->registry = &$config->getRegistry();
+        $this->logger = &$logger;
+        $this->installphase = $phase;
+        if ($this->type == 'multiple') {
+            $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this;
+        }
+    }
+
+    /**
+     * Validate the basic contents of a task tag.
+     * @param PEAR_PackageFile_v2
+     * @param array
+     * @param PEAR_Config
+     * @param array the entire parsed <file> tag
+     * @return true|array On error, return an array in format:
+     *    array(PEAR_TASK_ERROR_???[, param1][, param2][, ...])
+     *
+     *    For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in
+     *    For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array
+     *    of legal values in
+     * @static
+     * @abstract
+     */
+    function validateXml($pkg, $xml, $config, $fileXml)
+    {
+    }
+
+    /**
+     * Initialize a task instance with the parameters
+     * @param array raw, parsed xml
+     * @param array attributes from the <file> tag containing this task
+     * @param string|null last installed version of this package
+     * @abstract
+     */
+    function init($xml, $fileAttributes, $lastVersion)
+    {
+    }
+
+    /**
+     * Begin a task processing session.  All multiple tasks will be processed after each file
+     * has been successfully installed, all simple tasks should perform their task here and
+     * return any errors using the custom throwError() method to allow forward compatibility
+     *
+     * This method MUST NOT write out any changes to disk
+     * @param PEAR_PackageFile_v2
+     * @param string file contents
+     * @param string the eventual final file location (informational only)
+     * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+     *         (use $this->throwError), otherwise return the new contents
+     * @abstract
+     */
+    function startSession($pkg, $contents, $dest)
+    {
+    }
+
+    /**
+     * This method is used to process each of the tasks for a particular multiple class
+     * type.  Simple tasks need not implement this method.
+     * @param array an array of tasks
+     * @access protected
+     * @static
+     * @abstract
+     */
+    function run($tasks)
+    {
+    }
+
+    /**
+     * @static
+     * @final
+     */
+    function hasPostinstallTasks()
+    {
+        return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']);
+    }
+
+    /**
+     * @static
+     * @final
+     */
+     function runPostinstallTasks()
+     {
+         foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) {
+             $err = call_user_func(array($class, 'run'),
+                  $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]);
+             if ($err) {
+                 return PEAR_Task_Common::throwError($err);
+             }
+         }
+         unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']);
+    }
+
+    /**
+     * Determines whether a role is a script
+     * @return bool
+     */
+    function isScript()
+    {
+        return $this->type == 'script';
+    }
+
+    function throwError($msg, $code = -1)
+    {
+        include_once 'PEAR.php';
+        return PEAR::raiseError($msg, $code);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php
new file mode 100644 (file)
index 0000000..e43ecca
--- /dev/null
@@ -0,0 +1,323 @@
+<?php
+/**
+ * <tasks:postinstallscript>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Postinstallscript.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the postinstallscript file task.
+ *
+ * Note that post-install scripts are handled separately from installation, by the
+ * "pear run-scripts" command
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Postinstallscript extends PEAR_Task_Common
+{
+    var $type = 'script';
+    var $_class;
+    var $_params;
+    var $_obj;
+    /**
+     *
+     * @var PEAR_PackageFile_v2
+     */
+    var $_pkg;
+    var $_contents;
+    var $phase = PEAR_TASK_INSTALL;
+
+    /**
+     * Validate the raw xml at parsing-time.
+     *
+     * This also attempts to validate the script to make sure it meets the criteria
+     * for a post-install script
+     * @param PEAR_PackageFile_v2
+     * @param array The XML contents of the <postinstallscript> tag
+     * @param PEAR_Config
+     * @param array the entire parsed <file> tag
+     * @static
+     */
+    function validateXml($pkg, $xml, $config, $fileXml)
+    {
+        if ($fileXml['role'] != 'php') {
+            return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+            $fileXml['name'] . '" must be role="php"');
+        }
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $file = $pkg->getFileContents($fileXml['name']);
+        if (PEAR::isError($file)) {
+            PEAR::popErrorHandling();
+            return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                $fileXml['name'] . '" is not valid: ' .
+                $file->getMessage());
+        } elseif ($file === null) {
+            return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                $fileXml['name'] . '" could not be retrieved for processing!');
+        } else {
+            $analysis = $pkg->analyzeSourceCode($file, true);
+            if (!$analysis) {
+                PEAR::popErrorHandling();
+                $warnings = '';
+                foreach ($pkg->getValidationWarnings() as $warn) {
+                    $warnings .= $warn['message'] . "\n";
+                }
+                return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' .
+                    $fileXml['name'] . '" failed: ' . $warnings);
+            }
+            if (count($analysis['declared_classes']) != 1) {
+                PEAR::popErrorHandling();
+                return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                    $fileXml['name'] . '" must declare exactly 1 class');
+            }
+            $class = $analysis['declared_classes'][0];
+            if ($class != str_replace(array('/', '.php'), array('_', ''),
+                  $fileXml['name']) . '_postinstall') {
+                PEAR::popErrorHandling();
+                return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                    $fileXml['name'] . '" class "' . $class . '" must be named "' .
+                    str_replace(array('/', '.php'), array('_', ''),
+                    $fileXml['name']) . '_postinstall"');
+            }
+            if (!isset($analysis['declared_methods'][$class])) {
+                PEAR::popErrorHandling();
+                return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                    $fileXml['name'] . '" must declare methods init() and run()');
+            }
+            $methods = array('init' => 0, 'run' => 1);
+            foreach ($analysis['declared_methods'][$class] as $method) {
+                if (isset($methods[$method])) {
+                    unset($methods[$method]);
+                }
+            }
+            if (count($methods)) {
+                PEAR::popErrorHandling();
+                return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                    $fileXml['name'] . '" must declare methods init() and run()');
+            }
+        }
+        PEAR::popErrorHandling();
+        $definedparams = array();
+        $tasksNamespace = $pkg->getTasksNs() . ':';
+        if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) {
+            // in order to support the older betas, which did not expect internal tags
+            // to also use the namespace
+            $tasksNamespace = '';
+        }
+        if (isset($xml[$tasksNamespace . 'paramgroup'])) {
+            $params = $xml[$tasksNamespace . 'paramgroup'];
+            if (!is_array($params) || !isset($params[0])) {
+                $params = array($params);
+            }
+            foreach ($params as $param) {
+                if (!isset($param[$tasksNamespace . 'id'])) {
+                    return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                        $fileXml['name'] . '" <paramgroup> must have ' .
+                        'an ' . $tasksNamespace . 'id> tag');
+                }
+                if (isset($param[$tasksNamespace . 'name'])) {
+                    if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" ' . $tasksNamespace .
+                            'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+                            '" parameter "' . $param[$tasksNamespace . 'name'] .
+                            '" has not been previously defined');
+                    }
+                    if (!isset($param[$tasksNamespace . 'conditiontype'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" ' . $tasksNamespace .
+                            'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+                            '" must have a ' . $tasksNamespace .
+                            'conditiontype> tag containing either "=", ' .
+                            '"!=", or "preg_match"');
+                    }
+                    if (!in_array($param[$tasksNamespace . 'conditiontype'],
+                          array('=', '!=', 'preg_match'))) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" ' . $tasksNamespace .
+                            'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+                            '" must have a ' . $tasksNamespace .
+                            'conditiontype> tag containing either "=", ' .
+                            '"!=", or "preg_match"');
+                    }
+                    if (!isset($param[$tasksNamespace . 'value'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" ' . $tasksNamespace .
+                            'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+                            '" must have a ' . $tasksNamespace .
+                            'value> tag containing expected parameter value');
+                    }
+                }
+                if (isset($param[$tasksNamespace . 'instructions'])) {
+                    if (!is_string($param[$tasksNamespace . 'instructions'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" ' . $tasksNamespace .
+                            'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+                            '" ' . $tasksNamespace . 'instructions> must be simple text');
+                    }
+                }
+                if (!isset($param[$tasksNamespace . 'param'])) {
+                    continue; // <param> is no longer required
+                }
+                $subparams = $param[$tasksNamespace . 'param'];
+                if (!is_array($subparams) || !isset($subparams[0])) {
+                    $subparams = array($subparams);
+                }
+                foreach ($subparams as $subparam) {
+                    if (!isset($subparam[$tasksNamespace . 'name'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" parameter for ' .
+                            $tasksNamespace . 'paramgroup> id "' .
+                            $param[$tasksNamespace . 'id'] . '" must have ' .
+                            'a ' . $tasksNamespace . 'name> tag');
+                    }
+                    if (!preg_match('/[a-zA-Z0-9]+/',
+                          $subparam[$tasksNamespace . 'name'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" parameter "' .
+                            $subparam[$tasksNamespace . 'name'] .
+                            '" for ' . $tasksNamespace . 'paramgroup> id "' .
+                            $param[$tasksNamespace . 'id'] .
+                            '" is not a valid name.  Must contain only alphanumeric characters');
+                    }
+                    if (!isset($subparam[$tasksNamespace . 'prompt'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" parameter "' .
+                            $subparam[$tasksNamespace . 'name'] .
+                            '" for ' . $tasksNamespace . 'paramgroup> id "' .
+                            $param[$tasksNamespace . 'id'] .
+                            '" must have a ' . $tasksNamespace . 'prompt> tag');
+                    }
+                    if (!isset($subparam[$tasksNamespace . 'type'])) {
+                        return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+                            $fileXml['name'] . '" parameter "' .
+                            $subparam[$tasksNamespace . 'name'] .
+                            '" for ' . $tasksNamespace . 'paramgroup> id "' .
+                            $param[$tasksNamespace . 'id'] .
+                            '" must have a ' . $tasksNamespace . 'type> tag');
+                    }
+                    $definedparams[] = $param[$tasksNamespace . 'id'] . '::' .
+                    $subparam[$tasksNamespace . 'name'];
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Initialize a task instance with the parameters
+     * @param array raw, parsed xml
+     * @param array attributes from the <file> tag containing this task
+     * @param string|null last installed version of this package, if any (useful for upgrades)
+     */
+    function init($xml, $fileattribs, $lastversion)
+    {
+        $this->_class = str_replace('/', '_', $fileattribs['name']);
+        $this->_filename = $fileattribs['name'];
+        $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall';
+        $this->_params = $xml;
+        $this->_lastversion = $lastversion;
+    }
+
+    /**
+     * Strip the tasks: namespace from internal params
+     *
+     * @access private
+     */
+    function _stripNamespace($params = null)
+    {
+        if ($params === null) {
+            $params = array();
+            if (!is_array($this->_params)) {
+                return;
+            }
+            foreach ($this->_params as $i => $param) {
+                if (is_array($param)) {
+                    $param = $this->_stripNamespace($param);
+                }
+                $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param;
+            }
+            $this->_params = $params;
+        } else {
+            $newparams = array();
+            foreach ($params as $i => $param) {
+                if (is_array($param)) {
+                    $param = $this->_stripNamespace($param);
+                }
+                $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param;
+            }
+            return $newparams;
+        }
+    }
+
+    /**
+     * Unlike other tasks, the installed file name is passed in instead of the file contents,
+     * because this task is handled post-installation
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param string file name
+     * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail
+     *         (use $this->throwError)
+     */
+    function startSession($pkg, $contents)
+    {
+        if ($this->installphase != PEAR_TASK_INSTALL) {
+            return false;
+        }
+        // remove the tasks: namespace if present
+        $this->_pkg = $pkg;
+        $this->_stripNamespace();
+        $this->logger->log(0, 'Including external post-installation script "' .
+            $contents . '" - any errors are in this script');
+        include_once $contents;
+        if (class_exists($this->_class)) {
+            $this->logger->log(0, 'Inclusion succeeded');
+        } else {
+            return $this->throwError('init of post-install script class "' . $this->_class
+                . '" failed');
+        }
+        $this->_obj = new $this->_class;
+        $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"');
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $res = $this->_obj->init($this->config, $pkg, $this->_lastversion);
+        PEAR::popErrorHandling();
+        if ($res) {
+            $this->logger->log(0, 'init succeeded');
+        } else {
+            return $this->throwError('init of post-install script "' . $this->_class .
+                '->init()" failed');
+        }
+        $this->_contents = $contents;
+        return true;
+    }
+
+    /**
+     * No longer used
+     * @see PEAR_PackageFile_v2::runPostinstallScripts()
+     * @param array an array of tasks
+     * @param string install or upgrade
+     * @access protected
+     * @static
+     */
+    function run()
+    {
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php
new file mode 100644 (file)
index 0000000..8f358bf
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+/**
+ * <tasks:postinstallscript> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Postinstallscript.php';
+/**
+ * Abstracts the postinstallscript file task xml.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript
+{
+    /**
+     * parent package file object
+     *
+     * @var PEAR_PackageFile_v2_rw
+     */
+    var $_pkg;
+    /**
+     * Enter description here...
+     *
+     * @param PEAR_PackageFile_v2_rw $pkg
+     * @param PEAR_Config $config
+     * @param PEAR_Frontend $logger
+     * @param array $fileXml
+     * @return PEAR_Task_Postinstallscript_rw
+     */
+    function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml)
+    {
+        parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+        $this->_contents = $fileXml;
+        $this->_pkg = &$pkg;
+        $this->_params = array();
+    }
+
+    function validate()
+    {
+        return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents);
+    }
+
+    function getName()
+    {
+        return 'postinstallscript';
+    }
+
+    /**
+     * add a simple <paramgroup> to the post-install script
+     *
+     * Order is significant, so call this method in the same
+     * sequence the users should see the paramgroups.  The $params
+     * parameter should either be the result of a call to {@link getParam()}
+     * or an array of calls to getParam().
+     *
+     * Use {@link addConditionTypeGroup()} to add a <paramgroup> containing
+     * a <conditiontype> tag
+     * @param string $id <paramgroup> id as seen by the script
+     * @param array|false $params array of getParam() calls, or false for no params
+     * @param string|false $instructions
+     */
+    function addParamGroup($id, $params = false, $instructions = false)
+    {
+        if ($params && isset($params[0]) && !isset($params[1])) {
+            $params = $params[0];
+        }
+        $stuff =
+            array(
+                $this->_pkg->getTasksNs() . ':id' => $id,
+            );
+        if ($instructions) {
+            $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions;
+        }
+        if ($params) {
+            $stuff[$this->_pkg->getTasksNs() . ':param'] = $params;
+        }
+        $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff;
+    }
+
+    /**
+     * add a complex <paramgroup> to the post-install script with conditions
+     *
+     * This inserts a <paramgroup> with
+     *
+     * Order is significant, so call this method in the same
+     * sequence the users should see the paramgroups.  The $params
+     * parameter should either be the result of a call to {@link getParam()}
+     * or an array of calls to getParam().
+     *
+     * Use {@link addParamGroup()} to add a simple <paramgroup>
+     *
+     * @param string $id <paramgroup> id as seen by the script
+     * @param string $oldgroup <paramgroup> id of the section referenced by
+     *                         <conditiontype>
+     * @param string $param name of the <param> from the older section referenced
+     *                      by <contitiontype>
+     * @param string $value value to match of the parameter
+     * @param string $conditiontype one of '=', '!=', 'preg_match'
+     * @param array|false $params array of getParam() calls, or false for no params
+     * @param string|false $instructions
+     */
+    function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=',
+                                   $params = false, $instructions = false)
+    {
+        if ($params && isset($params[0]) && !isset($params[1])) {
+            $params = $params[0];
+        }
+        $stuff = array(
+            $this->_pkg->getTasksNs() . ':id' => $id,
+        );
+        if ($instructions) {
+            $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions;
+        }
+        $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param;
+        $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype;
+        $stuff[$this->_pkg->getTasksNs() . ':value'] = $value;
+        if ($params) {
+            $stuff[$this->_pkg->getTasksNs() . ':param'] = $params;
+        }
+        $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff;
+    }
+
+    function getXml()
+    {
+        return $this->_params;
+    }
+
+    /**
+     * Use to set up a param tag for use in creating a paramgroup
+     * @static
+     */
+    function getParam($name, $prompt, $type = 'string', $default = null)
+    {
+        if ($default !== null) {
+            return
+            array(
+                $this->_pkg->getTasksNs() . ':name' => $name,
+                $this->_pkg->getTasksNs() . ':prompt' => $prompt,
+                $this->_pkg->getTasksNs() . ':type' => $type,
+                $this->_pkg->getTasksNs() . ':default' => $default
+            );
+        }
+        return
+            array(
+                $this->_pkg->getTasksNs() . ':name' => $name,
+                $this->_pkg->getTasksNs() . ':prompt' => $prompt,
+                $this->_pkg->getTasksNs() . ':type' => $type,
+            );
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Replace.php b/WEB-INF/lib/pear/PEAR/Task/Replace.php
new file mode 100644 (file)
index 0000000..376df64
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+/**
+ * <tasks:replace>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Replace.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the replace file task.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Replace extends PEAR_Task_Common
+{
+    var $type = 'simple';
+    var $phase = PEAR_TASK_PACKAGEANDINSTALL;
+    var $_replacements;
+
+    /**
+     * Validate the raw xml at parsing-time.
+     * @param PEAR_PackageFile_v2
+     * @param array raw, parsed xml
+     * @param PEAR_Config
+     * @static
+     */
+    function validateXml($pkg, $xml, $config, $fileXml)
+    {
+        if (!isset($xml['attribs'])) {
+            return array(PEAR_TASK_ERROR_NOATTRIBS);
+        }
+        if (!isset($xml['attribs']['type'])) {
+            return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type');
+        }
+        if (!isset($xml['attribs']['to'])) {
+            return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to');
+        }
+        if (!isset($xml['attribs']['from'])) {
+            return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from');
+        }
+        if ($xml['attribs']['type'] == 'pear-config') {
+            if (!in_array($xml['attribs']['to'], $config->getKeys())) {
+                return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+                    $config->getKeys());
+            }
+        } elseif ($xml['attribs']['type'] == 'php-const') {
+            if (defined($xml['attribs']['to'])) {
+                return true;
+            } else {
+                return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+                    array('valid PHP constant'));
+            }
+        } elseif ($xml['attribs']['type'] == 'package-info') {
+            if (in_array($xml['attribs']['to'],
+                array('name', 'summary', 'channel', 'notes', 'extends', 'description',
+                    'release_notes', 'license', 'release-license', 'license-uri',
+                    'version', 'api-version', 'state', 'api-state', 'release_date',
+                    'date', 'time'))) {
+                return true;
+            } else {
+                return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+                    array('name', 'summary', 'channel', 'notes', 'extends', 'description',
+                    'release_notes', 'license', 'release-license', 'license-uri',
+                    'version', 'api-version', 'state', 'api-state', 'release_date',
+                    'date', 'time'));
+            }
+        } else {
+            return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'],
+                array('pear-config', 'package-info', 'php-const'));
+        }
+        return true;
+    }
+
+    /**
+     * Initialize a task instance with the parameters
+     * @param array raw, parsed xml
+     * @param unused
+     */
+    function init($xml, $attribs)
+    {
+        $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml;
+    }
+
+    /**
+     * Do a package.xml 1.0 replacement, with additional package-info fields available
+     *
+     * See validateXml() source for the complete list of allowed fields
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param string file contents
+     * @param string the eventual final file location (informational only)
+     * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+     *         (use $this->throwError), otherwise return the new contents
+     */
+    function startSession($pkg, $contents, $dest)
+    {
+        $subst_from = $subst_to = array();
+        foreach ($this->_replacements as $a) {
+            $a = $a['attribs'];
+            $to = '';
+            if ($a['type'] == 'pear-config') {
+                if ($this->installphase == PEAR_TASK_PACKAGE) {
+                    return false;
+                }
+                if ($a['to'] == 'master_server') {
+                    $chan = $this->registry->getChannel($pkg->getChannel());
+                    if (!PEAR::isError($chan)) {
+                        $to = $chan->getServer();
+                    } else {
+                        $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]");
+                        return false;
+                    }
+                } else {
+                    if ($this->config->isDefinedLayer('ftp')) {
+                        // try the remote config file first
+                        $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel());
+                        if (is_null($to)) {
+                            // then default to local
+                            $to = $this->config->get($a['to'], null, $pkg->getChannel());
+                        }
+                    } else {
+                        $to = $this->config->get($a['to'], null, $pkg->getChannel());
+                    }
+                }
+                if (is_null($to)) {
+                    $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]");
+                    return false;
+                }
+            } elseif ($a['type'] == 'php-const') {
+                if ($this->installphase == PEAR_TASK_PACKAGE) {
+                    return false;
+                }
+                if (defined($a['to'])) {
+                    $to = constant($a['to']);
+                } else {
+                    $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]");
+                    return false;
+                }
+            } else {
+                if ($t = $pkg->packageInfo($a['to'])) {
+                    $to = $t;
+                } else {
+                    $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]");
+                    return false;
+                }
+            }
+            if (!is_null($to)) {
+                $subst_from[] = $a['from'];
+                $subst_to[] = $to;
+            }
+        }
+        $this->logger->log(3, "doing " . sizeof($subst_from) .
+            " substitution(s) for $dest");
+        if (sizeof($subst_from)) {
+            $contents = str_replace($subst_from, $subst_to, $contents);
+        }
+        return $contents;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php b/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php
new file mode 100644 (file)
index 0000000..32dad58
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * <tasks:replace> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Replace.php';
+/**
+ * Abstracts the replace task xml.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Replace_rw extends PEAR_Task_Replace
+{
+    function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml)
+    {
+        parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+        $this->_contents = $fileXml;
+        $this->_pkg = &$pkg;
+        $this->_params = array();
+    }
+
+    function validate()
+    {
+        return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents);
+    }
+
+    function setInfo($from, $to, $type)
+    {
+        $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type));
+    }
+
+    function getName()
+    {
+        return 'replace';
+    }
+
+    function getXml()
+    {
+        return $this->_params;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Unixeol.php b/WEB-INF/lib/pear/PEAR/Task/Unixeol.php
new file mode 100644 (file)
index 0000000..89ca81b
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * <tasks:unixeol>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Unixeol.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the unix line endings file task.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Unixeol extends PEAR_Task_Common
+{
+    var $type = 'simple';
+    var $phase = PEAR_TASK_PACKAGE;
+    var $_replacements;
+
+    /**
+     * Validate the raw xml at parsing-time.
+     * @param PEAR_PackageFile_v2
+     * @param array raw, parsed xml
+     * @param PEAR_Config
+     * @static
+     */
+    function validateXml($pkg, $xml, $config, $fileXml)
+    {
+        if ($xml != '') {
+            return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed');
+        }
+        return true;
+    }
+
+    /**
+     * Initialize a task instance with the parameters
+     * @param array raw, parsed xml
+     * @param unused
+     */
+    function init($xml, $attribs)
+    {
+    }
+
+    /**
+     * Replace all line endings with line endings customized for the current OS
+     *
+     * See validateXml() source for the complete list of allowed fields
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param string file contents
+     * @param string the eventual final file location (informational only)
+     * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+     *         (use $this->throwError), otherwise return the new contents
+     */
+    function startSession($pkg, $contents, $dest)
+    {
+        $this->logger->log(3, "replacing all line endings with \\n in $dest");
+        return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php b/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php
new file mode 100644 (file)
index 0000000..b2ae5fa
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * <tasks:unixeol> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Unixeol.php';
+/**
+ * Abstracts the unixeol task xml.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol
+{
+    function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml)
+    {
+        parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+        $this->_contents = $fileXml;
+        $this->_pkg = &$pkg;
+        $this->_params = array();
+    }
+
+    function validate()
+    {
+        return true;
+    }
+
+    function getName()
+    {
+        return 'unixeol';
+    }
+
+    function getXml()
+    {
+        return '';
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Windowseol.php b/WEB-INF/lib/pear/PEAR/Task/Windowseol.php
new file mode 100644 (file)
index 0000000..8ba4171
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * <tasks:windowseol>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Windowseol.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the windows line endsings file task.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Windowseol extends PEAR_Task_Common
+{
+    var $type = 'simple';
+    var $phase = PEAR_TASK_PACKAGE;
+    var $_replacements;
+
+    /**
+     * Validate the raw xml at parsing-time.
+     * @param PEAR_PackageFile_v2
+     * @param array raw, parsed xml
+     * @param PEAR_Config
+     * @static
+     */
+    function validateXml($pkg, $xml, $config, $fileXml)
+    {
+        if ($xml != '') {
+            return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed');
+        }
+        return true;
+    }
+
+    /**
+     * Initialize a task instance with the parameters
+     * @param array raw, parsed xml
+     * @param unused
+     */
+    function init($xml, $attribs)
+    {
+    }
+
+    /**
+     * Replace all line endings with windows line endings
+     *
+     * See validateXml() source for the complete list of allowed fields
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @param string file contents
+     * @param string the eventual final file location (informational only)
+     * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+     *         (use $this->throwError), otherwise return the new contents
+     */
+    function startSession($pkg, $contents, $dest)
+    {
+        $this->logger->log(3, "replacing all line endings with \\r\\n in $dest");
+        return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php b/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php
new file mode 100644 (file)
index 0000000..f0f1149
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * <tasks:windowseol> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Windowseol.php';
+/**
+ * Abstracts the windowseol task xml.
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol
+{
+    function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml)
+    {
+        parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+        $this->_contents = $fileXml;
+        $this->_pkg = &$pkg;
+        $this->_params = array();
+    }
+
+    function validate()
+    {
+        return true;
+    }
+
+    function getName()
+    {
+        return 'windowseol';
+    }
+
+    function getXml()
+    {
+        return '';
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Validate.php b/WEB-INF/lib/pear/PEAR/Validate.php
new file mode 100644 (file)
index 0000000..176560b
--- /dev/null
@@ -0,0 +1,629 @@
+<?php
+/**
+ * PEAR_Validate
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Validate.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+/**#@+
+ * Constants for install stage
+ */
+define('PEAR_VALIDATE_INSTALLING', 1);
+define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others
+define('PEAR_VALIDATE_NORMAL', 3);
+define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others
+define('PEAR_VALIDATE_PACKAGING', 7);
+/**#@-*/
+require_once 'PEAR/Common.php';
+require_once 'PEAR/Validator/PECL.php';
+
+/**
+ * Validation class for package.xml - channel-level advanced validation
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a1
+ */
+class PEAR_Validate
+{
+    var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG;
+    /**
+     * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     */
+    var $_packagexml;
+    /**
+     * @var int one of the PEAR_VALIDATE_* constants
+     */
+    var $_state = PEAR_VALIDATE_NORMAL;
+    /**
+     * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same)
+     * @var array
+     * @access private
+     */
+    var $_failures = array('error' => array(), 'warning' => array());
+
+    /**
+     * Override this method to handle validation of normal package names
+     * @param string
+     * @return bool
+     * @access protected
+     */
+    function _validPackageName($name)
+    {
+        return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name);
+    }
+
+    /**
+     * @param string package name to validate
+     * @param string name of channel-specific validation package
+     * @final
+     */
+    function validPackageName($name, $validatepackagename = false)
+    {
+        if ($validatepackagename) {
+            if (strtolower($name) == strtolower($validatepackagename)) {
+                return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name);
+            }
+        }
+        return $this->_validPackageName($name);
+    }
+
+    /**
+     * This validates a bundle name, and bundle names must conform
+     * to the PEAR naming convention, so the method is final and static.
+     * @param string
+     * @final
+     * @static
+     */
+    function validGroupName($name)
+    {
+        return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name);
+    }
+
+    /**
+     * Determine whether $state represents a valid stability level
+     * @param string
+     * @return bool
+     * @static
+     * @final
+     */
+    function validState($state)
+    {
+        return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable'));
+    }
+
+    /**
+     * Get a list of valid stability levels
+     * @return array
+     * @static
+     * @final
+     */
+    function getValidStates()
+    {
+        return array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+    }
+
+    /**
+     * Determine whether a version is a properly formatted version number that can be used
+     * by version_compare
+     * @param string
+     * @return bool
+     * @static
+     * @final
+     */
+    function validVersion($ver)
+    {
+        return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
+    }
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     */
+    function setPackageFile(&$pf)
+    {
+        $this->_packagexml = &$pf;
+    }
+
+    /**
+     * @access private
+     */
+    function _addFailure($field, $reason)
+    {
+        $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason);
+    }
+
+    /**
+     * @access private
+     */
+    function _addWarning($field, $reason)
+    {
+        $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason);
+    }
+
+    function getFailures()
+    {
+        $failures = $this->_failures;
+        $this->_failures = array('warnings' => array(), 'errors' => array());
+        return $failures;
+    }
+
+    /**
+     * @param int one of the PEAR_VALIDATE_* constants
+     */
+    function validate($state = null)
+    {
+        if (!isset($this->_packagexml)) {
+            return false;
+        }
+        if ($state !== null) {
+            $this->_state = $state;
+        }
+        $this->_failures = array('warnings' => array(), 'errors' => array());
+        $this->validatePackageName();
+        $this->validateVersion();
+        $this->validateMaintainers();
+        $this->validateDate();
+        $this->validateSummary();
+        $this->validateDescription();
+        $this->validateLicense();
+        $this->validateNotes();
+        if ($this->_packagexml->getPackagexmlVersion() == '1.0') {
+            $this->validateState();
+            $this->validateFilelist();
+        } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' ||
+                  $this->_packagexml->getPackagexmlVersion() == '2.1') {
+            $this->validateTime();
+            $this->validateStability();
+            $this->validateDeps();
+            $this->validateMainFilelist();
+            $this->validateReleaseFilelist();
+            //$this->validateGlobalTasks();
+            $this->validateChangelog();
+        }
+        return !((bool) count($this->_failures['errors']));
+    }
+
+    /**
+     * @access protected
+     */
+    function validatePackageName()
+    {
+        if ($this->_state == PEAR_VALIDATE_PACKAGING ||
+              $this->_state == PEAR_VALIDATE_NORMAL) {
+            if (($this->_packagexml->getPackagexmlVersion() == '2.0' ||
+                 $this->_packagexml->getPackagexmlVersion() == '2.1') &&
+                  $this->_packagexml->getExtends()) {
+                $version = $this->_packagexml->getVersion() . '';
+                $name = $this->_packagexml->getPackage();
+                $test = array_shift($a = explode('.', $version));
+                if ($test == '0') {
+                    return true;
+                }
+                $vlen = strlen($test);
+                $majver = substr($name, strlen($name) - $vlen);
+                while ($majver && !is_numeric($majver{0})) {
+                    $majver = substr($majver, 1);
+                }
+                if ($majver != $test) {
+                    $this->_addWarning('package', "package $name extends package " .
+                        $this->_packagexml->getExtends() . ' and so the name should ' .
+                        'have a postfix equal to the major version like "' .
+                        $this->_packagexml->getExtends() . $test . '"');
+                    return true;
+                } elseif (substr($name, 0, strlen($name) - $vlen) !=
+                            $this->_packagexml->getExtends()) {
+                    $this->_addWarning('package', "package $name extends package " .
+                        $this->_packagexml->getExtends() . ' and so the name must ' .
+                        'be an extension like "' . $this->_packagexml->getExtends() .
+                        $test . '"');
+                    return true;
+                }
+            }
+        }
+        if (!$this->validPackageName($this->_packagexml->getPackage())) {
+            $this->_addFailure('name', 'package name "' .
+                $this->_packagexml->getPackage() . '" is invalid');
+            return false;
+        }
+    }
+
+    /**
+     * @access protected
+     */
+    function validateVersion()
+    {
+        if ($this->_state != PEAR_VALIDATE_PACKAGING) {
+            if (!$this->validVersion($this->_packagexml->getVersion())) {
+                $this->_addFailure('version',
+                    'Invalid version number "' . $this->_packagexml->getVersion() . '"');
+            }
+            return false;
+        }
+        $version = $this->_packagexml->getVersion();
+        $versioncomponents = explode('.', $version);
+        if (count($versioncomponents) != 3) {
+            $this->_addWarning('version',
+                'A version number should have 3 decimals (x.y.z)');
+            return true;
+        }
+        $name = $this->_packagexml->getPackage();
+        // version must be based upon state
+        switch ($this->_packagexml->getState()) {
+            case 'snapshot' :
+                return true;
+            case 'devel' :
+                if ($versioncomponents[0] . 'a' == '0a') {
+                    return true;
+                }
+                if ($versioncomponents[0] == 0) {
+                    $versioncomponents[0] = '0';
+                    $this->_addWarning('version',
+                        'version "' . $version . '" should be "' .
+                        implode('.' ,$versioncomponents) . '"');
+                } else {
+                    $this->_addWarning('version',
+                        'packages with devel stability must be < version 1.0.0');
+                }
+                return true;
+            break;
+            case 'alpha' :
+            case 'beta' :
+                // check for a package that extends a package,
+                // like Foo and Foo2
+                if ($this->_state == PEAR_VALIDATE_PACKAGING) {
+                    if (substr($versioncomponents[2], 1, 2) == 'rc') {
+                        $this->_addFailure('version', 'Release Candidate versions ' .
+                            'must have capital RC, not lower-case rc');
+                        return false;
+                    }
+                }
+                if (!$this->_packagexml->getExtends()) {
+                    if ($versioncomponents[0] == '1') {
+                        if ($versioncomponents[2]{0} == '0') {
+                            if ($versioncomponents[2] == '0') {
+                                // version 1.*.0000
+                                $this->_addWarning('version',
+                                    'version 1.' . $versioncomponents[1] .
+                                        '.0 probably should not be alpha or beta');
+                                return true;
+                            } elseif (strlen($versioncomponents[2]) > 1) {
+                                // version 1.*.0RC1 or 1.*.0beta24 etc.
+                                return true;
+                            } else {
+                                // version 1.*.0
+                                $this->_addWarning('version',
+                                    'version 1.' . $versioncomponents[1] .
+                                        '.0 probably should not be alpha or beta');
+                                return true;
+                            }
+                        } else {
+                            $this->_addWarning('version',
+                                'bugfix versions (1.3.x where x > 0) probably should ' .
+                                'not be alpha or beta');
+                            return true;
+                        }
+                    } elseif ($versioncomponents[0] != '0') {
+                        $this->_addWarning('version',
+                            'major versions greater than 1 are not allowed for packages ' .
+                            'without an <extends> tag or an identical postfix (foo2 v2.0.0)');
+                        return true;
+                    }
+                    if ($versioncomponents[0] . 'a' == '0a') {
+                        return true;
+                    }
+                    if ($versioncomponents[0] == 0) {
+                        $versioncomponents[0] = '0';
+                        $this->_addWarning('version',
+                            'version "' . $version . '" should be "' .
+                            implode('.' ,$versioncomponents) . '"');
+                    }
+                } else {
+                    $vlen = strlen($versioncomponents[0] . '');
+                    $majver = substr($name, strlen($name) - $vlen);
+                    while ($majver && !is_numeric($majver{0})) {
+                        $majver = substr($majver, 1);
+                    }
+                    if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) {
+                        $this->_addWarning('version', 'first version number "' .
+                            $versioncomponents[0] . '" must match the postfix of ' .
+                            'package name "' . $name . '" (' .
+                            $majver . ')');
+                        return true;
+                    }
+                    if ($versioncomponents[0] == $majver) {
+                        if ($versioncomponents[2]{0} == '0') {
+                            if ($versioncomponents[2] == '0') {
+                                // version 2.*.0000
+                                $this->_addWarning('version',
+                                    "version $majver." . $versioncomponents[1] .
+                                        '.0 probably should not be alpha or beta');
+                                return false;
+                            } elseif (strlen($versioncomponents[2]) > 1) {
+                                // version 2.*.0RC1 or 2.*.0beta24 etc.
+                                return true;
+                            } else {
+                                // version 2.*.0
+                                $this->_addWarning('version',
+                                    "version $majver." . $versioncomponents[1] .
+                                        '.0 cannot be alpha or beta');
+                                return true;
+                            }
+                        } else {
+                            $this->_addWarning('version',
+                                "bugfix versions ($majver.x.y where y > 0) should " .
+                                'not be alpha or beta');
+                            return true;
+                        }
+                    } elseif ($versioncomponents[0] != '0') {
+                        $this->_addWarning('version',
+                            "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases");
+                        return true;
+                    }
+                    if ($versioncomponents[0] . 'a' == '0a') {
+                        return true;
+                    }
+                    if ($versioncomponents[0] == 0) {
+                        $versioncomponents[0] = '0';
+                        $this->_addWarning('version',
+                            'version "' . $version . '" should be "' .
+                            implode('.' ,$versioncomponents) . '"');
+                    }
+                }
+                return true;
+            break;
+            case 'stable' :
+                if ($versioncomponents[0] == '0') {
+                    $this->_addWarning('version', 'versions less than 1.0.0 cannot ' .
+                    'be stable');
+                    return true;
+                }
+                if (!is_numeric($versioncomponents[2])) {
+                    if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i',
+                          $versioncomponents[2])) {
+                        $this->_addWarning('version', 'version "' . $version . '" or any ' .
+                            'RC/beta/alpha version cannot be stable');
+                        return true;
+                    }
+                }
+                // check for a package that extends a package,
+                // like Foo and Foo2
+                if ($this->_packagexml->getExtends()) {
+                    $vlen = strlen($versioncomponents[0] . '');
+                    $majver = substr($name, strlen($name) - $vlen);
+                    while ($majver && !is_numeric($majver{0})) {
+                        $majver = substr($majver, 1);
+                    }
+                    if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) {
+                        $this->_addWarning('version', 'first version number "' .
+                            $versioncomponents[0] . '" must match the postfix of ' .
+                            'package name "' . $name . '" (' .
+                            $majver . ')');
+                        return true;
+                    }
+                } elseif ($versioncomponents[0] > 1) {
+                    $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' .
+                        '1 for any package that does not have an <extends> tag');
+                }
+                return true;
+            break;
+            default :
+                return false;
+            break;
+        }
+    }
+
+    /**
+     * @access protected
+     */
+    function validateMaintainers()
+    {
+        // maintainers can only be truly validated server-side for most channels
+        // but allow this customization for those who wish it
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateDate()
+    {
+        if ($this->_state == PEAR_VALIDATE_NORMAL ||
+              $this->_state == PEAR_VALIDATE_PACKAGING) {
+
+            if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/',
+                  $this->_packagexml->getDate(), $res) ||
+                  count($res) < 4
+                  || !checkdate($res[2], $res[3], $res[1])
+                ) {
+                $this->_addFailure('date', 'invalid release date "' .
+                    $this->_packagexml->getDate() . '"');
+                return false;
+            }
+
+            if ($this->_state == PEAR_VALIDATE_PACKAGING &&
+                  $this->_packagexml->getDate() != date('Y-m-d')) {
+                $this->_addWarning('date', 'Release Date "' .
+                    $this->_packagexml->getDate() . '" is not today');
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateTime()
+    {
+        if (!$this->_packagexml->getTime()) {
+            // default of no time value set
+            return true;
+        }
+
+        // packager automatically sets time, so only validate if pear validate is called
+        if ($this->_state = PEAR_VALIDATE_NORMAL) {
+            if (!preg_match('/\d\d:\d\d:\d\d/',
+                  $this->_packagexml->getTime())) {
+                $this->_addFailure('time', 'invalid release time "' .
+                    $this->_packagexml->getTime() . '"');
+                return false;
+            }
+
+            $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches);
+            if ($result === false || empty($matches)) {
+                $this->_addFailure('time', 'invalid release time "' .
+                    $this->_packagexml->getTime() . '"');
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateState()
+    {
+        // this is the closest to "final" php4 can get
+        if (!PEAR_Validate::validState($this->_packagexml->getState())) {
+            if (strtolower($this->_packagexml->getState() == 'rc')) {
+                $this->_addFailure('state', 'RC is not a state, it is a version ' .
+                    'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta');
+            }
+            $this->_addFailure('state', 'invalid release state "' .
+                $this->_packagexml->getState() . '", must be one of: ' .
+                implode(', ', PEAR_Validate::getValidStates()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateStability()
+    {
+        $ret = true;
+        $packagestability = $this->_packagexml->getState();
+        $apistability = $this->_packagexml->getState('api');
+        if (!PEAR_Validate::validState($packagestability)) {
+            $this->_addFailure('state', 'invalid release stability "' .
+                $this->_packagexml->getState() . '", must be one of: ' .
+                implode(', ', PEAR_Validate::getValidStates()));
+            $ret = false;
+        }
+        $apistates = PEAR_Validate::getValidStates();
+        array_shift($apistates); // snapshot is not allowed
+        if (!in_array($apistability, $apistates)) {
+            $this->_addFailure('state', 'invalid API stability "' .
+                $this->_packagexml->getState('api') . '", must be one of: ' .
+                implode(', ', $apistates));
+            $ret = false;
+        }
+        return $ret;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateSummary()
+    {
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateDescription()
+    {
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateLicense()
+    {
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateNotes()
+    {
+        return true;
+    }
+
+    /**
+     * for package.xml 2.0 only - channels can't use package.xml 1.0
+     * @access protected
+     */
+    function validateDependencies()
+    {
+        return true;
+    }
+
+    /**
+     * for package.xml 1.0 only
+     * @access private
+     */
+    function _validateFilelist()
+    {
+        return true; // placeholder for now
+    }
+
+    /**
+     * for package.xml 2.0 only
+     * @access protected
+     */
+    function validateMainFilelist()
+    {
+        return true; // placeholder for now
+    }
+
+    /**
+     * for package.xml 2.0 only
+     * @access protected
+     */
+    function validateReleaseFilelist()
+    {
+        return true; // placeholder for now
+    }
+
+    /**
+     * @access protected
+     */
+    function validateChangelog()
+    {
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateFilelist()
+    {
+        return true;
+    }
+
+    /**
+     * @access protected
+     */
+    function validateDeps()
+    {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/Validator/PECL.php b/WEB-INF/lib/pear/PEAR/Validator/PECL.php
new file mode 100644 (file)
index 0000000..89b951f
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Channel Validator for the pecl.php.net channel
+ *
+ * PHP 4 and PHP 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: PECL.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a5
+ */
+/**
+ * This is the parent class for all validators
+ */
+require_once 'PEAR/Validate.php';
+/**
+ * Channel Validator for the pecl.php.net channel
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.4.0a5
+ */
+class PEAR_Validator_PECL extends PEAR_Validate
+{
+    function validateVersion()
+    {
+        if ($this->_state == PEAR_VALIDATE_PACKAGING) {
+            $version = $this->_packagexml->getVersion();
+            $versioncomponents = explode('.', $version);
+            $last = array_pop($versioncomponents);
+            if (substr($last, 1, 2) == 'rc') {
+                $this->_addFailure('version', 'Release Candidate versions must have ' .
+                'upper-case RC, not lower-case rc');
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function validatePackageName()
+    {
+        $ret = parent::validatePackageName();
+        if ($this->_packagexml->getPackageType() == 'extsrc' ||
+              $this->_packagexml->getPackageType() == 'zendextsrc') {
+            if (strtolower($this->_packagexml->getPackage()) !=
+                  strtolower($this->_packagexml->getProvidesExtension())) {
+                $this->_addWarning('providesextension', 'package name "' .
+                    $this->_packagexml->getPackage() . '" is different from extension name "' .
+                    $this->_packagexml->getProvidesExtension() . '"');
+            }
+        }
+        return $ret;
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR/XMLParser.php b/WEB-INF/lib/pear/PEAR/XMLParser.php
new file mode 100644 (file)
index 0000000..7f091c1
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+/**
+ * PEAR_XMLParser
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Greg Beaver <cellog@php.net>
+ * @author     Stephan Schmidt (original XML_Unserializer code)
+ * @copyright  1997-2009 The Authors
+ * @license   http://opensource.org/licenses/bsd-license New BSD License
+ * @version    CVS: $Id: XMLParser.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.4.0a1
+ */
+
+/**
+ * Parser for any xml file
+ * @category  pear
+ * @package   PEAR
+ * @author    Greg Beaver <cellog@php.net>
+ * @author    Stephan Schmidt (original XML_Unserializer code)
+ * @copyright 1997-2009 The Authors
+ * @license   http://opensource.org/licenses/bsd-license New BSD License
+ * @version   Release: 1.9.4
+ * @link      http://pear.php.net/package/PEAR
+ * @since     Class available since Release 1.4.0a1
+ */
+class PEAR_XMLParser
+{
+    /**
+     * unserilialized data
+     * @var string $_serializedData
+     */
+    var $_unserializedData = null;
+
+    /**
+     * name of the root tag
+     * @var string $_root
+     */
+    var $_root = null;
+
+    /**
+     * stack for all data that is found
+     * @var array    $_dataStack
+     */
+    var $_dataStack = array();
+
+    /**
+     * stack for all values that are generated
+     * @var array    $_valStack
+     */
+    var $_valStack = array();
+
+    /**
+     * current tag depth
+     * @var int    $_depth
+     */
+    var $_depth = 0;
+
+    /**
+     * The XML encoding to use
+     * @var string $encoding
+     */
+    var $encoding = 'ISO-8859-1';
+
+    /**
+     * @return array
+     */
+    function getData()
+    {
+        return $this->_unserializedData;
+    }
+
+    /**
+     * @param string xml content
+     * @return true|PEAR_Error
+     */
+    function parse($data)
+    {
+        if (!extension_loaded('xml')) {
+            include_once 'PEAR.php';
+            return PEAR::raiseError("XML Extension not found", 1);
+        }
+        $this->_dataStack =  $this->_valStack = array();
+        $this->_depth = 0;
+
+        if (
+            strpos($data, 'encoding="UTF-8"')
+            || strpos($data, 'encoding="utf-8"')
+            || strpos($data, "encoding='UTF-8'")
+            || strpos($data, "encoding='utf-8'")
+        ) {
+            $this->encoding = 'UTF-8';
+        }
+
+        if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') {
+            $data = utf8_decode($data);
+            $this->encoding = 'ISO-8859-1';
+        }
+
+        $xp = xml_parser_create($this->encoding);
+        xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0);
+        xml_set_object($xp, $this);
+        xml_set_element_handler($xp, 'startHandler', 'endHandler');
+        xml_set_character_data_handler($xp, 'cdataHandler');
+        if (!xml_parse($xp, $data)) {
+            $msg = xml_error_string(xml_get_error_code($xp));
+            $line = xml_get_current_line_number($xp);
+            xml_parser_free($xp);
+            include_once 'PEAR.php';
+            return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2);
+        }
+        xml_parser_free($xp);
+        return true;
+    }
+
+    /**
+     * Start element handler for XML parser
+     *
+     * @access private
+     * @param  object $parser  XML parser object
+     * @param  string $element XML element
+     * @param  array  $attribs attributes of XML tag
+     * @return void
+     */
+    function startHandler($parser, $element, $attribs)
+    {
+        $this->_depth++;
+        $this->_dataStack[$this->_depth] = null;
+
+        $val = array(
+            'name'         => $element,
+            'value'        => null,
+            'type'         => 'string',
+            'childrenKeys' => array(),
+            'aggregKeys'   => array()
+       );
+
+        if (count($attribs) > 0) {
+            $val['children'] = array();
+            $val['type'] = 'array';
+            $val['children']['attribs'] = $attribs;
+        }
+
+        array_push($this->_valStack, $val);
+    }
+
+    /**
+     * post-process data
+     *
+     * @param string $data
+     * @param string $element element name
+     */
+    function postProcess($data, $element)
+    {
+        return trim($data);
+    }
+
+    /**
+     * End element handler for XML parser
+     *
+     * @access private
+     * @param  object XML parser object
+     * @param  string
+     * @return void
+     */
+    function endHandler($parser, $element)
+    {
+        $value = array_pop($this->_valStack);
+        $data  = $this->postProcess($this->_dataStack[$this->_depth], $element);
+
+        // adjust type of the value
+        switch (strtolower($value['type'])) {
+            // unserialize an array
+            case 'array':
+                if ($data !== '') {
+                    $value['children']['_content'] = $data;
+                }
+
+                $value['value'] = isset($value['children']) ? $value['children'] : array();
+                break;
+
+            /*
+             * unserialize a null value
+             */
+            case 'null':
+                $data = null;
+                break;
+
+            /*
+             * unserialize any scalar value
+             */
+            default:
+                settype($data, $value['type']);
+                $value['value'] = $data;
+                break;
+        }
+
+        $parent = array_pop($this->_valStack);
+        if ($parent === null) {
+            $this->_unserializedData = &$value['value'];
+            $this->_root = &$value['name'];
+            return true;
+        }
+
+        // parent has to be an array
+        if (!isset($parent['children']) || !is_array($parent['children'])) {
+            $parent['children'] = array();
+            if ($parent['type'] != 'array') {
+                $parent['type'] = 'array';
+            }
+        }
+
+        if (!empty($value['name'])) {
+            // there already has been a tag with this name
+            if (in_array($value['name'], $parent['childrenKeys'])) {
+                // no aggregate has been created for this tag
+                if (!in_array($value['name'], $parent['aggregKeys'])) {
+                    if (isset($parent['children'][$value['name']])) {
+                        $parent['children'][$value['name']] = array($parent['children'][$value['name']]);
+                    } else {
+                        $parent['children'][$value['name']] = array();
+                    }
+                    array_push($parent['aggregKeys'], $value['name']);
+                }
+                array_push($parent['children'][$value['name']], $value['value']);
+            } else {
+                $parent['children'][$value['name']] = &$value['value'];
+                array_push($parent['childrenKeys'], $value['name']);
+            }
+        } else {
+            array_push($parent['children'],$value['value']);
+        }
+        array_push($this->_valStack, $parent);
+
+        $this->_depth--;
+    }
+
+    /**
+     * Handler for character data
+     *
+     * @access private
+     * @param  object XML parser object
+     * @param  string CDATA
+     * @return void
+     */
+    function cdataHandler($parser, $cdata)
+    {
+        $this->_dataStack[$this->_depth] .= $cdata;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/PEAR5.php b/WEB-INF/lib/pear/PEAR5.php
new file mode 100644 (file)
index 0000000..4286067
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * This is only meant for PHP 5 to get rid of certain strict warning
+ * that doesn't get hidden since it's in the shutdown function
+ */
+class PEAR5
+{
+    /**
+    * If you have a class that's mostly/entirely static, and you need static
+    * properties, you can use this method to simulate them. Eg. in your method(s)
+    * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar');
+    * You MUST use a reference, or they will not persist!
+    *
+    * @access public
+    * @param  string $class  The calling classname, to prevent clashes
+    * @param  string $var    The variable to retrieve.
+    * @return mixed   A reference to the variable. If not set it will be
+    *                 auto initialised to NULL.
+    */
+    static function &getStaticProperty($class, $var)
+    {
+        static $properties;
+        if (!isset($properties[$class])) {
+            $properties[$class] = array();
+        }
+
+        if (!array_key_exists($var, $properties[$class])) {
+            $properties[$class][$var] = null;
+        }
+
+        return $properties[$class][$var];
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/README b/WEB-INF/lib/pear/README
new file mode 100644 (file)
index 0000000..2dabf3e
--- /dev/null
@@ -0,0 +1,32 @@
+PEAR - The PEAR Installer
+=========================
+
+What is the PEAR Installer?  What is PEAR?
+
+PEAR is the PHP Extension and Application Repository, found at
+http://pear.php.net.  The PEAR Installer is this software, which
+contains executable files and PHP code that is used to download
+and install PEAR code from pear.php.net.
+
+PEAR contains useful software libraries and applications such as
+MDB2 (database abstraction), HTML_QuickForm (HTML forms management),
+PhpDocumentor (auto-documentation generator), DB_DataObject
+(Data Access Abstraction), and many hundreds more.  Browse all
+available packages at http://pear.php.net, the list is constantly
+growing and updating to reflect improvements in the PHP language.
+
+DOCUMENTATION
+=============
+
+Documentation for PEAR can be found at http://pear.php.net/manual/.
+Installation documentation can be found in the INSTALL file included
+in this tarball.
+
+WARNING: DO NOT RUN PEAR WITHOUT INSTALLING IT - if you downloaded this
+tarball manually, you MUST install it.  Read the instructions in INSTALL
+prior to use.
+
+
+Happy PHPing, we hope PEAR will be a great tool for your development work!
+
+$Id: README 313023 2011-07-06 19:17:11Z dufuz $
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/System.php b/WEB-INF/lib/pear/System.php
new file mode 100644 (file)
index 0000000..c27d446
--- /dev/null
@@ -0,0 +1,629 @@
+<?php
+/**
+ * File/Directory manipulation
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    System
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: System.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR.php';
+require_once 'Console/Getopt.php';
+
+$GLOBALS['_System_temp_files'] = array();
+
+/**
+* System offers cross plattform compatible system functions
+*
+* Static functions for different operations. Should work under
+* Unix and Windows. The names and usage has been taken from its respectively
+* GNU commands. The functions will return (bool) false on error and will
+* trigger the error with the PHP trigger_error() function (you can silence
+* the error by prefixing a '@' sign after the function call, but this
+* is not recommended practice.  Instead use an error handler with
+* {@link set_error_handler()}).
+*
+* Documentation on this class you can find in:
+* http://pear.php.net/manual/
+*
+* Example usage:
+* if (!@System::rm('-r file1 dir1')) {
+*    print "could not delete file1 or dir1";
+* }
+*
+* In case you need to to pass file names with spaces,
+* pass the params as an array:
+*
+* System::rm(array('-r', $file1, $dir1));
+*
+* @category   pear
+* @package    System
+* @author     Tomas V.V. Cox <cox@idecnet.com>
+* @copyright  1997-2006 The PHP Group
+* @license    http://opensource.org/licenses/bsd-license.php New BSD License
+* @version    Release: 1.9.4
+* @link       http://pear.php.net/package/PEAR
+* @since      Class available since Release 0.1
+* @static
+*/
+class System
+{
+    /**
+     * returns the commandline arguments of a function
+     *
+     * @param    string  $argv           the commandline
+     * @param    string  $short_options  the allowed option short-tags
+     * @param    string  $long_options   the allowed option long-tags
+     * @return   array   the given options and there values
+     * @static
+     * @access private
+     */
+    function _parseArgs($argv, $short_options, $long_options = null)
+    {
+        if (!is_array($argv) && $argv !== null) {
+            // Find all items, quoted or otherwise
+            preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
+            $argv = $av[1];
+            foreach ($av[2] as $k => $a) {
+                if (empty($a)) {
+                    continue;
+                }
+                $argv[$k] = trim($a) ;
+            }
+        }
+        return Console_Getopt::getopt2($argv, $short_options, $long_options);
+    }
+
+    /**
+     * Output errors with PHP trigger_error(). You can silence the errors
+     * with prefixing a "@" sign to the function call: @System::mkdir(..);
+     *
+     * @param mixed $error a PEAR error or a string with the error message
+     * @return bool false
+     * @static
+     * @access private
+     */
+    function raiseError($error)
+    {
+        if (PEAR::isError($error)) {
+            $error = $error->getMessage();
+        }
+        trigger_error($error, E_USER_WARNING);
+        return false;
+    }
+
+    /**
+     * Creates a nested array representing the structure of a directory
+     *
+     * System::_dirToStruct('dir1', 0) =>
+     *   Array
+     *    (
+     *    [dirs] => Array
+     *        (
+     *            [0] => dir1
+     *        )
+     *
+     *    [files] => Array
+     *        (
+     *            [0] => dir1/file2
+     *            [1] => dir1/file3
+     *        )
+     *    )
+     * @param    string  $sPath      Name of the directory
+     * @param    integer $maxinst    max. deep of the lookup
+     * @param    integer $aktinst    starting deep of the lookup
+     * @param    bool    $silent     if true, do not emit errors.
+     * @return   array   the structure of the dir
+     * @static
+     * @access   private
+     */
+    function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
+    {
+        $struct = array('dirs' => array(), 'files' => array());
+        if (($dir = @opendir($sPath)) === false) {
+            if (!$silent) {
+                System::raiseError("Could not open dir $sPath");
+            }
+            return $struct; // XXX could not open error
+        }
+
+        $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
+        $list = array();
+        while (false !== ($file = readdir($dir))) {
+            if ($file != '.' && $file != '..') {
+                $list[] = $file;
+            }
+        }
+
+        closedir($dir);
+        natsort($list);
+        if ($aktinst < $maxinst || $maxinst == 0) {
+            foreach ($list as $val) {
+                $path = $sPath . DIRECTORY_SEPARATOR . $val;
+                if (is_dir($path) && !is_link($path)) {
+                    $tmp    = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
+                    $struct = array_merge_recursive($struct, $tmp);
+                } else {
+                    $struct['files'][] = $path;
+                }
+            }
+        }
+
+        return $struct;
+    }
+
+    /**
+     * Creates a nested array representing the structure of a directory and files
+     *
+     * @param    array $files Array listing files and dirs
+     * @return   array
+     * @static
+     * @see System::_dirToStruct()
+     */
+    function _multipleToStruct($files)
+    {
+        $struct = array('dirs' => array(), 'files' => array());
+        settype($files, 'array');
+        foreach ($files as $file) {
+            if (is_dir($file) && !is_link($file)) {
+                $tmp    = System::_dirToStruct($file, 0);
+                $struct = array_merge_recursive($tmp, $struct);
+            } else {
+                if (!in_array($file, $struct['files'])) {
+                    $struct['files'][] = $file;
+                }
+            }
+        }
+        return $struct;
+    }
+
+    /**
+     * The rm command for removing files.
+     * Supports multiple files and dirs and also recursive deletes
+     *
+     * @param    string  $args   the arguments for rm
+     * @return   mixed   PEAR_Error or true for success
+     * @static
+     * @access   public
+     */
+    function rm($args)
+    {
+        $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
+        if (PEAR::isError($opts)) {
+            return System::raiseError($opts);
+        }
+        foreach ($opts[0] as $opt) {
+            if ($opt[0] == 'r') {
+                $do_recursive = true;
+            }
+        }
+        $ret = true;
+        if (isset($do_recursive)) {
+            $struct = System::_multipleToStruct($opts[1]);
+            foreach ($struct['files'] as $file) {
+                if (!@unlink($file)) {
+                    $ret = false;
+                }
+            }
+
+            rsort($struct['dirs']);
+            foreach ($struct['dirs'] as $dir) {
+                if (!@rmdir($dir)) {
+                    $ret = false;
+                }
+            }
+        } else {
+            foreach ($opts[1] as $file) {
+                $delete = (is_dir($file)) ? 'rmdir' : 'unlink';
+                if (!@$delete($file)) {
+                    $ret = false;
+                }
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Make directories.
+     *
+     * The -p option will create parent directories
+     * @param    string  $args    the name of the director(y|ies) to create
+     * @return   bool    True for success
+     * @static
+     * @access   public
+     */
+    function mkDir($args)
+    {
+        $opts = System::_parseArgs($args, 'pm:');
+        if (PEAR::isError($opts)) {
+            return System::raiseError($opts);
+        }
+
+        $mode = 0777; // default mode
+        foreach ($opts[0] as $opt) {
+            if ($opt[0] == 'p') {
+                $create_parents = true;
+            } elseif ($opt[0] == 'm') {
+                // if the mode is clearly an octal number (starts with 0)
+                // convert it to decimal
+                if (strlen($opt[1]) && $opt[1]{0} == '0') {
+                    $opt[1] = octdec($opt[1]);
+                } else {
+                    // convert to int
+                    $opt[1] += 0;
+                }
+                $mode = $opt[1];
+            }
+        }
+
+        $ret = true;
+        if (isset($create_parents)) {
+            foreach ($opts[1] as $dir) {
+                $dirstack = array();
+                while ((!file_exists($dir) || !is_dir($dir)) &&
+                        $dir != DIRECTORY_SEPARATOR) {
+                    array_unshift($dirstack, $dir);
+                    $dir = dirname($dir);
+                }
+
+                while ($newdir = array_shift($dirstack)) {
+                    if (!is_writeable(dirname($newdir))) {
+                        $ret = false;
+                        break;
+                    }
+
+                    if (!mkdir($newdir, $mode)) {
+                        $ret = false;
+                    }
+                }
+            }
+        } else {
+            foreach($opts[1] as $dir) {
+                if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
+                    $ret = false;
+                }
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Concatenate files
+     *
+     * Usage:
+     * 1) $var = System::cat('sample.txt test.txt');
+     * 2) System::cat('sample.txt test.txt > final.txt');
+     * 3) System::cat('sample.txt test.txt >> final.txt');
+     *
+     * Note: as the class use fopen, urls should work also (test that)
+     *
+     * @param    string  $args   the arguments
+     * @return   boolean true on success
+     * @static
+     * @access   public
+     */
+    function &cat($args)
+    {
+        $ret = null;
+        $files = array();
+        if (!is_array($args)) {
+            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
+        }
+
+        $count_args = count($args);
+        for ($i = 0; $i < $count_args; $i++) {
+            if ($args[$i] == '>') {
+                $mode = 'wb';
+                $outputfile = $args[$i+1];
+                break;
+            } elseif ($args[$i] == '>>') {
+                $mode = 'ab+';
+                $outputfile = $args[$i+1];
+                break;
+            } else {
+                $files[] = $args[$i];
+            }
+        }
+        $outputfd = false;
+        if (isset($mode)) {
+            if (!$outputfd = fopen($outputfile, $mode)) {
+                $err = System::raiseError("Could not open $outputfile");
+                return $err;
+            }
+            $ret = true;
+        }
+        foreach ($files as $file) {
+            if (!$fd = fopen($file, 'r')) {
+                System::raiseError("Could not open $file");
+                continue;
+            }
+            while ($cont = fread($fd, 2048)) {
+                if (is_resource($outputfd)) {
+                    fwrite($outputfd, $cont);
+                } else {
+                    $ret .= $cont;
+                }
+            }
+            fclose($fd);
+        }
+        if (is_resource($outputfd)) {
+            fclose($outputfd);
+        }
+        return $ret;
+    }
+
+    /**
+     * Creates temporary files or directories. This function will remove
+     * the created files when the scripts finish its execution.
+     *
+     * Usage:
+     *   1) $tempfile = System::mktemp("prefix");
+     *   2) $tempdir  = System::mktemp("-d prefix");
+     *   3) $tempfile = System::mktemp();
+     *   4) $tempfile = System::mktemp("-t /var/tmp prefix");
+     *
+     * prefix -> The string that will be prepended to the temp name
+     *           (defaults to "tmp").
+     * -d     -> A temporary dir will be created instead of a file.
+     * -t     -> The target dir where the temporary (file|dir) will be created. If
+     *           this param is missing by default the env vars TMP on Windows or
+     *           TMPDIR in Unix will be used. If these vars are also missing
+     *           c:\windows\temp or /tmp will be used.
+     *
+     * @param   string  $args  The arguments
+     * @return  mixed   the full path of the created (file|dir) or false
+     * @see System::tmpdir()
+     * @static
+     * @access  public
+     */
+    function mktemp($args = null)
+    {
+        static $first_time = true;
+        $opts = System::_parseArgs($args, 't:d');
+        if (PEAR::isError($opts)) {
+            return System::raiseError($opts);
+        }
+
+        foreach ($opts[0] as $opt) {
+            if ($opt[0] == 'd') {
+                $tmp_is_dir = true;
+            } elseif ($opt[0] == 't') {
+                $tmpdir = $opt[1];
+            }
+        }
+
+        $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
+        if (!isset($tmpdir)) {
+            $tmpdir = System::tmpdir();
+        }
+
+        if (!System::mkDir(array('-p', $tmpdir))) {
+            return false;
+        }
+
+        $tmp = tempnam($tmpdir, $prefix);
+        if (isset($tmp_is_dir)) {
+            unlink($tmp); // be careful possible race condition here
+            if (!mkdir($tmp, 0700)) {
+                return System::raiseError("Unable to create temporary directory $tmpdir");
+            }
+        }
+
+        $GLOBALS['_System_temp_files'][] = $tmp;
+        if (isset($tmp_is_dir)) {
+            //$GLOBALS['_System_temp_files'][] = dirname($tmp);
+        }
+
+        if ($first_time) {
+            PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
+            $first_time = false;
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Remove temporary files created my mkTemp. This function is executed
+     * at script shutdown time
+     *
+     * @static
+     * @access private
+     */
+    function _removeTmpFiles()
+    {
+        if (count($GLOBALS['_System_temp_files'])) {
+            $delete = $GLOBALS['_System_temp_files'];
+            array_unshift($delete, '-r');
+            System::rm($delete);
+            $GLOBALS['_System_temp_files'] = array();
+        }
+    }
+
+    /**
+     * Get the path of the temporal directory set in the system
+     * by looking in its environments variables.
+     * Note: php.ini-recommended removes the "E" from the variables_order setting,
+     * making unavaible the $_ENV array, that s why we do tests with _ENV
+     *
+     * @static
+     * @return string The temporary directory on the system
+     */
+    function tmpdir()
+    {
+        if (OS_WINDOWS) {
+            if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
+                return $var;
+            }
+            if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
+                return $var;
+            }
+            if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
+                return $var;
+            }
+            if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
+                return $var;
+            }
+            return getenv('SystemRoot') . '\temp';
+        }
+        if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
+            return $var;
+        }
+        return realpath('/tmp');
+    }
+
+    /**
+     * The "which" command (show the full path of a command)
+     *
+     * @param string $program The command to search for
+     * @param mixed  $fallback Value to return if $program is not found
+     *
+     * @return mixed A string with the full path or false if not found
+     * @static
+     * @author Stig Bakken <ssb@php.net>
+     */
+    function which($program, $fallback = false)
+    {
+        // enforce API
+        if (!is_string($program) || '' == $program) {
+            return $fallback;
+        }
+
+        // full path given
+        if (basename($program) != $program) {
+            $path_elements[] = dirname($program);
+            $program = basename($program);
+        } else {
+            // Honor safe mode
+            if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) {
+                $path = getenv('PATH');
+                if (!$path) {
+                    $path = getenv('Path'); // some OSes are just stupid enough to do this
+                }
+            }
+            $path_elements = explode(PATH_SEPARATOR, $path);
+        }
+
+        if (OS_WINDOWS) {
+            $exe_suffixes = getenv('PATHEXT')
+                                ? explode(PATH_SEPARATOR, getenv('PATHEXT'))
+                                : array('.exe','.bat','.cmd','.com');
+            // allow passing a command.exe param
+            if (strpos($program, '.') !== false) {
+                array_unshift($exe_suffixes, '');
+            }
+            // is_executable() is not available on windows for PHP4
+            $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file';
+        } else {
+            $exe_suffixes = array('');
+            $pear_is_executable = 'is_executable';
+        }
+
+        foreach ($exe_suffixes as $suff) {
+            foreach ($path_elements as $dir) {
+                $file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
+                if (@$pear_is_executable($file)) {
+                    return $file;
+                }
+            }
+        }
+        return $fallback;
+    }
+
+    /**
+     * The "find" command
+     *
+     * Usage:
+     *
+     * System::find($dir);
+     * System::find("$dir -type d");
+     * System::find("$dir -type f");
+     * System::find("$dir -name *.php");
+     * System::find("$dir -name *.php -name *.htm*");
+     * System::find("$dir -maxdepth 1");
+     *
+     * Params implmented:
+     * $dir            -> Start the search at this directory
+     * -type d         -> return only directories
+     * -type f         -> return only files
+     * -maxdepth <n>   -> max depth of recursion
+     * -name <pattern> -> search pattern (bash style). Multiple -name param allowed
+     *
+     * @param  mixed Either array or string with the command line
+     * @return array Array of found files
+     * @static
+     *
+     */
+    function find($args)
+    {
+        if (!is_array($args)) {
+            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
+        }
+        $dir = realpath(array_shift($args));
+        if (!$dir) {
+            return array();
+        }
+        $patterns = array();
+        $depth = 0;
+        $do_files = $do_dirs = true;
+        $args_count = count($args);
+        for ($i = 0; $i < $args_count; $i++) {
+            switch ($args[$i]) {
+                case '-type':
+                    if (in_array($args[$i+1], array('d', 'f'))) {
+                        if ($args[$i+1] == 'd') {
+                            $do_files = false;
+                        } else {
+                            $do_dirs = false;
+                        }
+                    }
+                    $i++;
+                    break;
+                case '-name':
+                    $name = preg_quote($args[$i+1], '#');
+                    // our magic characters ? and * have just been escaped,
+                    // so now we change the escaped versions to PCRE operators
+                    $name = strtr($name, array('\?' => '.', '\*' => '.*'));
+                    $patterns[] = '('.$name.')';
+                    $i++;
+                    break;
+                case '-maxdepth':
+                    $depth = $args[$i+1];
+                    break;
+            }
+        }
+        $path = System::_dirToStruct($dir, $depth, 0, true);
+        if ($do_files && $do_dirs) {
+            $files = array_merge($path['files'], $path['dirs']);
+        } elseif ($do_dirs) {
+            $files = $path['dirs'];
+        } else {
+            $files = $path['files'];
+        }
+        if (count($patterns)) {
+            $dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
+            $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
+            $ret = array();
+            $files_count = count($files);
+            for ($i = 0; $i < $files_count; $i++) {
+                // only search in the part of the file below the current directory
+                $filepart = basename($files[$i]);
+                if (preg_match($pattern, $filepart)) {
+                    $ret[] = $files[$i];
+                }
+            }
+            return $ret;
+        }
+        return $files;
+    }
+}
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/package.dtd b/WEB-INF/lib/pear/package.dtd
new file mode 100644 (file)
index 0000000..5b471b7
--- /dev/null
@@ -0,0 +1,103 @@
+<!--
+     $Id: package.dtd,v 1.38 2005-11-12 02:23:07 cellog Exp $
+
+     This is the PEAR package description, version 1.0.
+     It should be used with the informal public identifier:
+
+         "-//PHP Group//DTD PEAR Package 1.0//EN//XML"
+
+     Copyright (c) 1997-2005 The PHP Group             
+
+     This source file is subject to version 3.00 of the PHP license,
+     that is bundled with this package in the file LICENSE, and is
+     available at through the world-wide-web at
+     http://www.php.net/license/3_0.txt. 
+     If you did not receive a copy of the PHP license and are unable to
+     obtain it through the world-wide-web, please send a note to
+     license@php.net so we can mail you a copy immediately.
+
+     Authors:
+         Stig S. Bakken <ssb@fast.no>
+         Gregory Beaver <cellog@php.net>
+
+  -->
+<!ENTITY % NUMBER "CDATA">
+<!ELEMENT package (name,summary,description,license?,maintainers,release,changelog?)>
+<!ATTLIST package type    (source|binary|empty) "empty"
+                  version CDATA                 #REQUIRED
+                  packagerversion CDATA         #IMPLIED>
+
+<!ELEMENT name (#PCDATA)>
+
+<!ELEMENT summary (#PCDATA)>
+
+<!ELEMENT license (#PCDATA)>
+
+<!ELEMENT description (#PCDATA)>
+
+<!ELEMENT maintainers (maintainer)+>
+
+<!ELEMENT maintainer (user|role|name|email)+>
+
+<!ELEMENT user (#PCDATA)>
+
+<!ELEMENT role (#PCDATA)>
+
+<!ELEMENT email (#PCDATA)>
+
+<!ELEMENT changelog (release)+>
+
+<!ELEMENT release (version,date,license,state,notes,warnings?,provides*,deps?,configureoptions?,filelist?)>
+
+<!ELEMENT version (#PCDATA)>
+
+<!ELEMENT date (#PCDATA)>
+
+<!ELEMENT state (#PCDATA)>
+
+<!ELEMENT notes (#PCDATA)>
+
+<!ELEMENT warnings (#PCDATA)>
+
+<!ELEMENT deps (dep*)>
+
+<!ELEMENT dep (#PCDATA)>
+<!ATTLIST dep type    (pkg|ext|php) #REQUIRED
+                              rel     (has|eq|lt|le|gt|ge)                          #IMPLIED
+                              version CDATA                                     #IMPLIED
+                              optional (yes|no)     'no'>
+
+<!ELEMENT configureoptions (configureoption)+>
+
+<!ELEMENT configureoption EMPTY>
+<!ATTLIST configureoption name CDATA #REQUIRED
+                                           default CDATA #IMPLIED
+                                           prompt CDATA #REQUIRED>
+
+<!ELEMENT provides EMPTY>
+<!ATTLIST provides type (ext|prog|class|function|feature|api) #REQUIRED
+                                name CDATA #REQUIRED
+                                extends CDATA #IMPLIED>
+<!ELEMENT filelist (dir|file)+>
+
+<!ELEMENT dir (dir|file)+>
+<!ATTLIST dir name           CDATA #REQUIRED
+              role           (php|ext|src|test|doc|data|script) 'php'
+              baseinstalldir CDATA #IMPLIED>
+
+<!ELEMENT file (replace*)>
+<!ATTLIST file role           (php|ext|src|test|doc|data|script) 'php'
+               debug          (na|on|off)        'na'
+               format         CDATA              #IMPLIED
+               baseinstalldir CDATA              #IMPLIED
+               platform       CDATA              #IMPLIED
+               md5sum         CDATA              #IMPLIED
+               name           CDATA              #REQUIRED
+               install-as     CDATA              #IMPLIED>
+
+<!ELEMENT replace EMPTY>
+<!ATTLIST replace type (php-const|pear-config|package-info) #REQUIRED
+                              from CDATA #REQUIRED
+                              to CDATA #REQUIRED>
+
+
diff --git a/WEB-INF/lib/pear/readme_pear_integration.txt b/WEB-INF/lib/pear/readme_pear_integration.txt
new file mode 100644 (file)
index 0000000..2886527
--- /dev/null
@@ -0,0 +1,77 @@
+PEAR integration notes.
+
+These notes explain how PEAR and its modules were integrated in Anuko Time Tracker project.
+
+PEAR packages can be downloaded from http://pear.php.net/packages.php 
+(click on the package group, then package name, then Download link).
+For example, for PEAR it will be http://pear.php.net/package/PEAR/download
+
+
+PEAR PACKAGE
+
+- Download PEAR package from http://pear.php.net/package/PEAR/download
+- Extract the files (what is in the deepest PEAR-1.9.1 folder) into WEB-INF/lib/pear/ folder in Time Tracker, so that you have something like:
+
+folders:
+
+OS
+PEAR
+scripts
+
+and files
+
+INSTALL
+LICENSE
+and others in your WEB-INF/lib/pear/ folder.
+
+
+DB PACKAGES
+
+NOTE: currently we are trying migrate from the old DB package to a newer MDB2 package.
+This is why we have (temporarily) both of them here.
+When the migration is finished the DB module will be removed.
+
+DB PACKAGE
+- Download DB module from http://pear.php.net/package/DB/download
+- From archive DB-1.7.14RC1.tgz take "DB.php" file and DB folder and put them into WEB-INF/lib/pear
+
+MDB2 PACKAGE
+- Download MDB2 module from http://pear.php.net/package/MDB2/download
+- From archive MDB2-2.5.0b3.tgz take "MDB2.php" file and MDB2 folder and put them into WEB-INF/lib/pear
+
+MDB2_Driver_mysql package
+- Download MDB2_Driver_mysql module from http://pear.php.net/package/MDB2_Driver_mysql/download
+- From archive MDB2_Driver_mysql_1.5.0b3.tgz merge the content of MDB2 folder with your WEB-INF/lib/pear/MDB2
+(a collection of mysql.php files organized in a directory structure).
+
+If you need Time Tracker to work with non mysql data sources install additional MDB2 drivers
+(similarly to MDB2_Driver_mysql).
+
+
+Net_SMTP PACKAGE
+
+- Download Net_SMTP module from http://pear.php.net/package/Net_SMTP/download
+- From archive Net_SMTP-1.4.2.tgz take the "SMTP.php" file and put it into WEB-INF/lib/pear/Net(you will need to create the Net folder).
+
+
+Net_Socket PACKAGE
+
+- Download Net_Socket module (dependency of Net_SMTP) from http://pear.php.net/package/Net_Socket/download
+- From archive Net_Socket-1.0.9.tgz take the "Socket.php" file and put it into WEB-INF/lib/pear/Net folder.
+
+
+Mail PACKAGE
+
+- Download Mail module from http://pear.php.net/package/Mail/download
+- From archive Mail-1.2.0.tgz take "Mail.php" file and Mail folder. Put them in WEB-INF/lib/pear folder.
+
+Now we have PEAR, and PEAR DB, PEAR MDB2, PEAR Net_SMTP, PEAR Mail modules installed.
+
+
+
+Add this line to any place in config.php.dist to set PHP include path for PEAR and its modules:
+
+set_include_path(realpath(dirname(__FILE__).'/lib/pear') . PATH_SEPARATOR . get_include_path());
+
+Note: it is important to include realpath(dirname(__FILE__).'/lib/pear') first to eliminate any potential
+PEAR compatibility issues for systems where another version of PEAR may be installed (like SME Server 8.0).
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/scripts/pear.bat b/WEB-INF/lib/pear/scripts/pear.bat
new file mode 100644 (file)
index 0000000..d7675bb
--- /dev/null
@@ -0,0 +1,111 @@
+@ECHO OFF
+
+REM ----------------------------------------------------------------------
+REM PHP version 5
+REM ----------------------------------------------------------------------
+REM Copyright (c) 1997-2010 The Authors
+REM ----------------------------------------------------------------------
+REM http://opensource.org/licenses/bsd-license.php New BSD License
+REM ----------------------------------------------------------------------
+REM  Authors:     Alexander Merz (alexmerz@php.net)
+REM ----------------------------------------------------------------------
+REM
+REM  Last updated 12/29/2004 ($Id$ is not replaced if the file is binary)
+
+REM change this lines to match the paths of your system
+REM -------------------
+
+
+REM Test to see if this is a raw pear.bat (uninstalled version)
+SET TMPTMPTMPTMPT=@includ
+SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@
+FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED)
+
+REM Check PEAR global ENV, set them if they do not exist
+IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@"
+IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@"
+IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@"
+
+GOTO :INSTALLED
+
+:NOTINSTALLED
+ECHO WARNING: This is a raw, uninstalled pear.bat
+
+REM Check to see if we can grab the directory of this file (Windows NT+)
+IF %~n0 == pear (
+FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" (
+SET "PHP_PEAR_PHP_BIN=%%~$PATH:x"
+echo Using PHP Executable "%PHP_PEAR_PHP_BIN%"
+"%PHP_PEAR_PHP_BIN%" -v
+GOTO :NEXTTEST
+))
+GOTO :FAILAUTODETECT
+:NEXTTEST
+IF "%PHP_PEAR_PHP_BIN%" NEQ "" (
+
+REM We can use this PHP to run a temporary php file to get the dirname of pear
+
+echo ^<?php $s=getcwd^(^);chdir^($a=dirname^(__FILE__^).'\\'^);if^(stristr^($a,'\\scripts'^)^)$a=dirname^(dirname^($a^)^).'\\';$f=fopen^($s.'\\~a.a','wb'^);echo$s.'\\~a.a';fwrite^($f,$a^);fclose^($f^);chdir^($s^);?^> > ~~getloc.php
+"%PHP_PEAR_PHP_BIN%" ~~getloc.php
+set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a
+DEL ~a.a
+DEL ~~getloc.php
+set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear"
+
+REM Make sure there is a pearcmd.php at our disposal
+
+IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php (
+IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+)
+)
+GOTO :INSTALLED
+) ELSE (
+REM Windows Me/98 cannot succeed, so allow the batch to fail
+)
+:FAILAUTODETECT
+echo WARNING: failed to auto-detect pear information
+:INSTALLED
+
+REM Check Folders and files
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2
+IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR
+IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR
+
+REM launch pearcmd
+GOTO RUN
+:PEAR_INSTALL_ERROR
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_INSTALL_ERROR2
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO pearcmd.php could not be found there.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_BIN_ERROR
+ECHO PHP_PEAR_BIN_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_BIN_DIR%
+GOTO END
+:PEAR_PHPBIN_ERROR
+ECHO PHP_PEAR_PHP_BIN is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_PHP_BIN%
+GOTO END
+:RUN
+"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d open_basedir="" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d register_argc_argv="On" -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9
+:END
+@ECHO ON
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/scripts/pear.sh b/WEB-INF/lib/pear/scripts/pear.sh
new file mode 100644 (file)
index 0000000..48ab067
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# first find which PHP binary to use
+if test "x$PHP_PEAR_PHP_BIN" != "x"; then
+  PHP="$PHP_PEAR_PHP_BIN"
+else
+  if test "@php_bin@" = '@'php_bin'@'; then
+    PHP=php
+  else
+    PHP="@php_bin@"
+  fi
+fi
+
+# then look for the right pear include dir
+if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
+  INCDIR=$PHP_PEAR_INSTALL_DIR
+  INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
+else
+  if test "@php_dir@" = '@'php_dir'@'; then
+    INCDIR=`dirname $0`
+    INCARG=""
+  else
+    INCDIR="@php_dir@"
+    INCARG="-d include_path=@php_dir@"
+  fi
+fi
+
+exec $PHP -C -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@"
diff --git a/WEB-INF/lib/pear/scripts/pearcmd.php b/WEB-INF/lib/pear/scripts/pearcmd.php
new file mode 100644 (file)
index 0000000..a3a928a
--- /dev/null
@@ -0,0 +1,448 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * Command line interface
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: pearcmd.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ */
+
+ob_end_clean();
+if (!defined('PEAR_RUNTYPE')) {
+    // this is defined in peclcmd.php as 'pecl'
+    define('PEAR_RUNTYPE', 'pear');
+}
+define('PEAR_IGNORE_BACKTRACE', 1);
+/**
+ * @nodep Gtk
+ */
+if ('@include_path@' != '@'.'include_path'.'@') {
+    ini_set('include_path', '@include_path@');
+    $raw = false;
+} else {
+    // this is a raw, uninstalled pear, either a cvs checkout, or php distro
+    $raw = true;
+}
+@ini_set('allow_url_fopen', true);
+if (!ini_get('safe_mode')) {
+    @set_time_limit(0);
+}
+ob_implicit_flush(true);
+@ini_set('track_errors', true);
+@ini_set('html_errors', false);
+@ini_set('magic_quotes_runtime', false);
+$_PEAR_PHPDIR = '#$%^&*';
+set_error_handler('error_handler');
+
+$pear_package_version = "@pear_version@";
+
+require_once 'PEAR.php';
+require_once 'PEAR/Frontend.php';
+require_once 'PEAR/Config.php';
+require_once 'PEAR/Command.php';
+require_once 'Console/Getopt.php';
+
+
+PEAR_Command::setFrontendType('CLI');
+$all_commands = PEAR_Command::getCommands();
+
+// remove this next part when we stop supporting that crap-ass PHP 4.2
+if (!isset($_SERVER['argv']) && !isset($argv) && !isset($HTTP_SERVER_VARS['argv'])) {
+    echo 'ERROR: either use the CLI php executable, or set register_argc_argv=On in php.ini';
+    exit(1);
+}
+
+$argv = Console_Getopt::readPHPArgv();
+// fix CGI sapi oddity - the -- in pear.bat/pear is not removed
+if (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') {
+    unset($argv[1]);
+    $argv = array_values($argv);
+}
+$progname = PEAR_RUNTYPE;
+array_shift($argv);
+$options = Console_Getopt::getopt2($argv, "c:C:d:D:Gh?sSqu:vV");
+if (PEAR::isError($options)) {
+    usage($options);
+}
+
+$opts = $options[0];
+
+$fetype = 'CLI';
+if ($progname == 'gpear' || $progname == 'pear-gtk') {
+    $fetype = 'Gtk';
+} else {
+    foreach ($opts as $opt) {
+        if ($opt[0] == 'G') {
+            $fetype = 'Gtk';
+        }
+    }
+}
+//Check if Gtk and PHP >= 5.1.0
+if ($fetype == 'Gtk' && version_compare(phpversion(), '5.1.0', '>=')) {
+    $fetype = 'Gtk2';
+}
+
+$pear_user_config = '';
+$pear_system_config = '';
+$store_user_config = false;
+$store_system_config = false;
+$verbose = 1;
+
+foreach ($opts as $opt) {
+    switch ($opt[0]) {
+        case 'c':
+            $pear_user_config = $opt[1];
+            break;
+        case 'C':
+            $pear_system_config = $opt[1];
+            break;
+    }
+}
+
+PEAR_Command::setFrontendType($fetype);
+$ui = &PEAR_Command::getFrontendObject();
+$config = &PEAR_Config::singleton($pear_user_config, $pear_system_config);
+
+if (PEAR::isError($config)) {
+    $_file = '';
+    if ($pear_user_config !== false) {
+       $_file .= $pear_user_config;
+    }
+    if ($pear_system_config !== false) {
+       $_file .= '/' . $pear_system_config;
+    }
+    if ($_file == '/') {
+        $_file = 'The default config file';
+    }
+    $config->getMessage();
+    $ui->outputData("ERROR: $_file is not a valid config file or is corrupted.");
+    // We stop, we have no idea where we are :)
+    exit(1);
+}
+
+// this is used in the error handler to retrieve a relative path
+$_PEAR_PHPDIR = $config->get('php_dir');
+$ui->setConfig($config);
+PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError"));
+if (ini_get('safe_mode')) {
+    $ui->outputData('WARNING: running in safe mode requires that all files created ' .
+        'be the same uid as the current script.  PHP reports this script is uid: ' .
+        @getmyuid() . ', and current user is: ' . @get_current_user());
+}
+
+$verbose = $config->get("verbose");
+$cmdopts = array();
+
+if ($raw) {
+    if (!$config->isDefinedLayer('user') && !$config->isDefinedLayer('system')) {
+        $found = false;
+        foreach ($opts as $opt) {
+            if ($opt[0] == 'd' || $opt[0] == 'D') {
+                $found = true; // the user knows what they are doing, and are setting config values
+            }
+        }
+        if (!$found) {
+            // no prior runs, try to install PEAR
+            if (strpos(dirname(__FILE__), 'scripts')) {
+                $packagexml = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'package2.xml';
+                $pearbase = dirname(dirname(__FILE__));
+            } else {
+                $packagexml = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'package2.xml';
+                $pearbase = dirname(__FILE__);
+            }
+            if (file_exists($packagexml)) {
+                $options[1] = array(
+                    'install',
+                    $packagexml
+                );
+                $config->set('php_dir', $pearbase . DIRECTORY_SEPARATOR . 'php');
+                $config->set('data_dir', $pearbase . DIRECTORY_SEPARATOR . 'data');
+                $config->set('doc_dir', $pearbase . DIRECTORY_SEPARATOR . 'docs');
+                $config->set('test_dir', $pearbase . DIRECTORY_SEPARATOR . 'tests');
+                $config->set('ext_dir', $pearbase . DIRECTORY_SEPARATOR . 'extensions');
+                $config->set('bin_dir', $pearbase);
+                $config->mergeConfigFile($pearbase . 'pear.ini', false);
+                $config->store();
+                $config->set('auto_discover', 1);
+            }
+        }
+    }
+}
+foreach ($opts as $opt) {
+    $param = !empty($opt[1]) ? $opt[1] : true;
+    switch ($opt[0]) {
+        case 'd':
+            if ($param === true) {
+                die('Invalid usage of "-d" option, expected -d config_value=value, ' .
+                    'received "-d"' . "\n");
+            }
+            $possible = explode('=', $param);
+            if (count($possible) != 2) {
+                die('Invalid usage of "-d" option, expected -d config_value=value, received "' .
+                    $param . '"' . "\n");
+            }
+            list($key, $value) = explode('=', $param);
+            $config->set($key, $value, 'user');
+            break;
+        case 'D':
+            if ($param === true) {
+                die('Invalid usage of "-d" option, expected -d config_value=value, ' .
+                    'received "-d"' . "\n");
+            }
+            $possible = explode('=', $param);
+            if (count($possible) != 2) {
+                die('Invalid usage of "-d" option, expected -d config_value=value, received "' .
+                    $param . '"' . "\n");
+            }
+            list($key, $value) = explode('=', $param);
+            $config->set($key, $value, 'system');
+            break;
+        case 's':
+            $store_user_config = true;
+            break;
+        case 'S':
+            $store_system_config = true;
+            break;
+        case 'u':
+            $config->remove($param, 'user');
+            break;
+        case 'v':
+            $config->set('verbose', $config->get('verbose') + 1);
+            break;
+        case 'q':
+            $config->set('verbose', $config->get('verbose') - 1);
+            break;
+        case 'V':
+            usage(null, 'version');
+        case 'c':
+        case 'C':
+            break;
+        default:
+            // all non pear params goes to the command
+            $cmdopts[$opt[0]] = $param;
+            break;
+    }
+}
+
+if ($store_system_config) {
+    $config->store('system');
+}
+
+if ($store_user_config) {
+    $config->store('user');
+}
+
+$command = (isset($options[1][0])) ? $options[1][0] : null;
+if (empty($command) && ($store_user_config || $store_system_config)) {
+    exit;
+}
+
+if ($fetype == 'Gtk' || $fetype == 'Gtk2') {
+    if (!$config->validConfiguration()) {
+        PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' .
+            "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" .
+            'file to one of these locations, or use the -c and -s options to create one');
+    }
+    Gtk::main();
+} else do {
+    if ($command == 'help') {
+        usage(null, @$options[1][1]);
+    }
+
+    if (!$config->validConfiguration()) {
+        PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' .
+            "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" .
+            'file to one of these locations, or use the -c and -s options to create one');
+    }
+
+    PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+    $cmd = PEAR_Command::factory($command, $config);
+    PEAR::popErrorHandling();
+    if (PEAR::isError($cmd)) {
+        usage(null, @$options[1][0]);
+    }
+
+    $short_args = $long_args = null;
+    PEAR_Command::getGetoptArgs($command, $short_args, $long_args);
+    array_shift($options[1]);
+    $tmp = Console_Getopt::getopt2($options[1], $short_args, $long_args);
+
+    if (PEAR::isError($tmp)) {
+        break;
+    }
+
+    list($tmpopt, $params) = $tmp;
+    $opts = array();
+    foreach ($tmpopt as $foo => $tmp2) {
+        list($opt, $value) = $tmp2;
+        if ($value === null) {
+            $value = true; // options without args
+        }
+
+        if (strlen($opt) == 1) {
+            $cmdoptions = $cmd->getOptions($command);
+            foreach ($cmdoptions as $o => $d) {
+                if (isset($d['shortopt']) && $d['shortopt'] == $opt) {
+                    $opts[$o] = $value;
+                }
+            }
+        } else {
+            if (substr($opt, 0, 2) == '--') {
+                $opts[substr($opt, 2)] = $value;
+            }
+        }
+    }
+
+    $ok = $cmd->run($command, $opts, $params);
+    if ($ok === false) {
+        PEAR::raiseError("unknown command `$command'");
+    }
+
+    if (PEAR::isError($ok)) {
+        PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError"));
+        PEAR::raiseError($ok);
+    }
+} while (false);
+
+// {{{ usage()
+
+function usage($error = null, $helpsubject = null)
+{
+    global $progname, $all_commands;
+    $stdout = fopen('php://stdout', 'w');
+    if (PEAR::isError($error)) {
+        fputs($stdout, $error->getMessage() . "\n");
+    } elseif ($error !== null) {
+        fputs($stdout, "$error\n");
+    }
+
+    if ($helpsubject != null) {
+        $put = cmdHelp($helpsubject);
+    } else {
+        $put = "Commands:\n";
+        $maxlen = max(array_map("strlen", $all_commands));
+        $formatstr = "%-{$maxlen}s  %s\n";
+        ksort($all_commands);
+        foreach ($all_commands as $cmd => $class) {
+            $put .= sprintf($formatstr, $cmd, PEAR_Command::getDescription($cmd));
+        }
+        $put .=
+            "Usage: $progname [options] command [command-options] <parameters>\n".
+            "Type \"$progname help options\" to list all options.\n".
+            "Type \"$progname help shortcuts\" to list all command shortcuts.\n".
+            "Type \"$progname help <command>\" to get the help for the specified command.";
+    }
+    fputs($stdout, "$put\n");
+    fclose($stdout);
+
+    if ($error === null) {
+        exit(0);
+    }
+    exit(1);
+}
+
+function cmdHelp($command)
+{
+    global $progname, $all_commands, $config;
+    if ($command == "options") {
+        return
+        "Options:\n".
+        "     -v         increase verbosity level (default 1)\n".
+        "     -q         be quiet, decrease verbosity level\n".
+        "     -c file    find user configuration in `file'\n".
+        "     -C file    find system configuration in `file'\n".
+        "     -d foo=bar set user config variable `foo' to `bar'\n".
+        "     -D foo=bar set system config variable `foo' to `bar'\n".
+        "     -G         start in graphical (Gtk) mode\n".
+        "     -s         store user configuration\n".
+        "     -S         store system configuration\n".
+        "     -u foo     unset `foo' in the user configuration\n".
+        "     -h, -?     display help/usage (this message)\n".
+        "     -V         version information\n";
+    } elseif ($command == "shortcuts") {
+        $sc = PEAR_Command::getShortcuts();
+        $ret = "Shortcuts:\n";
+        foreach ($sc as $s => $c) {
+            $ret .= sprintf("     %-8s %s\n", $s, $c);
+        }
+        return $ret;
+
+    } elseif ($command == "version") {
+        return "PEAR Version: ".$GLOBALS['pear_package_version'].
+               "\nPHP Version: ".phpversion().
+               "\nZend Engine Version: ".zend_version().
+               "\nRunning on: ".php_uname();
+
+    } elseif ($help = PEAR_Command::getHelp($command)) {
+        if (is_string($help)) {
+            return "$progname $command [options] $help\n";
+        }
+
+        if ($help[1] === null) {
+            return "$progname $command $help[0]";
+        }
+
+        return "$progname $command [options] $help[0]\n$help[1]";
+    }
+
+    return "Command '$command' is not valid, try '$progname help'";
+}
+
+// }}}
+
+function error_handler($errno, $errmsg, $file, $line, $vars) {
+    if ((defined('E_STRICT') && $errno & E_STRICT) || (defined('E_DEPRECATED') &&
+          $errno & E_DEPRECATED) || !error_reporting()) {
+        if (defined('E_STRICT') && $errno & E_STRICT) {
+            return; // E_STRICT
+        }
+        if (defined('E_DEPRECATED') && $errno & E_DEPRECATED) {
+            return; // E_DEPRECATED
+        }
+        if ($GLOBALS['config']->get('verbose') < 4) {
+            return false; // @silenced error, show all if debug is high enough
+        }
+    }
+    $errortype = array (
+        E_ERROR   =>  "Error",
+        E_WARNING   =>  "Warning",
+        E_PARSE   =>  "Parsing Error",
+        E_NOTICE   =>  "Notice",
+        E_CORE_ERROR  =>  "Core Error",
+        E_CORE_WARNING  =>  "Core Warning",
+        E_COMPILE_ERROR  =>  "Compile Error",
+        E_COMPILE_WARNING =>  "Compile Warning",
+        E_USER_ERROR =>  "User Error",
+        E_USER_WARNING =>  "User Warning",
+        E_USER_NOTICE =>  "User Notice"
+    );
+    $prefix = $errortype[$errno];
+    global $_PEAR_PHPDIR;
+    if (stristr($file, $_PEAR_PHPDIR)) {
+        $file = substr($file, strlen($_PEAR_PHPDIR) + 1);
+    } else {
+        $file = basename($file);
+    }
+    print "\n$prefix: $errmsg in $file on line $line\n";
+    return false;
+}
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * mode: php
+ * End:
+ */
+// vim600:syn=php
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/scripts/peardev.bat b/WEB-INF/lib/pear/scripts/peardev.bat
new file mode 100644 (file)
index 0000000..8b67c7d
--- /dev/null
@@ -0,0 +1,115 @@
+@ECHO OFF
+
+REM ----------------------------------------------------------------------
+REM PHP version 5
+REM ----------------------------------------------------------------------
+REM Copyright (c) 1997-2004 The PHP Group
+REM ----------------------------------------------------------------------
+REM  This source file is subject to version 3.0 of the PHP license,
+REM  that is bundled with this package in the file LICENSE, and is
+REM  available at through the world-wide-web at
+REM  http://www.php.net/license/3_0.txt.
+REM  If you did not receive a copy of the PHP license and are unable to
+REM  obtain it through the world-wide-web, please send a note to
+REM  license@php.net so we can mail you a copy immediately.
+REM ----------------------------------------------------------------------
+REM  Authors:     Alexander Merz (alexmerz@php.net)
+REM ----------------------------------------------------------------------
+REM
+REM  $Id: peardev.bat,v 1.6 2007-09-03 03:00:17 cellog Exp $
+
+REM change this lines to match the paths of your system
+REM -------------------
+
+
+REM Test to see if this is a raw pear.bat (uninstalled version)
+SET TMPTMPTMPTMPT=@includ
+SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@
+FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED)
+
+REM Check PEAR global ENV, set them if they do not exist
+IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@"
+IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@"
+IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@"
+GOTO :INSTALLED
+
+:NOTINSTALLED
+ECHO WARNING: This is a raw, uninstalled pear.bat
+
+REM Check to see if we can grab the directory of this file (Windows NT+)
+IF %~n0 == pear (
+FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" (
+SET "PHP_PEAR_PHP_BIN=%%~$PATH:x"
+echo Using PHP Executable "%PHP_PEAR_PHP_BIN%"
+"%PHP_PEAR_PHP_BIN%" -v
+GOTO :NEXTTEST
+))
+GOTO :FAILAUTODETECT
+:NEXTTEST
+IF "%PHP_PEAR_PHP_BIN%" NEQ "" (
+
+REM We can use this PHP to run a temporary php file to get the dirname of pear
+
+echo ^<?php $s=getcwd^(^);chdir^($a=dirname^(__FILE__^).'\\'^);if^(stristr^($a,'\\scripts'^)^)$a=dirname^(dirname^($a^)^).'\\';$f=fopen^($s.'\\~a.a','wb'^);echo$s.'\\~a.a';fwrite^($f,$a^);fclose^($f^);chdir^($s^);?^> > ~~getloc.php
+"%PHP_PEAR_PHP_BIN%" ~~getloc.php
+set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a
+DEL ~a.a
+DEL ~~getloc.php
+set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear"
+
+REM Make sure there is a pearcmd.php at our disposal
+
+IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php (
+IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+)
+)
+GOTO :INSTALLED
+) ELSE (
+REM Windows Me/98 cannot succeed, so allow the batch to fail
+)
+:FAILAUTODETECT
+echo WARNING: failed to auto-detect pear information
+:INSTALLED
+
+REM Check Folders and files
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2
+IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR
+IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR
+REM launch pearcmd
+GOTO RUN
+:PEAR_INSTALL_ERROR
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_INSTALL_ERROR2
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO pearcmd.php could not be found there.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_BIN_ERROR
+ECHO PHP_PEAR_BIN_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_BIN_DIR%
+GOTO END
+:PEAR_PHPBIN_ERROR
+ECHO PHP_PEAR_PHP_BIN is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_PHP_BIN%
+GOTO END
+:RUN
+"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d memory_limit="-1" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d open_basedir="" -d output_buffering=1 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9
+:END
+@ECHO ON
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/scripts/peardev.sh b/WEB-INF/lib/pear/scripts/peardev.sh
new file mode 100644 (file)
index 0000000..635ec75
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# first find which PHP binary to use
+if test "x$PHP_PEAR_PHP_BIN" != "x"; then
+  PHP="$PHP_PEAR_PHP_BIN"
+else
+  if test "@php_bin@" = '@'php_bin'@'; then
+    PHP=php
+  else
+    PHP="@php_bin@"
+  fi
+fi
+
+# then look for the right pear include dir
+if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
+  INCDIR=$PHP_PEAR_INSTALL_DIR
+  INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
+else
+  if test "@php_dir@" = '@'php_dir'@'; then
+    INCDIR=`dirname $0`
+    INCARG=""
+  else
+    INCDIR="@php_dir@"
+    INCARG="-d include_path=@php_dir@"
+  fi
+fi
+
+exec $PHP -d date.timezone=UTC -d memory_limit="-1" -C -q $INCARG -d output_buffering=1 -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d variables_order=EGPCS -d auto_append_file="" $INCDIR/pearcmd.php "$@"
diff --git a/WEB-INF/lib/pear/scripts/pecl.bat b/WEB-INF/lib/pear/scripts/pecl.bat
new file mode 100644 (file)
index 0000000..e7c823b
--- /dev/null
@@ -0,0 +1,115 @@
+@ECHO OFF
+
+REM ----------------------------------------------------------------------
+REM PHP version 5
+REM ----------------------------------------------------------------------
+REM Copyright (c) 1997-2004 The PHP Group
+REM ----------------------------------------------------------------------
+REM  This source file is subject to version 3.0 of the PHP license,
+REM  that is bundled with this package in the file LICENSE, and is
+REM  available at through the world-wide-web at
+REM  http://www.php.net/license/3_0.txt.
+REM  If you did not receive a copy of the PHP license and are unable to
+REM  obtain it through the world-wide-web, please send a note to
+REM  license@php.net so we can mail you a copy immediately.
+REM ----------------------------------------------------------------------
+REM  Authors:     Alexander Merz (alexmerz@php.net)
+REM ----------------------------------------------------------------------
+REM
+REM  Last updated 02/08/2004 ($Id$ is not replaced if the file is binary)
+
+REM change this lines to match the paths of your system
+REM -------------------
+
+
+REM Test to see if this is a raw pear.bat (uninstalled version)
+SET TMPTMPTMPTMPT=@includ
+SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@
+FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED)
+
+REM Check PEAR global ENV, set them if they do not exist
+IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@"
+IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@"
+IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@"
+GOTO :INSTALLED
+
+:NOTINSTALLED
+ECHO WARNING: This is a raw, uninstalled pear.bat
+
+REM Check to see if we can grab the directory of this file (Windows NT+)
+IF %~n0 == pear (
+FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" (
+SET "PHP_PEAR_PHP_BIN=%%~$PATH:x"
+echo Using PHP Executable "%PHP_PEAR_PHP_BIN%"
+"%PHP_PEAR_PHP_BIN%" -v
+GOTO :NEXTTEST
+))
+GOTO :FAILAUTODETECT
+:NEXTTEST
+IF "%PHP_PEAR_PHP_BIN%" NEQ "" (
+
+REM We can use this PHP to run a temporary php file to get the dirname of pear
+
+echo ^<?php $s=getcwd^(^);chdir^($a=dirname^(__FILE__^).'\\'^);if^(stristr^($a,'\\scripts'^)^)$a=dirname^(dirname^($a^)^).'\\';$f=fopen^($s.'\\~a.a','wb'^);echo$s.'\\~a.a';fwrite^($f,$a^);fclose^($f^);chdir^($s^);?^> > ~~getloc.php
+"%PHP_PEAR_PHP_BIN%" ~~getloc.php
+set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a
+DEL ~a.a
+DEL ~~getloc.php
+set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear"
+
+REM Make sure there is a pearcmd.php at our disposal
+
+IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php (
+IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php
+)
+)
+GOTO :INSTALLED
+) ELSE (
+REM Windows Me/98 cannot succeed, so allow the batch to fail
+)
+:FAILAUTODETECT
+echo WARNING: failed to auto-detect pear information
+:INSTALLED
+
+REM Check Folders and files
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2
+IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR
+IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR
+REM launch pearcmd
+GOTO RUN
+:PEAR_INSTALL_ERROR
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_INSTALL_ERROR2
+ECHO PHP_PEAR_INSTALL_DIR is not set correctly.
+ECHO pearcmd.php could not be found there.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_INSTALL_DIR%
+GOTO END
+:PEAR_BIN_ERROR
+ECHO PHP_PEAR_BIN_DIR is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_BIN_DIR%
+GOTO END
+:PEAR_PHPBIN_ERROR
+ECHO PHP_PEAR_PHP_BIN is not set correctly.
+ECHO Please fix it using your environment variable or modify
+ECHO the default value in pear.bat
+ECHO The current value is:
+ECHO %PHP_PEAR_PHP_BIN%
+GOTO END
+:RUN
+"%PHP_PEAR_PHP_BIN%" -C -n -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -d register_argc_argv="On" -d variables_order=EGPCS -f "%PHP_PEAR_INSTALL_DIR%\peclcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9
+:END
+@ECHO ON
\ No newline at end of file
diff --git a/WEB-INF/lib/pear/scripts/pecl.sh b/WEB-INF/lib/pear/scripts/pecl.sh
new file mode 100644 (file)
index 0000000..332890c
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# first find which PHP binary to use
+if test "x$PHP_PEAR_PHP_BIN" != "x"; then
+  PHP="$PHP_PEAR_PHP_BIN"
+else
+  if test "@php_bin@" = '@'php_bin'@'; then
+    PHP=php
+  else
+    PHP="@php_bin@"
+  fi
+fi
+
+# then look for the right pear include dir
+if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
+  INCDIR=$PHP_PEAR_INSTALL_DIR
+  INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
+else
+  if test "@php_dir@" = '@'php_dir'@'; then
+    INCDIR=`dirname $0`
+    INCARG=""
+  else
+    INCDIR="@php_dir@"
+    INCARG="-d include_path=@php_dir@"
+  fi
+fi
+
+exec $PHP -C -n -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d safe_mode=0 -d register_argc_argv="On" $INCDIR/peclcmd.php "$@"
diff --git a/WEB-INF/lib/pear/scripts/peclcmd.php b/WEB-INF/lib/pear/scripts/peclcmd.php
new file mode 100644 (file)
index 0000000..498caaf
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * Command line interface
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: peclcmd.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ */
+
+/**
+ * @nodep Gtk
+ */
+if ('@include_path@' != '@'.'include_path'.'@') {
+    ini_set('include_path', '@include_path@');
+    $raw = false;
+} else {
+    // this is a raw, uninstalled pear, either a cvs checkout, or php distro
+    $raw = true;
+}
+define('PEAR_RUNTYPE', 'pecl');
+require_once 'pearcmd.php';
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * mode: php
+ * End:
+ */
+// vim600:syn=php
+
+?>
diff --git a/WEB-INF/lib/pear/template.spec b/WEB-INF/lib/pear/template.spec
new file mode 100644 (file)
index 0000000..37b477f
--- /dev/null
@@ -0,0 +1,72 @@
+Summary: PEAR: @summary@
+Name: @rpm_package@
+Version: @version@
+Release: 1
+License: @release_license@
+Group: Development/Libraries
+Source: http://@master_server@/get/@package@-%{version}.tgz
+BuildRoot: %{_tmppath}/%{name}-root
+URL: http://@master_server@/package/@package@
+Prefix: %{_prefix}
+BuildArchitectures: @arch@
+@extra_headers@
+
+%description
+@description@
+
+%prep
+rm -rf %{buildroot}/*
+%setup -c -T
+# XXX Source files location is missing here in pear cmd
+pear -v -c %{buildroot}/pearrc \
+        -d php_dir=%{_libdir}/php/pear \
+        -d doc_dir=/docs \
+        -d bin_dir=%{_bindir} \
+        -d data_dir=%{_libdir}/php/pear/data \
+        -d test_dir=%{_libdir}/php/pear/tests \
+        -d ext_dir=%{_libdir} \@extra_config@
+        -s
+
+%build
+echo BuildRoot=%{buildroot}
+
+%postun
+# if refcount = 0 then package has been removed (not upgraded)
+if [ "$1" -eq "0" ]; then
+    pear uninstall --nodeps -r @possible_channel@@package@
+    rm @rpm_xml_dir@/@package@.xml
+fi
+
+
+%post
+# if refcount = 2 then package has been upgraded
+if [ "$1" -ge "2" ]; then
+    pear upgrade --nodeps -r @rpm_xml_dir@/@package@.xml
+else
+    pear install --nodeps -r @rpm_xml_dir@/@package@.xml
+fi
+
+%install
+pear -c %{buildroot}/pearrc install --nodeps -R %{buildroot} \
+        $RPM_SOURCE_DIR/@package@-%{version}.tgz
+rm %{buildroot}/pearrc
+rm %{buildroot}/%{_libdir}/php/pear/.filemap
+rm %{buildroot}/%{_libdir}/php/pear/.lock
+rm -rf %{buildroot}/%{_libdir}/php/pear/.registry
+if [ "@doc_files@" != "" ]; then
+     mv %{buildroot}/docs/@package@/* .
+     rm -rf %{buildroot}/docs
+fi
+mkdir -p %{buildroot}@rpm_xml_dir@
+tar -xzf $RPM_SOURCE_DIR/@package@-%{version}.tgz package@package2xml@.xml
+cp -p package@package2xml@.xml %{buildroot}@rpm_xml_dir@/@package@.xml
+
+#rm -rf %{buildroot}/*
+#pear -q install -R %{buildroot} -n package@package2xml@.xml
+#mkdir -p %{buildroot}@rpm_xml_dir@
+#cp -p package@package2xml@.xml %{buildroot}@rpm_xml_dir@/@package@.xml
+
+%files
+    %defattr(-,root,root)
+    %doc @doc_files@
+    /
diff --git a/WEB-INF/lib/smarty/Smarty.class.php b/WEB-INF/lib/smarty/Smarty.class.php
new file mode 100644 (file)
index 0000000..feb8817
--- /dev/null
@@ -0,0 +1,815 @@
+<?php
+
+/**
+ * Project:     Smarty: the PHP compiling template engine
+ * File:        Smarty.class.php
+ * SVN:         $Id: Smarty.class.php 3895 2010-12-31 13:47:12Z uwe.tews@googlemail.com $
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ * For questions, help, comments, discussion, etc., please join the
+ * Smarty mailing list. Send a blank e-mail to
+ * smarty-discussion-subscribe@googlegroups.com
+ * 
+ * @link http://www.smarty.net/
+ * @copyright 2008 New Digital Group, Inc.
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author Uwe Tews 
+ * @package Smarty
+ * @version 3.0.7
+ */
+
+/**
+ * define shorthand directory separator constant
+ */
+if (!defined('DS')) {
+    define('DS', DIRECTORY_SEPARATOR);
+} 
+
+/**
+ * set SMARTY_DIR to absolute path to Smarty library files.
+ * Sets SMARTY_DIR only if user application has not already defined it.
+ */
+if (!defined('SMARTY_DIR')) {
+    define('SMARTY_DIR', dirname(__FILE__) . DS);
+} 
+
+/**
+ * set SMARTY_SYSPLUGINS_DIR to absolute path to Smarty internal plugins.
+ * Sets SMARTY_SYSPLUGINS_DIR only if user application has not already defined it.
+ */
+if (!defined('SMARTY_SYSPLUGINS_DIR')) {
+    define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DS);
+} 
+if (!defined('SMARTY_PLUGINS_DIR')) {
+    define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DS);
+} 
+if (!defined('SMARTY_RESOURCE_CHAR_SET')) {
+    define('SMARTY_RESOURCE_CHAR_SET', 'UTF-8');
+} 
+if (!defined('SMARTY_RESOURCE_DATE_FORMAT')) {
+    define('SMARTY_RESOURCE_DATE_FORMAT', '%b %e, %Y');
+} 
+
+/**
+ * register the class autoloader
+ */
+if (!defined('SMARTY_SPL_AUTOLOAD')) {
+    define('SMARTY_SPL_AUTOLOAD', 0);
+} 
+
+if (SMARTY_SPL_AUTOLOAD && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false) {
+    $registeredAutoLoadFunctions = spl_autoload_functions();
+    if (!isset($registeredAutoLoadFunctions['spl_autoload'])) {
+        spl_autoload_register();
+    } 
+} else {
+    spl_autoload_register('smartyAutoload');
+} 
+
+/**
+ * This is the main Smarty class
+ */
+class Smarty extends Smarty_Internal_Data {
+       /**
+       * constant definitions
+       */
+    // smarty version
+    const SMARTY_VERSION = 'Smarty-3.0.7'; 
+       //define variable scopes
+       const SCOPE_LOCAL = 0;
+       const SCOPE_PARENT = 1;
+       const SCOPE_ROOT = 2;
+       const SCOPE_GLOBAL = 3;
+       // define caching modes
+       const CACHING_OFF = 0;
+       const CACHING_LIFETIME_CURRENT = 1;
+       const CACHING_LIFETIME_SAVED = 2;
+       /** modes for handling of "<?php ... ?>" tags in templates. **/
+       const PHP_PASSTHRU = 0; //-> print tags as plain text
+       const PHP_QUOTE = 1; //-> escape tags as entities
+       const PHP_REMOVE = 2; //-> escape tags as entities
+       const PHP_ALLOW = 3; //-> escape tags as entities
+       // filter types
+       const FILTER_POST = 'post';
+       const FILTER_PRE = 'pre';
+       const FILTER_OUTPUT = 'output';
+       const FILTER_VARIABLE = 'variable';
+       // plugin types
+       const PLUGIN_FUNCTION = 'function';
+       const PLUGIN_BLOCK = 'block';
+       const PLUGIN_COMPILER = 'compiler';
+       const PLUGIN_MODIFIER = 'modifier';
+
+       /**
+       * static variables
+       */
+    // assigned global tpl vars
+    static $global_tpl_vars = array(); 
+
+       /**
+       * variables
+       */
+    // auto literal on delimiters with whitspace
+    public $auto_literal = true; 
+    // display error on not assigned variables
+    public $error_unassigned = false; 
+    // template directory
+    public $template_dir = null; 
+    // default template handler
+    public $default_template_handler_func = null; 
+    // compile directory
+    public $compile_dir = null; 
+    // plugins directory
+    public $plugins_dir = null; 
+    // cache directory
+    public $cache_dir = null; 
+    // config directory
+    public $config_dir = null; 
+    // force template compiling?
+    public $force_compile = false; 
+    // check template for modifications?
+    public $compile_check = true; 
+    // locking concurrent compiles
+    public $compile_locking = true; 
+    // use sub dirs for compiled/cached files?
+    public $use_sub_dirs = false; 
+    // compile_error?
+    public $compile_error = false; 
+    // caching enabled
+    public $caching = false; 
+    // merge compiled includes
+    public $merge_compiled_includes = false; 
+    // cache lifetime
+    public $cache_lifetime = 3600; 
+    // force cache file creation
+    public $force_cache = false; 
+    // cache_id
+    public $cache_id = null; 
+    // compile_id
+    public $compile_id = null; 
+    // template delimiters
+    public $left_delimiter = "{";
+    public $right_delimiter = "}"; 
+    // security
+    public $security_class = 'Smarty_Security';
+    public $security_policy = null;
+    public $php_handling = self::PHP_PASSTHRU;
+    public $allow_php_tag = false;
+    public $allow_php_templates = false;
+    public $direct_access_security = true; 
+    public $trusted_dir = array();
+    // debug mode
+    public $debugging = false;
+    public $debugging_ctrl = 'NONE';
+    public $smarty_debug_id = 'SMARTY_DEBUG';
+    public $debug_tpl = null; 
+    // When set, smarty does uses this value as error_reporting-level.
+    public $error_reporting = null; 
+    // config var settings
+    public $config_overwrite = true; //Controls whether variables with the same name overwrite each other.
+    public $config_booleanize = true; //Controls whether config values of on/true/yes and off/false/no get converted to boolean
+    public $config_read_hidden = false; //Controls whether hidden config sections/vars are read from the file.                                                      
+    // config vars
+    public $config_vars = array(); 
+    // assigned tpl vars
+    public $tpl_vars = array(); 
+    // dummy parent object
+    public $parent = null; 
+    // global template functions
+    public $template_functions = array(); 
+    // resource type used if none given
+    public $default_resource_type = 'file'; 
+    // caching type
+    public $caching_type = 'file'; 
+    // internal cache resource types
+    public $cache_resource_types = array('file'); 
+    // internal config properties
+    public $properties = array(); 
+    // config type
+    public $default_config_type = 'file'; 
+    // cached template objects
+    public $template_objects = null; 
+    // check If-Modified-Since headers
+    public $cache_modified_check = false; 
+    // registered plugins
+    public $registered_plugins = array(); 
+    // plugin search order
+    public $plugin_search_order = array('function', 'block', 'compiler', 'class'); 
+    // registered objects
+    public $registered_objects = array(); 
+    // registered classes
+    public $registered_classes = array(); 
+    // registered filters
+    public $registered_filters = array(); 
+    // registered resources
+    public $registered_resources = array(); 
+    // autoload filter
+    public $autoload_filters = array(); 
+    // status of filter on variable output
+    public $variable_filter = true; 
+    // default modifier
+    public $default_modifiers = array(); 
+    // global internal smarty  vars
+    static $_smarty_vars = array(); 
+    // start time for execution time calculation
+    public $start_time = 0; 
+    // default file permissions
+    public $_file_perms = 0644; 
+    // default dir permissions
+    public $_dir_perms = 0771; 
+    // block tag hierarchy
+    public $_tag_stack = array(); 
+    // flag if {block} tag is compiled for template inheritance
+    public $inheritance = false;
+    // generate deprecated function call notices?
+    public $deprecation_notices = true;
+    // Smarty 2 BC
+    public $_version = self::SMARTY_VERSION;
+    // self pointer to Smarty object
+    public $smarty;
+
+    /**
+     * Class constructor, initializes basic smarty properties
+     */
+    public function __construct()
+    { 
+               // selfpointer need by some other class methods
+               $this->smarty = $this;
+        if (is_callable('mb_internal_encoding')) {
+            mb_internal_encoding(SMARTY_RESOURCE_CHAR_SET);
+        } 
+        $this->start_time = microtime(true); 
+        // set default dirs
+        $this->template_dir = array('.' . DS . 'templates' . DS);
+        $this->compile_dir = '.' . DS . 'templates_c' . DS;
+        $this->plugins_dir = array(SMARTY_PLUGINS_DIR);
+        $this->cache_dir = '.' . DS . 'cache' . DS;
+        $this->config_dir = '.' . DS . 'configs' . DS;
+        $this->debug_tpl = SMARTY_DIR . 'debug.tpl';
+        if (isset($_SERVER['SCRIPT_NAME'])) {
+            $this->assignGlobal('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']);
+        } 
+    } 
+
+    /**
+     * Class destructor
+     */
+    public function __destruct()
+    { 
+    } 
+
+    /**
+     * fetches a rendered Smarty template
+     * 
+     * @param string $template the resource handle of the template file or template object
+     * @param mixed $cache_id cache id to be used with this template
+     * @param mixed $compile_id compile id to be used with this template
+     * @param object $ |null $parent next higher level of Smarty variables
+     * @return string rendered template output
+     */
+    public function fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false)
+    {
+        if (!empty($cache_id) && is_object($cache_id)) {
+            $parent = $cache_id;
+            $cache_id = null;
+        } 
+        if ($parent === null) {
+            // get default Smarty data object
+            $parent = $this;
+        } 
+        // create template object if necessary
+        ($template instanceof $this->template_class)? $_template = $template :
+        $_template = $this->createTemplate ($template, $cache_id, $compile_id, $parent, false);
+        if (isset($this->error_reporting)) {
+               $_smarty_old_error_level = error_reporting($this->error_reporting);
+       }
+       // check URL debugging control
+        if (!$this->debugging && $this->debugging_ctrl == 'URL') {
+            if (isset($_SERVER['QUERY_STRING'])) {
+                $_query_string = $_SERVER['QUERY_STRING'];
+            } else {
+                $_query_string = '';
+            } 
+            if (false !== strpos($_query_string, $this->smarty_debug_id)) {
+                if (false !== strpos($_query_string, $this->smarty_debug_id . '=on')) {
+                    // enable debugging for this browser session
+                    setcookie('SMARTY_DEBUG', true);
+                    $this->debugging = true;
+                } elseif (false !== strpos($_query_string, $this->smarty_debug_id . '=off')) {
+                    // disable debugging for this browser session
+                    setcookie('SMARTY_DEBUG', false);
+                    $this->debugging = false;
+                } else {
+                    // enable debugging for this page
+                    $this->debugging = true;
+                } 
+            } else {
+                if (isset($_COOKIE['SMARTY_DEBUG'])) {
+                    $this->debugging = true;
+                } 
+            } 
+        } 
+        // obtain data for cache modified check
+        if ($this->cache_modified_check && $this->caching && $display) {
+            $_isCached = $_template->isCached() && !$_template->has_nocache_code;
+            if ($_isCached) {
+                $_gmt_mtime = gmdate('D, d M Y H:i:s', $_template->getCachedTimestamp()) . ' GMT';
+            } else {
+                $_gmt_mtime = '';
+            } 
+        } 
+        // return rendered template
+        if ((!$this->caching || $_template->resource_object->isEvaluated) && (isset($this->autoload_filters['output']) || isset($this->registered_filters['output']))) {
+            $_output = Smarty_Internal_Filter_Handler::runFilter('output', $_template->getRenderedTemplate(), $_template);
+        } else {
+            $_output = $_template->getRenderedTemplate();
+        } 
+        $_template->rendered_content = null;
+        if (isset($this->error_reporting)) {
+               error_reporting($_smarty_old_error_level);
+        } 
+        // display or fetch
+        if ($display) {
+            if ($this->caching && $this->cache_modified_check) {
+                $_last_modified_date = @substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3);
+                if ($_isCached && $_gmt_mtime == $_last_modified_date) {
+                    if (php_sapi_name() == 'cgi')
+                        header('Status: 304 Not Modified');
+                    else
+                        header('HTTP/1.1 304 Not Modified');
+                } else {
+                    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $_template->getCachedTimestamp()) . ' GMT');
+                    echo $_output;
+                } 
+            } else {
+                echo $_output;
+            } 
+            // debug output
+            if ($this->debugging) {
+                Smarty_Internal_Debug::display_debug($this);
+            } 
+            return;
+        } else {
+            // return fetched content
+            return $_output;
+        } 
+    } 
+
+    /**
+     * displays a Smarty template
+     * 
+     * @param string $ |object $template the resource handle of the template file  or template object
+     * @param mixed $cache_id cache id to be used with this template
+     * @param mixed $compile_id compile id to be used with this template
+     * @param object $parent next higher level of Smarty variables
+     */
+    public function display($template, $cache_id = null, $compile_id = null, $parent = null)
+    { 
+        // display template
+        $this->fetch ($template, $cache_id, $compile_id, $parent, true);
+    } 
+
+    /**
+     * test if cache i valid
+     * 
+     * @param string $ |object $template the resource handle of the template file or template object
+     * @param mixed $cache_id cache id to be used with this template
+     * @param mixed $compile_id compile id to be used with this template
+     * @param object $parent next higher level of Smarty variables
+     * @return boolean cache status
+     */
+    public function isCached($template, $cache_id = null, $compile_id = null, $parent = null)
+    {
+       if ($parent === null) {
+               $parent = $this;
+       }
+        if (!($template instanceof $this->template_class)) {
+            $template = $this->createTemplate ($template, $cache_id, $compile_id, $parent, false);
+        } 
+        // return cache status of template
+        return $template->isCached();
+    } 
+
+    /**
+     * creates a data object
+     * 
+     * @param object $parent next higher level of Smarty variables
+     * @returns object data object
+     */
+    public function createData($parent = null)
+    {
+        return new Smarty_Data($parent, $this);
+    } 
+
+    /**
+     * creates a template object
+     * 
+     * @param string $template the resource handle of the template file
+     * @param mixed $cache_id cache id to be used with this template
+     * @param mixed $compile_id compile id to be used with this template
+     * @param object $parent next higher level of Smarty variables
+     * @param boolean $do_clone flag is Smarty object shall be cloned
+     * @returns object template object
+     */
+    public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true)
+    {
+        if (!empty($cache_id) && (is_object($cache_id) || is_array($cache_id))) {
+            $parent = $cache_id;
+            $cache_id = null;
+        } 
+        if (!empty($parent) && is_array($parent)) {
+            $data = $parent;
+            $parent = null;
+        } else {
+            $data = null;
+        } 
+        if (!is_object($template)) {
+            // we got a template resource
+            // already in template cache?
+            $_templateId =  sha1($template . $cache_id . $compile_id);
+            if (isset($this->template_objects[$_templateId]) && $this->caching) {
+                // return cached template object
+                $tpl = $this->template_objects[$_templateId];
+            } else {
+                // create new template object
+                if ($do_clone) {
+                       $tpl = new $this->template_class($template, clone $this, $parent, $cache_id, $compile_id);
+                } else {
+                       $tpl = new $this->template_class($template, $this, $parent, $cache_id, $compile_id);
+                }
+            } 
+        } else {
+            // just return a copy of template class
+            $tpl = $template;
+        } 
+        // fill data if present
+        if (!empty($data) && is_array($data)) {
+            // set up variable values
+            foreach ($data as $_key => $_val) {
+                $tpl->tpl_vars[$_key] = new Smarty_variable($_val);
+            } 
+        } 
+        return $tpl;
+    } 
+    
+     
+
+    /**
+     * Check if a template resource exists
+     * 
+     * @param string $resource_name template name
+     * @return boolean status
+     */
+    function templateExists($resource_name)
+    { 
+        // create template object
+        $save = $this->template_objects;
+        $tpl = new $this->template_class($resource_name, $this); 
+        // check if it does exists
+        $result = $tpl->isExisting();
+        $this->template_objects = $save;
+        return $result;
+    } 
+
+    /**
+     * Returns a single or all global  variables
+     * 
+     * @param object $smarty 
+     * @param string $varname variable name or null
+     * @return string variable value or or array of variables
+     */
+    function getGlobal($varname = null)
+    {
+        if (isset($varname)) {
+            if (isset(self::$global_tpl_vars[$varname])) {
+                return self::$global_tpl_vars[$varname]->value;
+            } else {
+                return '';
+            } 
+        } else {
+            $_result = array();
+            foreach (self::$global_tpl_vars AS $key => $var) {
+                $_result[$key] = $var->value;
+            } 
+            return $_result;
+        } 
+    } 
+
+    /**
+    * Empty cache folder
+    * 
+    * @param integer $exp_time expiration time
+    * @param string $type resource type
+    * @return integer number of cache files deleted
+    */
+    function clearAllCache($exp_time = null, $type = null)
+    { 
+       // load cache resource and call clearAll
+        return $this->loadCacheResource($type)->clearAll($exp_time);
+    }        
+
+    /**
+    * Empty cache for a specific template
+    * 
+    * @param string $template_name template name
+    * @param string $cache_id cache id
+    * @param string $compile_id compile id
+    * @param integer $exp_time expiration time
+    * @param string $type resource type
+    * @return integer number of cache files deleted
+    */
+    function clearCache($template_name, $cache_id = null, $compile_id = null, $exp_time = null, $type = null)
+    { 
+       // load cache resource and call clear
+        return $this->loadCacheResource($type)->clear($template_name, $cache_id, $compile_id, $exp_time);
+    }
+
+    /**
+     * Loads security class and enables security
+     */
+    public function enableSecurity($security_class = null)
+    {
+       if ($security_class instanceof Smarty_Security) {
+                       $this->security_policy = $security_class;
+                       return;
+               }
+       if ($security_class == null) {
+               $security_class = $this->security_class;
+       }
+        if (class_exists($security_class)) {
+            $this->security_policy = new $security_class($this);
+        } else {
+            throw new SmartyException("Security class '$security_class' is not defined");
+        } 
+    } 
+
+    /**
+     * Disable security
+     */
+    public function disableSecurity()
+    {
+       $this->security_policy = null;
+    } 
+
+    /**
+    * Loads cache resource.
+    * 
+    * @param string $type cache resource type
+    * @return object of cache resource
+    */
+    public function loadCacheResource($type = null) {
+        if (!isset($type)) {
+            $type = $this->caching_type;
+        } 
+        if (in_array($type, $this->cache_resource_types)) {
+            $cache_resource_class = 'Smarty_Internal_CacheResource_' . ucfirst($type);
+            return new $cache_resource_class($this);
+        } 
+        else {
+            // try plugins dir
+            $cache_resource_class = 'Smarty_CacheResource_' . ucfirst($type);
+            if ($this->loadPlugin($cache_resource_class)) {
+                return new $cache_resource_class($this);
+            } 
+            else {
+                throw new SmartyException("Unable to load cache resource '{$type}'");
+            } 
+        } 
+    } 
+
+
+    /**
+     * Set template directory
+     * 
+     * @param string $ |array $template_dir folder(s) of template sorces
+     */
+    public function setTemplateDir($template_dir)
+    {
+        $this->template_dir = (array)$template_dir;
+        return;
+    } 
+
+    /**
+     * Adds template directory(s) to existing ones
+     * 
+     * @param string $ |array $template_dir folder(s) of template sources
+     */
+    public function addTemplateDir($template_dir)
+    {
+        $this->template_dir = array_unique(array_merge((array)$this->template_dir, (array)$template_dir));
+        return;
+    } 
+    /**
+     * Adds directory of plugin files
+     * 
+     * @param object $smarty 
+     * @param string $ |array $ plugins folder
+     * @return 
+     */
+    function addPluginsDir($plugins_dir)
+    {
+        $this->plugins_dir = array_unique(array_merge((array)$this->plugins_dir, (array)$plugins_dir));
+        return;
+    } 
+
+
+    /**
+     * return a reference to a registered object
+     * 
+     * @param string $name object name
+     * @return object 
+     */
+    function getRegisteredObject($name)
+    {
+        if (!isset($this->registered_objects[$name]))
+            throw new SmartyException("'$name' is not a registered object");
+
+        if (!is_object($this->registered_objects[$name][0]))
+            throw new SmartyException("registered '$name' is not an object");
+
+        return $this->registered_objects[$name][0];
+    } 
+
+
+    /**
+     * return name of debugging template
+     * 
+     * @return string 
+     */
+    function getDebugTemplate()
+    {
+        return $this->debug_tpl;
+    } 
+
+    /**
+     * set the debug template
+     * 
+     * @param string $tpl_name 
+     * @return bool 
+     */
+    function setDebugTemplate($tpl_name)
+    {
+        return $this->debug_tpl = $tpl_name;
+    } 
+
+    /**
+     * Takes unknown classes and loads plugin files for them
+     * class name format: Smarty_PluginType_PluginName
+     * plugin filename format: plugintype.pluginname.php
+     * 
+     * @param string $plugin_name class plugin name to load
+     * @return string |boolean filepath of loaded file or false
+     */
+    public function loadPlugin($plugin_name, $check = true)
+    { 
+        // if function or class exists, exit silently (already loaded)
+        if ($check && (is_callable($plugin_name) || class_exists($plugin_name, false)))
+            return true; 
+        // Plugin name is expected to be: Smarty_[Type]_[Name]
+        $_plugin_name = strtolower($plugin_name);
+        $_name_parts = explode('_', $_plugin_name, 3); 
+        // class name must have three parts to be valid plugin
+        if (count($_name_parts) < 3 || $_name_parts[0] !== 'smarty') {
+            throw new SmartyException("plugin {$plugin_name} is not a valid name format");
+            return false;
+        } 
+        // if type is "internal", get plugin from sysplugins
+        if ($_name_parts[1] == 'internal') {
+            $file = SMARTY_SYSPLUGINS_DIR . $_plugin_name . '.php';
+            if (file_exists($file)) {
+                require_once($file);
+                return $file;
+            } else {
+                return false;
+            } 
+        } 
+        // plugin filename is expected to be: [type].[name].php
+        $_plugin_filename = "{$_name_parts[1]}.{$_name_parts[2]}.php"; 
+        // loop through plugin dirs and find the plugin
+        foreach((array)$this->plugins_dir as $_plugin_dir) {
+            if (strpos('/\\', substr($_plugin_dir, -1)) === false) {
+                $_plugin_dir .= DS;
+            } 
+            $file = $_plugin_dir . $_plugin_filename;
+            if (file_exists($file)) {
+                require_once($file);
+                return $file;
+            } 
+        } 
+        // no plugin loaded
+        return false;
+    } 
+
+    /**
+    * clean up properties on cloned object
+     */
+    public function __clone()
+    {
+       // clear config vars
+       $this->config_vars = array(); 
+       // clear assigned tpl vars
+       $this->tpl_vars = array();
+       // clear objects for external methods
+       unset($this->register);  
+       unset($this->filter);  
+       }
+
+
+    /**
+     * Handle unknown class methods
+        *
+     * @param string $name unknown methode name
+     * @param array $args aurgument array
+     */
+    public function __call($name, $args)
+    {
+        static $camel_func;
+        if (!isset($camel_func))
+            $camel_func = create_function('$c', 'return "_" . strtolower($c[1]);'); 
+        // see if this is a set/get for a property
+        $first3 = strtolower(substr($name, 0, 3));
+        if (in_array($first3, array('set', 'get')) && substr($name, 3, 1) !== '_') {
+            // try to keep case correct for future PHP 6.0 case-sensitive class methods
+            // lcfirst() not available < PHP 5.3.0, so improvise
+            $property_name = strtolower(substr($name, 3, 1)) . substr($name, 4); 
+            // convert camel case to underscored name
+            $property_name = preg_replace_callback('/([A-Z])/', $camel_func, $property_name);
+            if (!property_exists($this, $property_name)) {
+                throw new SmartyException("property '$property_name' does not exist.");
+                return false;
+            } 
+            if ($first3 == 'get')
+                return $this->$property_name;
+            else
+                return $this->$property_name = $args[0];
+        }
+       // Smarty Backward Compatible wrapper
+               if (strpos($name,'_') !== false) {
+               if (!isset($this->wrapper)) {
+                $this->wrapper = new Smarty_Internal_Wrapper($this);
+               } 
+               return $this->wrapper->convert($name, $args);
+        }
+        // external Smarty methods ?
+        foreach(array('filter','register') as $external) {  
+               if (method_exists("Smarty_Internal_{$external}",$name)) {
+                       if (!isset($this->$external)) {
+                               $class = "Smarty_Internal_{$external}";
+                       $this->$external = new $class($this);
+                       } 
+                       return call_user_func_array(array($this->$external,$name), $args);
+                       }
+               }
+               if (in_array($name,array('clearCompiledTemplate','compileAllTemplates','compileAllConfig','testInstall','getTags'))) {
+                       if (!isset($this->utility)) {
+               $this->utility = new Smarty_Internal_Utility($this);
+               } 
+               return call_user_func_array(array($this->utility,$name), $args);
+               }
+         // PHP4 call to constructor?
+        if (strtolower($name) == 'smarty') {
+            throw new SmartyException('Please use parent::__construct() to call parent constuctor');
+            return false;
+        } 
+        throw new SmartyException("Call of unknown function '$name'.");
+   } 
+} 
+
+/**
+ * Autoloader
+ */
+function smartyAutoload($class)
+{
+    $_class = strtolower($class);
+    if (substr($_class, 0, 16) === 'smarty_internal_' || $_class == 'smarty_security') {
+        include SMARTY_SYSPLUGINS_DIR . $_class . '.php';
+    } 
+} 
+
+/**
+ * Smarty exception class
+ */
+Class SmartyException extends Exception {
+}
+
+/**
+ * Smarty compiler exception class
+ */
+Class SmartyCompilerException extends SmartyException  {
+}
+
+?>
diff --git a/WEB-INF/lib/smarty/debug.tpl b/WEB-INF/lib/smarty/debug.tpl
new file mode 100644 (file)
index 0000000..058c5b2
--- /dev/null
@@ -0,0 +1,133 @@
+{capture name='_smarty_debug' assign=debug_output}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+    <title>Smarty Debug Console</title>
+<style type="text/css">
+{literal}
+body, h1, h2, td, th, p {
+    font-family: sans-serif;
+    font-weight: normal;
+    font-size: 0.9em;
+    margin: 1px;
+    padding: 0;
+}
+
+h1 {
+    margin: 0;
+    text-align: left;
+    padding: 2px;
+    background-color: #f0c040;
+    color:  black;
+    font-weight: bold;
+    font-size: 1.2em;
+ }
+
+h2 {
+    background-color: #9B410E;
+    color: white;
+    text-align: left;
+    font-weight: bold;
+    padding: 2px;
+    border-top: 1px solid black;
+}
+
+body {
+    background: black; 
+}
+
+p, table, div {
+    background: #f0ead8;
+} 
+
+p {
+    margin: 0;
+    font-style: italic;
+    text-align: center;
+}
+
+table {
+    width: 100%;
+}
+
+th, td {
+    font-family: monospace;
+    vertical-align: top;
+    text-align: left;
+    width: 50%;
+}
+
+td {
+    color: green;
+}
+
+.odd {
+    background-color: #eeeeee;
+}
+
+.even {
+    background-color: #fafafa;
+}
+
+.exectime {
+    font-size: 0.8em;
+    font-style: italic;
+}
+
+#table_assigned_vars th {
+    color: blue;
+}
+
+#table_config_vars th {
+    color: maroon;
+}
+{/literal}
+</style>
+</head>
+<body>
+
+<h1>Smarty Debug Console  -  {if isset($template_name)}{$template_name|debug_print_var}{else}Total Time {$execution_time|string_format:"%.5f"}{/if}</h1>
+
+{if !empty($template_data)}
+<h2>included templates &amp; config files (load time in seconds)</h2>
+
+<div>
+{foreach $template_data as $template}
+  <font color=brown>{$template.name}</font>
+  <span class="exectime">
+   (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"})
+  </span>
+  <br>
+{/foreach}
+</div>
+{/if}
+
+<h2>assigned template variables</h2>
+
+<table id="table_assigned_vars">
+    {foreach $assigned_vars as $vars}
+       <tr class="{if $vars@iteration % 2 eq 0}odd{else}even{/if}">   
+       <th>${$vars@key|escape:'html'}</th>
+       <td>{$vars|debug_print_var}</td></tr>
+    {/foreach}
+</table>
+
+<h2>assigned config file variables (outer template scope)</h2>
+
+<table id="table_config_vars">
+    {foreach $config_vars as $vars}
+       <tr class="{if $vars@iteration % 2 eq 0}odd{else}even{/if}">   
+       <th>{$vars@key|escape:'html'}</th>
+       <td>{$vars|debug_print_var}</td></tr>
+    {/foreach}
+
+</table>
+</body>
+</html>
+{/capture}
+<script type="text/javascript">
+{$id = $template_name|default:''|md5}
+    _smarty_console = window.open("","console{$id}","width=680,height=600,resizable,scrollbars=yes");
+    _smarty_console.document.write("{$debug_output|escape:'javascript'}");
+    _smarty_console.document.close();
+</script>
diff --git a/WEB-INF/lib/smarty/plugins/block.php.php b/WEB-INF/lib/smarty/plugins/block.php.php
new file mode 100644 (file)
index 0000000..8fedd8b
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Smarty plugin to execute PHP code
+ * 
+ * @package Smarty
+ * @subpackage PluginsBlock
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty {php}{/php} block plugin
+ * 
+ * @param string $content contents of the block
+ * @param object $template template object
+ * @param boolean $ &$repeat repeat flag
+ * @return string content re-formatted
+ */
+function smarty_block_php($params, $content, $template, &$repeat)
+{ 
+    if (!$template->allow_php_tag) {
+        throw new SmartyException("{php} is deprecated, set allow_php_tag = true to enable");
+    } 
+    eval($content);
+    return '';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/block.textformat.php b/WEB-INF/lib/smarty/plugins/block.textformat.php
new file mode 100644 (file)
index 0000000..517fd62
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Smarty plugin to format text blocks
+ *
+ * @package Smarty
+ * @subpackage PluginsBlock
+ */
+
+/**
+ * Smarty {textformat}{/textformat} block plugin
+ * 
+ * Type:     block function<br>
+ * Name:     textformat<br>
+ * Purpose:  format text a certain way with preset styles
+ *            or custom wrap/indent settings<br>
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.textformat.php {textformat}
+ *       (Smarty online manual)
+ * @param array $params parameters
+ * <pre>
+ * Params:   style: string (email)
+ *            indent: integer (0)
+ *            wrap: integer (80)
+ *            wrap_char string ("\n")
+ *            indent_char: string (" ")
+ *            wrap_boundary: boolean (true)
+ * </pre>
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $content contents of the block
+ * @param object $template template object
+ * @param boolean &$repeat repeat flag
+ * @return string content re-formatted
+ */
+function smarty_block_textformat($params, $content, $template, &$repeat)
+{
+    if (is_null($content)) {
+        return;
+    } 
+
+    $style = null;
+    $indent = 0;
+    $indent_first = 0;
+    $indent_char = ' ';
+    $wrap = 80;
+    $wrap_char = "\n";
+    $wrap_cut = false;
+    $assign = null;
+
+    foreach ($params as $_key => $_val) {
+        switch ($_key) {
+            case 'style':
+            case 'indent_char':
+            case 'wrap_char':
+            case 'assign':
+                $$_key = (string)$_val;
+                break;
+
+            case 'indent':
+            case 'indent_first':
+            case 'wrap':
+                $$_key = (int)$_val;
+                break;
+
+            case 'wrap_cut':
+                $$_key = (bool)$_val;
+                break;
+
+            default:
+                trigger_error("textformat: unknown attribute '$_key'");
+        } 
+    } 
+
+    if ($style == 'email') {
+        $wrap = 72;
+    } 
+    // split into paragraphs
+    $_paragraphs = preg_split('![\r\n][\r\n]!', $content);
+    $_output = '';
+
+    for($_x = 0, $_y = count($_paragraphs); $_x < $_y; $_x++) {
+        if ($_paragraphs[$_x] == '') {
+            continue;
+        } 
+        // convert mult. spaces & special chars to single space
+        $_paragraphs[$_x] = preg_replace(array('!\s+!', '!(^\s+)|(\s+$)!'), array(' ', ''), $_paragraphs[$_x]); 
+        // indent first line
+        if ($indent_first > 0) {
+            $_paragraphs[$_x] = str_repeat($indent_char, $indent_first) . $_paragraphs[$_x];
+        } 
+        // wordwrap sentences
+        $_paragraphs[$_x] = wordwrap($_paragraphs[$_x], $wrap - $indent, $wrap_char, $wrap_cut); 
+        // indent lines
+        if ($indent > 0) {
+            $_paragraphs[$_x] = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraphs[$_x]);
+        } 
+    } 
+    $_output = implode($wrap_char . $wrap_char, $_paragraphs);
+    
+    return $assign ? $template->assign($assign, $_output) : $_output;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.counter.php b/WEB-INF/lib/smarty/plugins/function.counter.php
new file mode 100644 (file)
index 0000000..7c50bd4
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Smarty plugin
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {counter} function plugin
+ *
+ * Type:     function<br>
+ * Name:     counter<br>
+ * Purpose:  print out a counter value
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @link http://smarty.php.net/manual/en/language.function.counter.php {counter}
+ *       (Smarty online manual)
+ * @param array parameters
+ * @param Smarty
+ * @param object $template template object
+ * @return string|null
+ */
+function smarty_function_counter($params, $template)
+{
+    static $counters = array();
+
+    $name = (isset($params['name'])) ? $params['name'] : 'default';
+    if (!isset($counters[$name])) {
+        $counters[$name] = array(
+            'start'=>1,
+            'skip'=>1,
+            'direction'=>'up',
+            'count'=>1
+            );
+    }
+    $counter =& $counters[$name];
+
+    if (isset($params['start'])) {
+        $counter['start'] = $counter['count'] = (int)$params['start'];
+    }
+
+    if (!empty($params['assign'])) {
+        $counter['assign'] = $params['assign'];
+    }
+
+    if (isset($counter['assign'])) {
+        $template->assign($counter['assign'], $counter['count']);
+    }
+    
+    if (isset($params['print'])) {
+        $print = (bool)$params['print'];
+    } else {
+        $print = empty($counter['assign']);
+    }
+
+    if ($print) {
+        $retval = $counter['count'];
+    } else {
+        $retval = null;
+    }
+
+    if (isset($params['skip'])) {
+        $counter['skip'] = $params['skip'];
+    }
+    
+    if (isset($params['direction'])) {
+        $counter['direction'] = $params['direction'];
+    }
+
+    if ($counter['direction'] == "down")
+        $counter['count'] -= $counter['skip'];
+    else
+        $counter['count'] += $counter['skip'];
+    
+    return $retval;
+    
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.cycle.php b/WEB-INF/lib/smarty/plugins/function.cycle.php
new file mode 100644 (file)
index 0000000..98e3e28
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {cycle} function plugin
+ *
+ * Type:     function<br>
+ * Name:     cycle<br>
+ * Date:     May 3, 2002<br>
+ * Purpose:  cycle through given values<br>
+ * Input:
+ *         - name = name of cycle (optional)
+ *         - values = comma separated list of values to cycle,
+ *                    or an array of values to cycle
+ *                    (this can be left out for subsequent calls)
+ *         - reset = boolean - resets given var to true
+ *         - print = boolean - print var or not. default is true
+ *         - advance = boolean - whether or not to advance the cycle
+ *         - delimiter = the value delimiter, default is ","
+ *         - assign = boolean, assigns to template var instead of
+ *                    printed.
+ *
+ * Examples:<br>
+ * <pre>
+ * {cycle values="#eeeeee,#d0d0d0d"}
+ * {cycle name=row values="one,two,three" reset=true}
+ * {cycle name=row}
+ * </pre>
+ * @link http://smarty.php.net/manual/en/language.function.cycle.php {cycle}
+ *       (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author credit to Mark Priatel <mpriatel@rogers.com>
+ * @author credit to Gerard <gerard@interfold.com>
+ * @author credit to Jason Sweat <jsweat_php@yahoo.com>
+ * @version  1.3
+ * @param array
+ * @param object $template template object
+ * @return string|null
+ */
+
+function smarty_function_cycle($params, $template)
+{
+    static $cycle_vars;
+    
+    $name = (empty($params['name'])) ? 'default' : $params['name'];
+    $print = (isset($params['print'])) ? (bool)$params['print'] : true;
+    $advance = (isset($params['advance'])) ? (bool)$params['advance'] : true;
+    $reset = (isset($params['reset'])) ? (bool)$params['reset'] : false;
+            
+    if (!in_array('values', array_keys($params))) {
+        if(!isset($cycle_vars[$name]['values'])) {
+            trigger_error("cycle: missing 'values' parameter");
+            return;
+        }
+    } else {
+        if(isset($cycle_vars[$name]['values'])
+            && $cycle_vars[$name]['values'] != $params['values'] ) {
+            $cycle_vars[$name]['index'] = 0;
+        }
+        $cycle_vars[$name]['values'] = $params['values'];
+    }
+
+    if (isset($params['delimiter'])) {
+        $cycle_vars[$name]['delimiter'] = $params['delimiter'];
+    } elseif (!isset($cycle_vars[$name]['delimiter'])) {
+        $cycle_vars[$name]['delimiter'] = ',';       
+    }
+    
+    if(is_array($cycle_vars[$name]['values'])) {
+        $cycle_array = $cycle_vars[$name]['values'];
+    } else {
+        $cycle_array = explode($cycle_vars[$name]['delimiter'],$cycle_vars[$name]['values']);
+    }
+    
+    if(!isset($cycle_vars[$name]['index']) || $reset ) {
+        $cycle_vars[$name]['index'] = 0;
+    }
+    
+    if (isset($params['assign'])) {
+        $print = false;
+        $template->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]);
+    }
+        
+    if($print) {
+        $retval = $cycle_array[$cycle_vars[$name]['index']];
+    } else {
+        $retval = null;
+    }
+
+    if($advance) {
+        if ( $cycle_vars[$name]['index'] >= count($cycle_array) -1 ) {
+            $cycle_vars[$name]['index'] = 0;
+        } else {
+            $cycle_vars[$name]['index']++;
+        }
+    }
+    
+    return $retval;
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.fetch.php b/WEB-INF/lib/smarty/plugins/function.fetch.php
new file mode 100644 (file)
index 0000000..2b09fb9
--- /dev/null
@@ -0,0 +1,216 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {fetch} plugin
+ *
+ * Type:     function<br>
+ * Name:     fetch<br>
+ * Purpose:  fetch file, web or ftp data and display results
+ * @link http://smarty.php.net/manual/en/language.function.fetch.php {fetch}
+ *       (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @param array $params parameters
+ * @param object $template template object
+ * @return string|null if the assign parameter is passed, Smarty assigns the
+ *                     result to a template variable
+ */
+function smarty_function_fetch($params, $template)
+{
+    if (empty($params['file'])) {
+        trigger_error("[plugin] fetch parameter 'file' cannot be empty",E_USER_NOTICE);
+        return;
+    }
+
+    $content = '';
+    if (isset($template->security_policy) && !preg_match('!^(http|ftp)://!i', $params['file'])) {
+        if(!$template->security_policy->isTrustedResourceDir($params['file'])) {
+            return;
+        }
+        
+        // fetch the file
+        if($fp = @fopen($params['file'],'r')) {
+            while(!feof($fp)) {
+                $content .= fgets ($fp,4096);
+            }
+            fclose($fp);
+        } else {
+            trigger_error('[plugin] fetch cannot read file \'' . $params['file'] . '\'',E_USER_NOTICE);
+            return;
+        }
+    } else {
+        // not a local file
+        if(preg_match('!^http://!i',$params['file'])) {
+            // http fetch
+            if($uri_parts = parse_url($params['file'])) {
+                // set defaults
+                $host = $server_name = $uri_parts['host'];
+                $timeout = 30;
+                $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*";
+                $agent = "Smarty Template Engine ".$template->_version;
+                $referer = "";
+                $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/';
+                $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : '';
+                $_is_proxy = false;
+                if(empty($uri_parts['port'])) {
+                    $port = 80;
+                } else {
+                    $port = $uri_parts['port'];
+                }
+                if(!empty($uri_parts['user'])) {
+                    $user = $uri_parts['user'];
+                }
+                if(!empty($uri_parts['pass'])) {
+                    $pass = $uri_parts['pass'];
+                }
+                // loop through parameters, setup headers
+                foreach($params as $param_key => $param_value) {
+                    switch($param_key) {
+                        case "file":
+                        case "assign":
+                        case "assign_headers":
+                            break;
+                        case "user":
+                            if(!empty($param_value)) {
+                                $user = $param_value;
+                            }
+                            break;
+                        case "pass":
+                            if(!empty($param_value)) {
+                                $pass = $param_value;
+                            }
+                            break;
+                        case "accept":
+                            if(!empty($param_value)) {
+                                $accept = $param_value;
+                            }
+                            break;
+                        case "header":
+                            if(!empty($param_value)) {
+                                if(!preg_match('![\w\d-]+: .+!',$param_value)) {
+                                    trigger_error("[plugin] invalid header format '".$param_value."'",E_USER_NOTICE);
+                                    return;
+                                } else {
+                                    $extra_headers[] = $param_value;
+                                }
+                            }
+                            break;
+                        case "proxy_host":
+                            if(!empty($param_value)) {
+                                $proxy_host = $param_value;
+                            }
+                            break;
+                        case "proxy_port":
+                            if(!preg_match('!\D!', $param_value)) {
+                                $proxy_port = (int) $param_value;
+                            } else {
+                                trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE);
+                                return;
+                            }
+                            break;
+                        case "agent":
+                            if(!empty($param_value)) {
+                                $agent = $param_value;
+                            }
+                            break;
+                        case "referer":
+                            if(!empty($param_value)) {
+                                $referer = $param_value;
+                            }
+                            break;
+                        case "timeout":
+                            if(!preg_match('!\D!', $param_value)) {
+                                $timeout = (int) $param_value;
+                            } else {
+                                trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE);
+                                return;
+                            }
+                            break;
+                        default:
+                            trigger_error("[plugin] unrecognized attribute '".$param_key."'",E_USER_NOTICE);
+                            return;
+                    }
+                }
+                if(!empty($proxy_host) && !empty($proxy_port)) {
+                    $_is_proxy = true;
+                    $fp = fsockopen($proxy_host,$proxy_port,$errno,$errstr,$timeout);
+                } else {
+                    $fp = fsockopen($server_name,$port,$errno,$errstr,$timeout);
+                }
+
+                if(!$fp) {
+                    trigger_error("[plugin] unable to fetch: $errstr ($errno)",E_USER_NOTICE);
+                    return;
+                } else {
+                    if($_is_proxy) {
+                        fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n");
+                    } else {
+                        fputs($fp, "GET $uri HTTP/1.0\r\n");
+                    }
+                    if(!empty($host)) {
+                        fputs($fp, "Host: $host\r\n");
+                    }
+                    if(!empty($accept)) {
+                        fputs($fp, "Accept: $accept\r\n");
+                    }
+                    if(!empty($agent)) {
+                        fputs($fp, "User-Agent: $agent\r\n");
+                    }
+                    if(!empty($referer)) {
+                        fputs($fp, "Referer: $referer\r\n");
+                    }
+                    if(isset($extra_headers) && is_array($extra_headers)) {
+                        foreach($extra_headers as $curr_header) {
+                            fputs($fp, $curr_header."\r\n");
+                        }
+                    }
+                    if(!empty($user) && !empty($pass)) {
+                        fputs($fp, "Authorization: BASIC ".base64_encode("$user:$pass")."\r\n");
+                    }
+
+                    fputs($fp, "\r\n");
+                    while(!feof($fp)) {
+                        $content .= fgets($fp,4096);
+                    }
+                    fclose($fp);
+                    $csplit = preg_split("!\r\n\r\n!",$content,2);
+
+                    $content = $csplit[1];
+
+                    if(!empty($params['assign_headers'])) {
+                        $template->assign($params['assign_headers'],preg_split("!\r\n!",$csplit[0]));
+                    }
+                }
+            } else {
+                trigger_error("[plugin fetch] unable to parse URL, check syntax",E_USER_NOTICE);
+                return;
+            }
+        } else {
+            // ftp fetch
+            if($fp = @fopen($params['file'],'r')) {
+                while(!feof($fp)) {
+                    $content .= fgets ($fp,4096);
+                }
+                fclose($fp);
+            } else {
+                trigger_error('[plugin] fetch cannot read file \'' . $params['file'] .'\'',E_USER_NOTICE);
+                return;
+            }
+        }
+
+    }
+
+
+    if (!empty($params['assign'])) {
+        $template->assign($params['assign'],$content);
+    } else {
+        return $content;
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php b/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php
new file mode 100644 (file)
index 0000000..6a1a3ff
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_checkboxes} function plugin
+ *
+ * File:       function.html_checkboxes.php<br>
+ * Type:       function<br>
+ * Name:       html_checkboxes<br>
+ * Date:       24.Feb.2003<br>
+ * Purpose:    Prints out a list of checkbox input types<br>
+ * Examples:
+ * <pre>
+ * {html_checkboxes values=$ids output=$names}
+ * {html_checkboxes values=$ids name='box' separator='<br>' output=$names}
+ * {html_checkboxes values=$ids checked=$checked separator='<br>' output=$names}
+ * </pre>
+ * @link http://smarty.php.net/manual/en/language.function.html.checkboxes.php {html_checkboxes}
+ *      (Smarty online manual)
+ * @author     Christopher Kvarme <christopher.kvarme@flashjab.com>
+ * @author credits to Monte Ohrt <monte at ohrt dot com>
+ * @version    1.0
+ * @param array $params parameters
+ * Input:<br>
+ *           - name       (optional) - string default "checkbox"
+ *           - values     (required) - array
+ *           - options    (optional) - associative array
+ *           - checked    (optional) - array default not set
+ *           - separator  (optional) - ie <br> or &nbsp;
+ *           - output     (optional) - the output next to each checkbox
+ *           - assign     (optional) - assign the output as an array to this variable
+ * @param object $template template object
+ * @return string
+ * @uses smarty_function_escape_special_chars()
+ */
+function smarty_function_html_checkboxes($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php');
+
+    $name = 'checkbox';
+    $values = null;
+    $options = null;
+    $selected = null;
+    $separator = '';
+    $labels = true;
+    $output = null;
+
+    $extra = '';
+
+    foreach($params as $_key => $_val) {
+        switch($_key) {
+            case 'name':
+            case 'separator':
+                $$_key = $_val;
+                break;
+
+            case 'labels':
+                $$_key = (bool)$_val;
+                break;
+
+            case 'options':
+                $$_key = (array)$_val;
+                break;
+
+            case 'values':
+            case 'output':
+                $$_key = array_values((array)$_val);
+                break;
+
+            case 'checked':
+            case 'selected':
+                $selected = array_map('strval', array_values((array)$_val));
+                break;
+
+            case 'checkboxes':
+                trigger_error('html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', E_USER_WARNING);
+                $options = (array)$_val;
+                break;
+
+            case 'assign':
+                break;
+
+            default:
+                if(!is_array($_val)) {
+                    $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"';
+                } else {
+                    trigger_error("html_checkboxes: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                }
+                break;
+        }
+    }
+
+    if (!isset($options) && !isset($values))
+        return ''; /* raise error here? */
+
+    settype($selected, 'array');
+    $_html_result = array();
+
+    if (isset($options)) {
+
+        foreach ($options as $_key=>$_val)
+            $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels);
+
+
+    } else {
+        foreach ($values as $_i=>$_key) {
+            $_val = isset($output[$_i]) ? $output[$_i] : '';
+            $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels);
+        }
+
+    }
+
+    if(!empty($params['assign'])) {
+        $template->assign($params['assign'], $_html_result);
+    } else {
+        return implode("\n",$_html_result);
+    }
+
+}
+
+function smarty_function_html_checkboxes_output($name, $value, $output, $selected, $extra, $separator, $labels) {
+    $_output = '';
+    if ($labels) $_output .= '<label>';
+    $_output .= '<input type="checkbox" name="'
+        . smarty_function_escape_special_chars($name) . '[]" value="'
+        . smarty_function_escape_special_chars($value) . '"';
+
+    if (in_array((string)$value, $selected)) {
+        $_output .= ' checked="checked"';
+    }
+    $_output .= $extra . ' />' . $output;
+    if ($labels) $_output .= '</label>';
+    $_output .=  $separator;
+
+    return $_output;
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_image.php b/WEB-INF/lib/smarty/plugins/function.html_image.php
new file mode 100644 (file)
index 0000000..abb7b57
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_image} function plugin
+ * 
+ * Type:     function<br>
+ * Name:     html_image<br>
+ * Date:     Feb 24, 2003<br>
+ * Purpose:  format HTML tags for the image<br>
+ * Examples: {html_image file="/images/masthead.gif"}
+ * Output:   <img src="/images/masthead.gif" width=400 height=23>
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.html.image.php {html_image}
+ *      (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author credits to Duda <duda@big.hu> 
+ * @version 1.0
+ * @param array $params parameters
+ * Input:<br>
+ *          - file = file (and path) of image (required)
+ *          - height = image height (optional, default actual height)
+ *          - width = image width (optional, default actual width)
+ *          - basedir = base directory for absolute paths, default
+ *                      is environment variable DOCUMENT_ROOT
+ *          - path_prefix = prefix for path output (optional, default empty)
+ * @param object $template template object
+ * @return string 
+ * @uses smarty_function_escape_special_chars()
+ */
+function smarty_function_html_image($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php');
+    $alt = '';
+    $file = '';
+    $height = '';
+    $width = '';
+    $extra = '';
+    $prefix = '';
+    $suffix = '';
+    $path_prefix = '';
+    $server_vars = $_SERVER;
+    $basedir = isset($server_vars['DOCUMENT_ROOT']) ? $server_vars['DOCUMENT_ROOT'] : '';
+    foreach($params as $_key => $_val) {
+        switch ($_key) {
+            case 'file':
+            case 'height':
+            case 'width':
+            case 'dpi':
+            case 'path_prefix':
+            case 'basedir':
+                $$_key = $_val;
+                break;
+
+            case 'alt':
+                if (!is_array($_val)) {
+                    $$_key = smarty_function_escape_special_chars($_val);
+                } else {
+                    throw new SmartyException ("html_image: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                } 
+                break;
+
+            case 'link':
+            case 'href':
+                $prefix = '<a href="' . $_val . '">';
+                $suffix = '</a>';
+                break;
+
+            default:
+                if (!is_array($_val)) {
+                    $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+                } else {
+                    throw new SmartyException ("html_image: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                } 
+                break;
+        } 
+    } 
+
+    if (empty($file)) {
+        trigger_error("html_image: missing 'file' parameter", E_USER_NOTICE);
+        return;
+    } 
+
+    if (substr($file, 0, 1) == '/') {
+        $_image_path = $basedir . $file;
+    } else {
+        $_image_path = $file;
+    } 
+
+    if (!isset($params['width']) || !isset($params['height'])) {
+        if (!$_image_data = @getimagesize($_image_path)) {
+            if (!file_exists($_image_path)) {
+                trigger_error("html_image: unable to find '$_image_path'", E_USER_NOTICE);
+                return;
+            } else if (!is_readable($_image_path)) {
+                trigger_error("html_image: unable to read '$_image_path'", E_USER_NOTICE);
+                return;
+            } else {
+                trigger_error("html_image: '$_image_path' is not a valid image file", E_USER_NOTICE);
+                return;
+            } 
+        } 
+        if (isset($template->security_policy)) {
+            if (!$template->security_policy->isTrustedResourceDir($_image_path)) {
+                return;
+            } 
+        } 
+
+        if (!isset($params['width'])) {
+            $width = $_image_data[0];
+        } 
+        if (!isset($params['height'])) {
+            $height = $_image_data[1];
+        } 
+    } 
+
+    if (isset($params['dpi'])) {
+        if (strstr($server_vars['HTTP_USER_AGENT'], 'Mac')) {
+            $dpi_default = 72;
+        } else {
+            $dpi_default = 96;
+        } 
+        $_resize = $dpi_default / $params['dpi'];
+        $width = round($width * $_resize);
+        $height = round($height * $_resize);
+    } 
+
+    return $prefix . '<img src="' . $path_prefix . $file . '" alt="' . $alt . '" width="' . $width . '" height="' . $height . '"' . $extra . ' />' . $suffix;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_options.php b/WEB-INF/lib/smarty/plugins/function.html_options.php
new file mode 100644 (file)
index 0000000..7ac0390
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_options} function plugin
+ * 
+ * Type:     function<br>
+ * Name:     html_options<br>
+ * Purpose:  Prints the list of <option> tags generated from
+ *            the passed parameters
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.html.options.php {html_image}
+ *      (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param array $params parameters
+ * Input:<br>
+ *            - name       (optional) - string default "select"
+ *            - values     (required if no options supplied) - array
+ *            - options    (required if no values supplied) - associative array
+ *            - selected   (optional) - string default not set
+ *            - output     (required if not options supplied) - array
+ * @param object $template template object
+ * @return string 
+ * @uses smarty_function_escape_special_chars()
+ */
+function smarty_function_html_options($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php');
+
+    $name = null;
+    $values = null;
+    $options = null;
+    $selected = array();
+    $output = null;
+    $id = null;
+    $class = null;
+
+    $extra = '';
+    $options_extra = '';
+
+    foreach($params as $_key => $_val) {
+        switch ($_key) {
+            case 'name':
+            case 'class':
+            case 'id':
+                $$_key = (string)$_val;
+                break;
+
+            case 'options':
+                $$_key = (array)$_val;
+                break;
+
+            case 'values':
+            case 'output':
+                $$_key = array_values((array)$_val);
+                break;
+
+            case 'selected':
+                $$_key = array_map('strval', array_values((array)$_val));
+                break;
+
+            default:
+                if (!is_array($_val)) {
+                    $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+                } else {
+                    trigger_error("html_options: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                } 
+                break;
+        } 
+    } 
+
+    if (!isset($options) && !isset($values))
+        return '';
+    /* raise error here? */
+
+    $_html_result = '';
+    $_idx = 0;
+
+    if (isset($options)) {
+        foreach ($options as $_key => $_val) {
+          $_html_result .= smarty_function_html_options_optoutput($_key, $_val, $selected, $id, $class, $_idx);
+        }
+    } else {
+        foreach ($values as $_i => $_key) {
+            $_val = isset($output[$_i]) ? $output[$_i] : '';
+            $_html_result .= smarty_function_html_options_optoutput($_key, $_val, $selected, $id, $class, $_idx);
+        } 
+    } 
+
+    if (!empty($name)) {
+        $_html_class = !empty($class) ? ' class="'.$class.'"' : '';
+        $_html_id = !empty($id) ? ' id="'.$id.'"' : '';
+        $_html_result = '<select name="' . $name . '"' . $_html_class . $_html_id . $extra . '>' . "\n" . $_html_result . '</select>' . "\n";
+    } 
+
+    return $_html_result;
+} 
+
+function smarty_function_html_options_optoutput($key, $value, $selected, $id, $class, &$idx)
+{
+    if (!is_array($value)) {
+        $_html_result = '<option value="' .
+        smarty_function_escape_special_chars($key) . '"';
+        if (in_array((string)$key, $selected))
+            $_html_result .= ' selected="selected"';
+        $_html_class = !empty($class) ? ' class="'.$class.' option"' : '';
+        $_html_id = !empty($id) ? ' id="'.$id.'-'.$idx.'"' : '';
+        $_html_result .= $_html_class . $_html_id . '>' . smarty_function_escape_special_chars($value) . '</option>' . "\n";
+        $idx++;
+    } else {
+        $_idx = 0;
+        $_html_result = smarty_function_html_options_optgroup($key, $value, $selected, $id.'-'.$idx, $class, $_idx);
+        $idx++;
+    }
+    return $_html_result;
+} 
+
+function smarty_function_html_options_optgroup($key, $values, $selected, $id, $class, &$idx)
+{
+    $optgroup_html = '<optgroup label="' . smarty_function_escape_special_chars($key) . '">' . "\n";
+    foreach ($values as $key => $value) {
+        $optgroup_html .= smarty_function_html_options_optoutput($key, $value, $selected, $id, $class, $idx);
+    } 
+    $optgroup_html .= "</optgroup>\n";
+    return $optgroup_html;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_radios.php b/WEB-INF/lib/smarty/plugins/function.html_radios.php
new file mode 100644 (file)
index 0000000..c6b27ed
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_radios} function plugin
+ * 
+ * File:       function.html_radios.php<br>
+ * Type:       function<br>
+ * Name:       html_radios<br>
+ * Date:       24.Feb.2003<br>
+ * Purpose:    Prints out a list of radio input types<br>
+ * Examples:
+ * <pre>
+ * {html_radios values=$ids output=$names}
+ * {html_radios values=$ids name='box' separator='<br>' output=$names}
+ * {html_radios values=$ids checked=$checked separator='<br>' output=$names}
+ * </pre>
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.html.radios.php {html_radios}
+ *      (Smarty online manual)
+ * @author Christopher Kvarme <christopher.kvarme@flashjab.com> 
+ * @author credits to Monte Ohrt <monte at ohrt dot com> 
+ * @version 1.0
+ * @param array $params parameters
+ * Input:<br>
+ *            - name       (optional) - string default "radio"
+ *            - values     (required) - array
+ *            - options    (optional) - associative array
+ *            - checked    (optional) - array default not set
+ *            - separator  (optional) - ie <br> or &nbsp;
+ *            - output     (optional) - the output next to each radio button
+ *            - assign     (optional) - assign the output as an array to this variable
+ * @param object $template template object
+ * @return string 
+ * @uses smarty_function_escape_special_chars()
+ */
+function smarty_function_html_radios($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php');
+
+    $name = 'radio';
+    $values = null;
+    $options = null;
+    $selected = null;
+    $separator = '';
+    $labels = true;
+    $label_ids = false;
+    $output = null;
+    $extra = '';
+
+    foreach($params as $_key => $_val) {
+        switch ($_key) {
+            case 'name':
+            case 'separator':
+                $$_key = (string)$_val;
+                break;
+
+            case 'checked':
+            case 'selected':
+                if (is_array($_val)) {
+                    trigger_error('html_radios: the "' . $_key . '" attribute cannot be an array', E_USER_WARNING);
+                } else {
+                    $selected = (string)$_val;
+                } 
+                break;
+
+            case 'labels':
+            case 'label_ids':
+                $$_key = (bool)$_val;
+                break;
+
+            case 'options':
+                $$_key = (array)$_val;
+                break;
+
+            case 'values':
+            case 'output':
+                $$_key = array_values((array)$_val);
+                break;
+
+            case 'radios':
+                trigger_error('html_radios: the use of the "radios" attribute is deprecated, use "options" instead', E_USER_WARNING);
+                $options = (array)$_val;
+                break;
+
+            case 'assign':
+                break;
+
+            default:
+                if (!is_array($_val)) {
+                    $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+                } else {
+                    trigger_error("html_radios: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                } 
+                break;
+        } 
+    } 
+
+    if (!isset($options) && !isset($values))
+        return '';
+    /* raise error here? */
+
+    $_html_result = array();
+
+    if (isset($options)) {
+        foreach ($options as $_key => $_val)
+        $_html_result[] = smarty_function_html_radios_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids);
+    } else {
+        foreach ($values as $_i => $_key) {
+            $_val = isset($output[$_i]) ? $output[$_i] : '';
+            $_html_result[] = smarty_function_html_radios_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids);
+        } 
+    } 
+
+    if (!empty($params['assign'])) {
+        $template->assign($params['assign'], $_html_result);
+    } else {
+        return implode("\n", $_html_result);
+    } 
+} 
+
+function smarty_function_html_radios_output($name, $value, $output, $selected, $extra, $separator, $labels, $label_ids)
+{
+    $_output = '';
+    if ($labels) {
+        if ($label_ids) {
+            $_id = smarty_function_escape_special_chars(preg_replace('![^\w\-\.]!', '_', $name . '_' . $value));
+            $_output .= '<label for="' . $_id . '">';
+        } else {
+            $_output .= '<label>';
+        } 
+    } 
+    $_output .= '<input type="radio" name="'
+     . smarty_function_escape_special_chars($name) . '" value="'
+     . smarty_function_escape_special_chars($value) . '"';
+
+    if ($labels && $label_ids) $_output .= ' id="' . $_id . '"';
+
+    if ((string)$value == $selected) {
+        $_output .= ' checked="checked"';
+    } 
+    $_output .= $extra . ' />' . $output;
+    if ($labels) $_output .= '</label>';
+    $_output .= $separator;
+
+    return $_output;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_select_date.php b/WEB-INF/lib/smarty/plugins/function.html_select_date.php
new file mode 100644 (file)
index 0000000..1d57fdc
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_select_date} plugin
+ * 
+ * Type:     function<br>
+ * Name:     html_select_date<br>
+ * Purpose:  Prints the dropdowns for date selection.
+ * 
+ * ChangeLog:<br>
+ *            - 1.0 initial release
+ *            - 1.1 added support for +/- N syntax for begin
+ *                 and end year values. (Monte)
+ *            - 1.2 added support for yyyy-mm-dd syntax for
+ *                 time value. (Jan Rosier)
+ *            - 1.3 added support for choosing format for
+ *                 month values (Gary Loescher)
+ *            - 1.3.1 added support for choosing format for
+ *                 day values (Marcus Bointon)
+ *            - 1.3.2 support negative timestamps, force year
+ *              dropdown to include given date unless explicitly set (Monte)
+ *            - 1.3.4 fix behaviour of 0000-00-00 00:00:00 dates to match that
+ *              of 0000-00-00 dates (cybot, boots)
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.html.select.date.php {html_select_date}
+ *      (Smarty online manual)
+ * @version 1.3.4
+ * @author Andrei Zmievski 
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param array $params parameters
+ * @param object $template template object
+ * @return string 
+ */
+function smarty_function_html_select_date($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php');
+    require_once(SMARTY_PLUGINS_DIR . 'shared.make_timestamp.php');
+    require_once(SMARTY_PLUGINS_DIR . 'function.html_options.php');
+
+    /* Default values. */
+    $prefix = "Date_";
+    $start_year = strftime("%Y");
+    $end_year = $start_year;
+    $display_days = true;
+    $display_months = true;
+    $display_years = true;
+    $month_format = "%B";
+    /* Write months as numbers by default  GL */
+    $month_value_format = "%m";
+    $day_format = "%02d";
+    /* Write day values using this format MB */
+    $day_value_format = "%d";
+    $year_as_text = false;
+    /* Display years in reverse order? Ie. 2000,1999,.... */
+    $reverse_years = false;
+    /* Should the select boxes be part of an array when returned from PHP?
+       e.g. setting it to "birthday", would create "birthday[Day]",
+       "birthday[Month]" & "birthday[Year]". Can be combined with prefix */
+    $field_array = null;
+    /* <select size>'s of the different <select> tags.
+       If not set, uses default dropdown. */
+    $day_size = null;
+    $month_size = null;
+    $year_size = null;
+    /* Unparsed attributes common to *ALL* the <select>/<input> tags.
+       An example might be in the template: all_extra ='class ="foo"'. */
+    $all_extra = null;
+    /* Separate attributes for the tags. */
+    $day_extra = null;
+    $month_extra = null;
+    $year_extra = null;
+    /* Order in which to display the fields.
+       "D" -> day, "M" -> month, "Y" -> year. */
+    $field_order = 'MDY';
+    /* String printed between the different fields. */
+    $field_separator = "\n";
+    $time = time();
+    $all_empty = null;
+    $day_empty = null;
+    $month_empty = null;
+    $year_empty = null;
+    $extra_attrs = '';
+
+    foreach ($params as $_key => $_value) {
+        switch ($_key) {
+            case 'prefix':
+            case 'time':
+            case 'start_year':
+            case 'end_year':
+            case 'month_format':
+            case 'day_format':
+            case 'day_value_format':
+            case 'field_array':
+            case 'day_size':
+            case 'month_size':
+            case 'year_size':
+            case 'all_extra':
+            case 'day_extra':
+            case 'month_extra':
+            case 'year_extra':
+            case 'field_order':
+            case 'field_separator':
+            case 'month_value_format':
+            case 'month_empty':
+            case 'day_empty':
+            case 'year_empty':
+                $$_key = (string)$_value;
+                break;
+
+            case 'all_empty':
+                $$_key = (string)$_value;
+                $day_empty = $month_empty = $year_empty = $all_empty;
+                break;
+
+            case 'display_days':
+            case 'display_months':
+            case 'display_years':
+            case 'year_as_text':
+            case 'reverse_years':
+                $$_key = (bool)$_value;
+                break;
+
+            default:
+                if (!is_array($_value)) {
+                    $extra_attrs .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_value) . '"';
+                } else {
+                    trigger_error("html_select_date: extra attribute '$_key' cannot be an array", E_USER_NOTICE);
+                } 
+                break;
+        } 
+    } 
+
+    if (preg_match('!^-\d+$!', $time)) {
+        // negative timestamp, use date()
+        $time = date('Y-m-d', $time);
+    } 
+    // If $time is not in format yyyy-mm-dd
+    if (preg_match('/^(\d{0,4}-\d{0,2}-\d{0,2})/', $time, $found)) {
+        $time = $found[1];
+    } else {
+        // use smarty_make_timestamp to get an unix timestamp and
+        // strftime to make yyyy-mm-dd
+        $time = strftime('%Y-%m-%d', smarty_make_timestamp($time));
+    } 
+    // Now split this in pieces, which later can be used to set the select
+    $time = explode("-", $time); 
+    // make syntax "+N" or "-N" work with start_year and end_year
+    if (preg_match('!^(\+|\-)\s*(\d+)$!', $end_year, $match)) {
+        if ($match[1] == '+') {
+            $end_year = strftime('%Y') + $match[2];
+        } else {
+            $end_year = strftime('%Y') - $match[2];
+        } 
+    } 
+    if (preg_match('!^(\+|\-)\s*(\d+)$!', $start_year, $match)) {
+        if ($match[1] == '+') {
+            $start_year = strftime('%Y') + $match[2];
+        } else {
+            $start_year = strftime('%Y') - $match[2];
+        } 
+    } 
+    if (strlen($time[0]) > 0) {
+        if ($start_year > $time[0] && !isset($params['start_year'])) {
+            // force start year to include given date if not explicitly set
+            $start_year = $time[0];
+        } 
+        if ($end_year < $time[0] && !isset($params['end_year'])) {
+            // force end year to include given date if not explicitly set
+            $end_year = $time[0];
+        } 
+    } 
+
+    $field_order = strtoupper($field_order);
+
+    $html_result = $month_result = $day_result = $year_result = "";
+
+    $field_separator_count = -1;
+    if ($display_months) {
+        $field_separator_count++;
+        $month_names = array();
+        $month_values = array();
+        if (isset($month_empty)) {
+            $month_names[''] = $month_empty;
+            $month_values[''] = '';
+        } 
+        for ($i = 1; $i <= 12; $i++) {
+            $month_names[$i] = strftime($month_format, mktime(0, 0, 0, $i, 1, 2000));
+            $month_values[$i] = strftime($month_value_format, mktime(0, 0, 0, $i, 1, 2000));
+        } 
+
+        $month_result .= '<select name=';
+        if (null !== $field_array) {
+            $month_result .= '"' . $field_array . '[' . $prefix . 'Month]"';
+        } else {
+            $month_result .= '"' . $prefix . 'Month"';
+        } 
+        if (null !== $month_size) {
+            $month_result .= ' size="' . $month_size . '"';
+        } 
+        if (null !== $month_extra) {
+            $month_result .= ' ' . $month_extra;
+        } 
+        if (null !== $all_extra) {
+            $month_result .= ' ' . $all_extra;
+        } 
+        $month_result .= $extra_attrs . '>' . "\n";
+
+        $month_result .= smarty_function_html_options(array('output' => $month_names,
+                'values' => $month_values,
+                'selected' => (int)$time[1] ? strftime($month_value_format, mktime(0, 0, 0, (int)$time[1], 1, 2000)) : '',
+                'print_result' => false),
+                 $template);
+        $month_result .= '</select>';
+    } 
+
+    if ($display_days) {
+        $field_separator_count++;
+        $days = array();
+        if (isset($day_empty)) {
+            $days[''] = $day_empty;
+            $day_values[''] = '';
+        } 
+        for ($i = 1; $i <= 31; $i++) {
+            $days[] = sprintf($day_format, $i);
+            $day_values[] = sprintf($day_value_format, $i);
+        } 
+
+        $day_result .= '<select name=';
+        if (null !== $field_array) {
+            $day_result .= '"' . $field_array . '[' . $prefix . 'Day]"';
+        } else {
+            $day_result .= '"' . $prefix . 'Day"';
+        } 
+        if (null !== $day_size) {
+            $day_result .= ' size="' . $day_size . '"';
+        } 
+        if (null !== $all_extra) {
+            $day_result .= ' ' . $all_extra;
+        } 
+        if (null !== $day_extra) {
+            $day_result .= ' ' . $day_extra;
+        } 
+        $day_result .= $extra_attrs . '>' . "\n";
+        $day_result .= smarty_function_html_options(array('output' => $days,
+                'values' => $day_values,
+                'selected' => $time[2],
+                'print_result' => false),
+             $template);
+        $day_result .= '</select>';
+    } 
+
+    if ($display_years) {
+        $field_separator_count++;
+        if (null !== $field_array) {
+            $year_name = $field_array . '[' . $prefix . 'Year]';
+        } else {
+            $year_name = $prefix . 'Year';
+        } 
+        if ($year_as_text) {
+            $year_result .= '<input type="text" name="' . $year_name . '" value="' . $time[0] . '" size="4" maxlength="4"';
+            if (null !== $all_extra) {
+                $year_result .= ' ' . $all_extra;
+            } 
+            if (null !== $year_extra) {
+                $year_result .= ' ' . $year_extra;
+            } 
+            $year_result .= ' />';
+        } else {
+            $years = range((int)$start_year, (int)$end_year);
+            if ($reverse_years) {
+                rsort($years, SORT_NUMERIC);
+            } else {
+                sort($years, SORT_NUMERIC);
+            } 
+            $yearvals = $years;
+            if (isset($year_empty)) {
+                array_unshift($years, $year_empty);
+                array_unshift($yearvals, '');
+            } 
+            $year_result .= '<select name="' . $year_name . '"';
+            if (null !== $year_size) {
+                $year_result .= ' size="' . $year_size . '"';
+            } 
+            if (null !== $all_extra) {
+                $year_result .= ' ' . $all_extra;
+            } 
+            if (null !== $year_extra) {
+                $year_result .= ' ' . $year_extra;
+            } 
+            $year_result .= $extra_attrs . '>' . "\n";
+            $year_result .= smarty_function_html_options(array('output' => $years,
+                    'values' => $yearvals,
+                    'selected' => $time[0],
+                    'print_result' => false),
+                   $template);
+            $year_result .= '</select>';
+        } 
+    } 
+    // Loop thru the field_order field
+    for ($i = 0; $i <= 2; $i++) {
+        $c = substr($field_order, $i, 1);
+        switch ($c) {
+            case 'D':
+                $html_result .= $day_result;
+                break;
+
+            case 'M':
+                $html_result .= $month_result;
+                break;
+
+            case 'Y':
+                $html_result .= $year_result;
+                break;
+        } 
+        // Add the field seperator
+        if ($i < $field_separator_count) {
+            $html_result .= $field_separator;
+        } 
+    } 
+
+    return $html_result;
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_select_time.php b/WEB-INF/lib/smarty/plugins/function.html_select_time.php
new file mode 100644 (file)
index 0000000..ddde4f5
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_select_time} function plugin
+ * 
+ * Type:     function<br>
+ * Name:     html_select_time<br>
+ * Purpose:  Prints the dropdowns for time selection
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.html.select.time.php {html_select_time}
+ *          (Smarty online manual)
+ * @author Roberto Berto <roberto@berto.net> 
+ * @credits Monte Ohrt <monte AT ohrt DOT com>
+ * @param array $params parameters
+ * @param object $template template object
+ * @return string 
+ * @uses smarty_make_timestamp()
+ */
+function smarty_function_html_select_time($params, $template)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.make_timestamp.php');
+    require_once(SMARTY_PLUGINS_DIR . 'function.html_options.php');
+
+    /* Default values. */
+    $prefix = "Time_";
+    $time = time();
+    $display_hours = true;
+    $display_minutes = true;
+    $display_seconds = true;
+    $display_meridian = true;
+    $use_24_hours = true;
+    $minute_interval = 1;
+    $second_interval = 1;
+    /* Should the select boxes be part of an array when returned from PHP?
+       e.g. setting it to "birthday", would create "birthday[Hour]",
+       "birthday[Minute]", "birthday[Seconds]" & "birthday[Meridian]".
+       Can be combined with prefix. */
+    $field_array = null;
+    $all_extra = null;
+    $hour_extra = null;
+    $minute_extra = null;
+    $second_extra = null;
+    $meridian_extra = null;
+
+    foreach ($params as $_key => $_value) {
+        switch ($_key) {
+            case 'prefix':
+            case 'time':
+            case 'field_array':
+            case 'all_extra':
+            case 'hour_extra':
+            case 'minute_extra':
+            case 'second_extra':
+            case 'meridian_extra':
+                $$_key = (string)$_value;
+                break;
+
+            case 'display_hours':
+            case 'display_minutes':
+            case 'display_seconds':
+            case 'display_meridian':
+            case 'use_24_hours':
+                $$_key = (bool)$_value;
+                break;
+
+            case 'minute_interval':
+            case 'second_interval':
+                $$_key = (int)$_value;
+                break;
+
+            default:
+                trigger_error("[html_select_time] unknown parameter $_key", E_USER_WARNING);
+        } 
+    } 
+
+    $time = smarty_make_timestamp($time);
+
+    $html_result = '';
+
+    if ($display_hours) {
+        $hours = $use_24_hours ? range(0, 23) : range(1, 12);
+        $hour_fmt = $use_24_hours ? '%H' : '%I';
+        for ($i = 0, $for_max = count($hours); $i < $for_max; $i++)
+        $hours[$i] = sprintf('%02d', $hours[$i]);
+        $html_result .= '<select name=';
+        if (null !== $field_array) {
+            $html_result .= '"' . $field_array . '[' . $prefix . 'Hour]"';
+        } else {
+            $html_result .= '"' . $prefix . 'Hour"';
+        } 
+        if (null !== $hour_extra) {
+            $html_result .= ' ' . $hour_extra;
+        } 
+        if (null !== $all_extra) {
+            $html_result .= ' ' . $all_extra;
+        } 
+        $html_result .= '>' . "\n";
+        $html_result .= smarty_function_html_options(array('output' => $hours,
+                'values' => $hours,
+                'selected' => strftime($hour_fmt, $time),
+                'print_result' => false),
+            $template);
+        $html_result .= "</select>\n";
+    } 
+
+    if ($display_minutes) {
+        $all_minutes = range(0, 59);
+        for ($i = 0, $for_max = count($all_minutes); $i < $for_max; $i += $minute_interval)
+        $minutes[] = sprintf('%02d', $all_minutes[$i]);
+        $selected = intval(floor(strftime('%M', $time) / $minute_interval) * $minute_interval);
+        $html_result .= '<select name=';
+        if (null !== $field_array) {
+            $html_result .= '"' . $field_array . '[' . $prefix . 'Minute]"';
+        } else {
+            $html_result .= '"' . $prefix . 'Minute"';
+        } 
+        if (null !== $minute_extra) {
+            $html_result .= ' ' . $minute_extra;
+        } 
+        if (null !== $all_extra) {
+            $html_result .= ' ' . $all_extra;
+        } 
+        $html_result .= '>' . "\n";
+
+        $html_result .= smarty_function_html_options(array('output' => $minutes,
+                'values' => $minutes,
+                'selected' => $selected,
+                'print_result' => false),
+              $template);
+        $html_result .= "</select>\n";
+    } 
+
+    if ($display_seconds) {
+        $all_seconds = range(0, 59);
+        for ($i = 0, $for_max = count($all_seconds); $i < $for_max; $i += $second_interval)
+        $seconds[] = sprintf('%02d', $all_seconds[$i]);
+        $selected = intval(floor(strftime('%S', $time) / $second_interval) * $second_interval);
+        $html_result .= '<select name=';
+        if (null !== $field_array) {
+            $html_result .= '"' . $field_array . '[' . $prefix . 'Second]"';
+        } else {
+            $html_result .= '"' . $prefix . 'Second"';
+        } 
+
+        if (null !== $second_extra) {
+            $html_result .= ' ' . $second_extra;
+        } 
+        if (null !== $all_extra) {
+            $html_result .= ' ' . $all_extra;
+        } 
+        $html_result .= '>' . "\n";
+
+        $html_result .= smarty_function_html_options(array('output' => $seconds,
+                'values' => $seconds,
+                'selected' => $selected,
+                'print_result' => false),
+             $template);
+        $html_result .= "</select>\n";
+    } 
+
+    if ($display_meridian && !$use_24_hours) {
+        $html_result .= '<select name=';
+        if (null !== $field_array) {
+            $html_result .= '"' . $field_array . '[' . $prefix . 'Meridian]"';
+        } else {
+            $html_result .= '"' . $prefix . 'Meridian"';
+        } 
+
+        if (null !== $meridian_extra) {
+            $html_result .= ' ' . $meridian_extra;
+        } 
+        if (null !== $all_extra) {
+            $html_result .= ' ' . $all_extra;
+        } 
+        $html_result .= '>' . "\n";
+
+        $html_result .= smarty_function_html_options(array('output' => array('AM', 'PM'),
+                'values' => array('am', 'pm'),
+                'selected' => strtolower(strftime('%p', $time)),
+                'print_result' => false),
+            $template);
+        $html_result .= "</select>\n";
+    } 
+
+    return $html_result;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.html_table.php b/WEB-INF/lib/smarty/plugins/function.html_table.php
new file mode 100644 (file)
index 0000000..7986a9b
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {html_table} function plugin
+ * 
+ * Type:     function<br>
+ * Name:     html_table<br>
+ * Date:     Feb 17, 2003<br>
+ * Purpose:  make an html table from an array of data<br>
+ * 
+ * 
+ * Examples:
+ * <pre>
+ * {table loop=$data}
+ * {table loop=$data cols=4 tr_attr='"bgcolor=red"'}
+ * {table loop=$data cols="first,second,third" tr_attr=$colors}
+ * </pre>
+ * 
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author credit to Messju Mohr <messju at lammfellpuschen dot de> 
+ * @author credit to boots <boots dot smarty at yahoo dot com> 
+ * @version 1.1
+ * @link http://smarty.php.net/manual/en/language.function.html.table.php {html_table}
+ *          (Smarty online manual)
+ * @param array $params parameters
+ * Input:<br>
+ *          - loop = array to loop through
+ *          - cols = number of columns, comma separated list of column names
+ *                   or array of column names
+ *          - rows = number of rows
+ *          - table_attr = table attributes
+ *          - th_attr = table heading attributes (arrays are cycled)
+ *          - tr_attr = table row attributes (arrays are cycled)
+ *          - td_attr = table cell attributes (arrays are cycled)
+ *          - trailpad = value to pad trailing cells with
+ *          - caption = text for caption element 
+ *          - vdir = vertical direction (default: "down", means top-to-bottom)
+ *          - hdir = horizontal direction (default: "right", means left-to-right)
+ *          - inner = inner loop (default "cols": print $loop line by line,
+ *                    $loop will be printed column by column otherwise)
+ * @param object $template template object
+ * @return string 
+ */
+function smarty_function_html_table($params, $template)
+{
+    $table_attr = 'border="1"';
+    $tr_attr = '';
+    $th_attr = '';
+    $td_attr = '';
+    $cols = $cols_count = 3;
+    $rows = 3;
+    $trailpad = '&nbsp;';
+    $vdir = 'down';
+    $hdir = 'right';
+    $inner = 'cols';
+    $caption = '';
+    $loop = null;
+
+    if (!isset($params['loop'])) {
+        trigger_error("html_table: missing 'loop' parameter",E_USER_WARNING);
+        return;
+    } 
+
+    foreach ($params as $_key => $_value) {
+        switch ($_key) {
+            case 'loop':
+                $$_key = (array)$_value;
+                break;
+
+            case 'cols':
+                if (is_array($_value) && !empty($_value)) {
+                    $cols = $_value;
+                    $cols_count = count($_value);
+                } elseif (!is_numeric($_value) && is_string($_value) && !empty($_value)) {
+                    $cols = explode(',', $_value);
+                    $cols_count = count($cols);
+                } elseif (!empty($_value)) {
+                    $cols_count = (int)$_value;
+                } else {
+                    $cols_count = $cols;
+                } 
+                break;
+
+            case 'rows':
+                $$_key = (int)$_value;
+                break;
+
+            case 'table_attr':
+            case 'trailpad':
+            case 'hdir':
+            case 'vdir':
+            case 'inner':
+            case 'caption':
+                $$_key = (string)$_value;
+                break;
+
+            case 'tr_attr':
+            case 'td_attr':
+            case 'th_attr':
+                $$_key = $_value;
+                break;
+        } 
+    } 
+
+    $loop_count = count($loop);
+    if (empty($params['rows'])) {
+        /* no rows specified */
+        $rows = ceil($loop_count / $cols_count);
+    } elseif (empty($params['cols'])) {
+        if (!empty($params['rows'])) {
+            /* no cols specified, but rows */
+            $cols_count = ceil($loop_count / $rows);
+        } 
+    } 
+
+    $output = "<table $table_attr>\n";
+
+    if (!empty($caption)) {
+        $output .= '<caption>' . $caption . "</caption>\n";
+    } 
+
+    if (is_array($cols)) {
+        $cols = ($hdir == 'right') ? $cols : array_reverse($cols);
+        $output .= "<thead><tr>\n";
+
+        for ($r = 0; $r < $cols_count; $r++) {
+            $output .= '<th' . smarty_function_html_table_cycle('th', $th_attr, $r) . '>';
+            $output .= $cols[$r];
+            $output .= "</th>\n";
+        } 
+        $output .= "</tr></thead>\n";
+    } 
+
+    $output .= "<tbody>\n";
+    for ($r = 0; $r < $rows; $r++) {
+        $output .= "<tr" . smarty_function_html_table_cycle('tr', $tr_attr, $r) . ">\n";
+        $rx = ($vdir == 'down') ? $r * $cols_count : ($rows-1 - $r) * $cols_count;
+
+        for ($c = 0; $c < $cols_count; $c++) {
+            $x = ($hdir == 'right') ? $rx + $c : $rx + $cols_count-1 - $c;
+            if ($inner != 'cols') {
+                /* shuffle x to loop over rows*/
+                $x = floor($x / $cols_count) + ($x % $cols_count) * $rows;
+            } 
+
+            if ($x < $loop_count) {
+                $output .= "<td" . smarty_function_html_table_cycle('td', $td_attr, $c) . ">" . $loop[$x] . "</td>\n";
+            } else {
+                $output .= "<td" . smarty_function_html_table_cycle('td', $td_attr, $c) . ">$trailpad</td>\n";
+            } 
+        } 
+        $output .= "</tr>\n";
+    } 
+    $output .= "</tbody>\n";
+    $output .= "</table>\n";
+
+    return $output;
+} 
+
+function smarty_function_html_table_cycle($name, $var, $no)
+{
+    if (!is_array($var)) {
+        $ret = $var;
+    } else {
+        $ret = $var[$no % count($var)];
+    } 
+
+    return ($ret) ? ' ' . $ret : '';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.mailto.php b/WEB-INF/lib/smarty/plugins/function.mailto.php
new file mode 100644 (file)
index 0000000..976218f
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {mailto} function plugin
+ * 
+ * Type:     function<br>
+ * Name:     mailto<br>
+ * Date:     May 21, 2002
+ * Purpose:  automate mailto address link creation, and optionally
+ *            encode them.<br>
+ * 
+ * Examples:
+ * <pre>
+ * {mailto address="me@domain.com"}
+ * {mailto address="me@domain.com" encode="javascript"}
+ * {mailto address="me@domain.com" encode="hex"}
+ * {mailto address="me@domain.com" subject="Hello to you!"}
+ * {mailto address="me@domain.com" cc="you@domain.com,they@domain.com"}
+ * {mailto address="me@domain.com" extra='class="mailto"'}
+ * </pre>
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.mailto.php {mailto}
+ *          (Smarty online manual)
+ * @version 1.2
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author credits to Jason Sweat (added cc, bcc and subject functionality) 
+ * @param array $params parameters
+ * Input:<br>
+ *          - address = e-mail address
+ *          - text = (optional) text to display, default is address
+ *          - encode = (optional) can be one of:
+ *                 * none : no encoding (default)
+ *                 * javascript : encode with javascript
+ *                 * javascript_charcode : encode with javascript charcode
+ *                 * hex : encode with hexidecimal (no javascript)
+ *          - cc = (optional) address(es) to carbon copy
+ *          - bcc = (optional) address(es) to blind carbon copy
+ *          - subject = (optional) e-mail subject
+ *          - newsgroups = (optional) newsgroup(s) to post to
+ *          - followupto = (optional) address(es) to follow up to
+ *          - extra = (optional) extra tags for the href link
+ * @param object $template template object
+ * @return string 
+ */
+function smarty_function_mailto($params, $template)
+{
+    $extra = '';
+
+    if (empty($params['address'])) {
+        trigger_error("mailto: missing 'address' parameter",E_USER_WARNING);
+        return;
+    } else {
+        $address = $params['address'];
+    } 
+
+    $text = $address; 
+    // netscape and mozilla do not decode %40 (@) in BCC field (bug?)
+    // so, don't encode it.
+    $search = array('%40', '%2C');
+    $replace = array('@', ',');
+    $mail_parms = array();
+    foreach ($params as $var => $value) {
+        switch ($var) {
+            case 'cc':
+            case 'bcc':
+            case 'followupto':
+                if (!empty($value))
+                    $mail_parms[] = $var . '=' . str_replace($search, $replace, rawurlencode($value));
+                break;
+
+            case 'subject':
+            case 'newsgroups':
+                $mail_parms[] = $var . '=' . rawurlencode($value);
+                break;
+
+            case 'extra':
+            case 'text':
+                $$var = $value;
+
+            default:
+        } 
+    } 
+
+    $mail_parm_vals = '';
+    for ($i = 0; $i < count($mail_parms); $i++) {
+        $mail_parm_vals .= (0 == $i) ? '?' : '&';
+        $mail_parm_vals .= $mail_parms[$i];
+    } 
+    $address .= $mail_parm_vals;
+
+    $encode = (empty($params['encode'])) ? 'none' : $params['encode'];
+    if (!in_array($encode, array('javascript', 'javascript_charcode', 'hex', 'none'))) {
+        trigger_error("mailto: 'encode' parameter must be none, javascript or hex",E_USER_WARNING);
+        return;
+    } 
+
+    if ($encode == 'javascript') {
+        $string = 'document.write(\'<a href="mailto:' . $address . '" ' . $extra . '>' . $text . '</a>\');';
+
+        $js_encode = '';
+        for ($x = 0; $x < strlen($string); $x++) {
+            $js_encode .= '%' . bin2hex($string[$x]);
+        } 
+
+        return '<script type="text/javascript">eval(unescape(\'' . $js_encode . '\'))</script>';
+    } elseif ($encode == 'javascript_charcode') {
+        $string = '<a href="mailto:' . $address . '" ' . $extra . '>' . $text . '</a>';
+
+        for($x = 0, $y = strlen($string); $x < $y; $x++) {
+            $ord[] = ord($string[$x]);
+        } 
+
+        $_ret = "<script type=\"text/javascript\" language=\"javascript\">\n";
+        $_ret .= "<!--\n";
+        $_ret .= "{document.write(String.fromCharCode(";
+        $_ret .= implode(',', $ord);
+        $_ret .= "))";
+        $_ret .= "}\n";
+        $_ret .= "//-->\n";
+        $_ret .= "</script>\n";
+
+        return $_ret;
+    } elseif ($encode == 'hex') {
+        preg_match('!^(.*)(\?.*)$!', $address, $match);
+        if (!empty($match[2])) {
+            trigger_error("mailto: hex encoding does not work with extra attributes. Try javascript.",E_USER_WARNING);
+            return;
+        } 
+        $address_encode = '';
+        for ($x = 0; $x < strlen($address); $x++) {
+            if (preg_match('!\w!', $address[$x])) {
+                $address_encode .= '%' . bin2hex($address[$x]);
+            } else {
+                $address_encode .= $address[$x];
+            } 
+        } 
+        $text_encode = '';
+        for ($x = 0; $x < strlen($text); $x++) {
+            $text_encode .= '&#x' . bin2hex($text[$x]) . ';';
+        } 
+
+        $mailto = "&#109;&#97;&#105;&#108;&#116;&#111;&#58;";
+        return '<a href="' . $mailto . $address_encode . '" ' . $extra . '>' . $text_encode . '</a>';
+    } else {
+        // no encoding
+        return '<a href="mailto:' . $address . '" ' . $extra . '>' . $text . '</a>';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/function.math.php b/WEB-INF/lib/smarty/plugins/function.math.php
new file mode 100644 (file)
index 0000000..cd90020
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * This plugin is only for Smarty2 BC
+ * @package Smarty
+ * @subpackage PluginsFunction
+ */
+
+/**
+ * Smarty {math} function plugin
+ *
+ * Type:     function<br>
+ * Name:     math<br>
+ * Purpose:  handle math computations in template<br>
+ * @link http://smarty.php.net/manual/en/language.function.math.php {math}
+ *          (Smarty online manual)
+ * @author   Monte Ohrt <monte at ohrt dot com>
+ * @param array $params parameters
+ * @param object $template template object
+ * @return string|null
+ */
+function smarty_function_math($params, $template)
+{
+    // be sure equation parameter is present
+    if (empty($params['equation'])) {
+        trigger_error("math: missing equation parameter",E_USER_WARNING);
+        return;
+    }
+
+    $equation = $params['equation'];
+
+    // make sure parenthesis are balanced
+    if (substr_count($equation,"(") != substr_count($equation,")")) {
+        trigger_error("math: unbalanced parenthesis",E_USER_WARNING);
+        return;
+    }
+
+    // match all vars in equation, make sure all are passed
+    preg_match_all("!(?:0x[a-fA-F0-9]+)|([a-zA-Z][a-zA-Z0-9_]*)!",$equation, $match);
+    $allowed_funcs = array('int','abs','ceil','cos','exp','floor','log','log10',
+                           'max','min','pi','pow','rand','round','sin','sqrt','srand','tan');
+    
+    foreach($match[1] as $curr_var) {
+        if ($curr_var && !in_array($curr_var, array_keys($params)) && !in_array($curr_var, $allowed_funcs)) {
+            trigger_error("math: function call $curr_var not allowed",E_USER_WARNING);
+            return;
+        }
+    }
+
+    foreach($params as $key => $val) {
+        if ($key != "equation" && $key != "format" && $key != "assign") {
+            // make sure value is not empty
+            if (strlen($val)==0) {
+                trigger_error("math: parameter $key is empty",E_USER_WARNING);
+                return;
+            }
+            if (!is_numeric($val)) {
+                trigger_error("math: parameter $key: is not numeric",E_USER_WARNING);
+                return;
+            }
+            $equation = preg_replace("/\b$key\b/", " \$params['$key'] ", $equation);
+        }
+    }
+    $smarty_math_result = null;
+    eval("\$smarty_math_result = ".$equation.";");
+
+    if (empty($params['format'])) {
+        if (empty($params['assign'])) {
+            return $smarty_math_result;
+        } else {
+            $template->assign($params['assign'],$smarty_math_result);
+        }
+    } else {
+        if (empty($params['assign'])){
+            printf($params['format'],$smarty_math_result);
+        } else {
+            $template->assign($params['assign'],sprintf($params['format'],$smarty_math_result));
+        }
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.capitalize.php b/WEB-INF/lib/smarty/plugins/modifier.capitalize.php
new file mode 100644 (file)
index 0000000..cd24589
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+
+/**
+ * Smarty capitalize modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     capitalize<br>
+ * Purpose:  capitalize words in the string
+ * 
+ * @link 
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $ 
+ * @return string 
+ */
+function smarty_modifier_capitalize($string, $uc_digits = false)
+{ 
+    // uppercase with php function ucwords
+    $upper_string = ucwords($string); 
+    // check for any missed hyphenated words
+    $upper_string = preg_replace("!(^|[^\p{L}'])([\p{Ll}])!ue", "'\\1'.ucfirst('\\2')", $upper_string); 
+    // check uc_digits case
+    if (!$uc_digits) {
+        if (preg_match_all("!\b([\p{L}]*[\p{N}]+[\p{L}]*)\b!u", $string, $matches, PREG_OFFSET_CAPTURE)) {
+            foreach($matches[1] as $match)
+            $upper_string = substr_replace($upper_string, $match[0], $match[1], strlen($match[0]));
+        } 
+    } 
+    return $upper_string;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.date_format.php b/WEB-INF/lib/smarty/plugins/modifier.date_format.php
new file mode 100644 (file)
index 0000000..3656c1c
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+
+/**
+ * Smarty date_format modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     date_format<br>
+ * Purpose:  format datestamps via strftime<br>
+ * Input:<br>
+ *          - string: input date string
+ *          - format: strftime format for output
+ *          - default_date: default date if $string is empty
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.date.format.php date_format (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $ 
+ * @param string $ 
+ * @param string $ 
+ * @return string |void
+ * @uses smarty_make_timestamp()
+ */
+function smarty_modifier_date_format($string, $format = SMARTY_RESOURCE_DATE_FORMAT, $default_date = '',$formatter='auto')
+{
+    /**
+    * Include the {@link shared.make_timestamp.php} plugin
+    */
+    require_once(SMARTY_PLUGINS_DIR . 'shared.make_timestamp.php');
+    if ($string != '') {
+        $timestamp = smarty_make_timestamp($string);
+    } elseif ($default_date != '') {
+        $timestamp = smarty_make_timestamp($default_date);
+    } else {
+        return;
+    } 
+    if($formatter=='strftime'||($formatter=='auto'&&strpos($format,'%')!==false)) {
+        if (DS == '\\') {
+            $_win_from = array('%D', '%h', '%n', '%r', '%R', '%t', '%T');
+            $_win_to = array('%m/%d/%y', '%b', "\n", '%I:%M:%S %p', '%H:%M', "\t", '%H:%M:%S');
+            if (strpos($format, '%e') !== false) {
+                $_win_from[] = '%e';
+                $_win_to[] = sprintf('%\' 2d', date('j', $timestamp));
+            } 
+            if (strpos($format, '%l') !== false) {
+                $_win_from[] = '%l';
+                $_win_to[] = sprintf('%\' 2d', date('h', $timestamp));
+            } 
+            $format = str_replace($_win_from, $_win_to, $format);
+        } 
+        return strftime($format, $timestamp);
+    } else {
+        return date($format, $timestamp);
+    }
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.debug_print_var.php b/WEB-INF/lib/smarty/plugins/modifier.debug_print_var.php
new file mode 100644 (file)
index 0000000..013337a
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage Debug
+ */
+
+/**
+ * Smarty debug_print_var modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     debug_print_var<br>
+ * Purpose:  formats variable contents for display in the console
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.debug.print.var.php debug_print_var (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param array $ |object
+ * @param integer $ 
+ * @param integer $ 
+ * @return string 
+ */
+function smarty_modifier_debug_print_var ($var, $depth = 0, $length = 40)
+{
+    $_replace = array("\n" => '<i>\n</i>',
+        "\r" => '<i>\r</i>',
+        "\t" => '<i>\t</i>'
+        );
+
+    switch (gettype($var)) {
+        case 'array' :
+            $results = '<b>Array (' . count($var) . ')</b>';
+            foreach ($var as $curr_key => $curr_val) {
+                $results .= '<br>' . str_repeat('&nbsp;', $depth * 2)
+                 . '<b>' . strtr($curr_key, $_replace) . '</b> =&gt; '
+                 . smarty_modifier_debug_print_var($curr_val, ++$depth, $length);
+                $depth--;
+            } 
+            break;
+        case 'object' :
+            $object_vars = get_object_vars($var);
+            $results = '<b>' . get_class($var) . ' Object (' . count($object_vars) . ')</b>';
+            foreach ($object_vars as $curr_key => $curr_val) {
+                $results .= '<br>' . str_repeat('&nbsp;', $depth * 2)
+                 . '<b> -&gt;' . strtr($curr_key, $_replace) . '</b> = '
+                 . smarty_modifier_debug_print_var($curr_val, ++$depth, $length);
+                $depth--;
+            } 
+            break;
+        case 'boolean' :
+        case 'NULL' :
+        case 'resource' :
+            if (true === $var) {
+                $results = 'true';
+            } elseif (false === $var) {
+                $results = 'false';
+            } elseif (null === $var) {
+                $results = 'null';
+            } else {
+                $results = htmlspecialchars((string) $var);
+            } 
+            $results = '<i>' . $results . '</i>';
+            break;
+        case 'integer' :
+        case 'float' :
+            $results = htmlspecialchars((string) $var);
+            break;
+        case 'string' :
+            $results = strtr($var, $_replace);
+            if (strlen($var) > $length) {
+                $results = substr($var, 0, $length - 3) . '...';
+            } 
+            $results = htmlspecialchars('"' . $results . '"');
+            break;
+        case 'unknown type' :
+        default :
+            $results = strtr((string) $var, $_replace);
+            if (strlen($results) > $length) {
+                $results = substr($results, 0, $length - 3) . '...';
+            } 
+            $results = htmlspecialchars($results);
+    } 
+
+    return $results;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.escape.php b/WEB-INF/lib/smarty/plugins/modifier.escape.php
new file mode 100644 (file)
index 0000000..37bc0e3
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+/**
+ * Smarty escape modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     escape<br>
+ * Purpose:  escape string for output
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.count.characters.php count_characters (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $string input string
+ * @param string $esc_type escape type
+ * @param string $char_set character set
+ * @return string escaped input string
+ */
+function smarty_modifier_escape($string, $esc_type = 'html', $char_set = SMARTY_RESOURCE_CHAR_SET)
+{
+    switch ($esc_type) {
+        case 'html':
+            return htmlspecialchars($string, ENT_QUOTES, $char_set);
+
+        case 'htmlall':
+            return htmlentities($string, ENT_QUOTES, $char_set);
+
+        case 'url':
+            return rawurlencode($string);
+
+        case 'urlpathinfo':
+            return str_replace('%2F', '/', rawurlencode($string));
+
+        case 'quotes': 
+            // escape unescaped single quotes
+            return preg_replace("%(?<!\\\\)'%", "\\'", $string);
+
+        case 'hex': 
+            // escape every character into hex
+            $return = '';
+            for ($x = 0; $x < strlen($string); $x++) {
+                $return .= '%' . bin2hex($string[$x]);
+            } 
+            return $return;
+
+        case 'hexentity':
+            $return = '';
+            for ($x = 0; $x < strlen($string); $x++) {
+                $return .= '&#x' . bin2hex($string[$x]) . ';';
+            } 
+            return $return;
+
+        case 'decentity':
+            $return = '';
+            for ($x = 0; $x < strlen($string); $x++) {
+                $return .= '&#' . ord($string[$x]) . ';';
+            } 
+            return $return;
+
+        case 'javascript': 
+            // escape quotes and backslashes, newlines, etc.
+            return strtr($string, array('\\' => '\\\\', "'" => "\\'", '"' => '\\"', "\r" => '\\r', "\n" => '\\n', '</' => '<\/'));
+
+        case 'mail': 
+          require_once(SMARTY_PLUGINS_DIR . 'shared.mb_str_replace.php');
+          return smarty_mb_str_replace(array('@', '.'), array(' [AT] ', ' [DOT] '), $string);
+
+        case 'nonstd': 
+            // escape non-standard chars, such as ms document quotes
+            $_res = '';
+            for($_i = 0, $_len = strlen($string); $_i < $_len; $_i++) {
+                $_ord = ord(substr($string, $_i, 1)); 
+                // non-standard char, escape it
+                if ($_ord >= 126) {
+                    $_res .= '&#' . $_ord . ';';
+                } else {
+                    $_res .= substr($string, $_i, 1);
+                } 
+            } 
+            return $_res;
+
+        default:
+            return $string;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.regex_replace.php b/WEB-INF/lib/smarty/plugins/modifier.regex_replace.php
new file mode 100644 (file)
index 0000000..9f14880
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+
+/**
+ * Smarty regex_replace modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     regex_replace<br>
+ * Purpose:  regular expression search/replace
+ * @link http://smarty.php.net/manual/en/language.modifier.regex.replace.php
+ *          regex_replace (Smarty online manual)
+ * @author   Monte Ohrt <monte at ohrt dot com>
+ * @param string
+ * @param string|array
+ * @param string|array
+ * @return string
+ */
+function smarty_modifier_regex_replace($string, $search, $replace)
+{
+    if(is_array($search)) {
+      foreach($search as $idx => $s)
+        $search[$idx] = _smarty_regex_replace_check($s);
+    } else {
+      $search = _smarty_regex_replace_check($search);
+    }       
+
+    return preg_replace($search, $replace, $string);
+}
+
+function _smarty_regex_replace_check($search)
+{
+    if (($pos = strpos($search,"\0")) !== false)
+      $search = substr($search,0,$pos);
+    if (preg_match('!([a-zA-Z\s]+)$!s', $search, $match) && (strpos($match[1], 'e') !== false)) {
+        /* remove eval-modifier from $search */
+        $search = substr($search, 0, -strlen($match[1])) . preg_replace('![e\s]+!', '', $match[1]);
+    }
+    return $search;
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.replace.php b/WEB-INF/lib/smarty/plugins/modifier.replace.php
new file mode 100644 (file)
index 0000000..0636a19
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Smarty plugin
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+
+/**
+ * Smarty replace modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     replace<br>
+ * Purpose:  simple search/replace
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.replace.php replace (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author Uwe Tews 
+ * @param string $ 
+ * @param string $ 
+ * @param string $ 
+ * @return string 
+ */
+function smarty_modifier_replace($string, $search, $replace)
+{
+    require_once(SMARTY_PLUGINS_DIR . 'shared.mb_str_replace.php');
+    return smarty_mb_str_replace($search, $replace, $string);
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.spacify.php b/WEB-INF/lib/smarty/plugins/modifier.spacify.php
new file mode 100644 (file)
index 0000000..f14e026
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Smarty plugin
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+
+/**
+ * Smarty spacify modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     spacify<br>
+ * Purpose:  add spaces between characters in a string
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.spacify.php spacify (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $ 
+ * @param string $ 
+ * @return string 
+ */
+function smarty_modifier_spacify($string, $spacify_char = ' ')
+{ 
+    // mb_ functions available?
+    if (function_exists('mb_strlen') && mb_detect_encoding($string, 'UTF-8, ISO-8859-1') === 'UTF-8') {
+        $strlen = mb_strlen($string);
+        while ($strlen) {
+            $array[] = mb_substr($string, 0, 1, "UTF-8");
+            $string = mb_substr($string, 1, $strlen, "UTF-8");
+            $strlen = mb_strlen($string);
+        } 
+        return implode($spacify_char, $array);
+    } else {
+        return implode($spacify_char, preg_split('//', $string, -1));
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifier.truncate.php b/WEB-INF/lib/smarty/plugins/modifier.truncate.php
new file mode 100644 (file)
index 0000000..0e9d4b9
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifier
+ */
+/**
+ * Smarty truncate modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     truncate<br>
+ * Purpose:  Truncate a string to a certain length if necessary,
+ *               optionally splitting in the middle of a word, and
+ *               appending the $etc string or inserting $etc into the middle.
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.truncate.php truncate (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @param string $string input string
+ * @param integer $length lenght of truncated text
+ * @param string $etc end string
+ * @param boolean $break_words truncate at word boundary
+ * @param boolean $middle truncate in the middle of text
+ * @return string truncated string
+ */
+function smarty_modifier_truncate($string, $length = 80, $etc = '...',
+    $break_words = false, $middle = false)
+{
+    if ($length == 0)
+        return '';
+
+    if (is_callable('mb_strlen')) {
+        if (mb_detect_encoding($string, 'UTF-8, ISO-8859-1') === 'UTF-8') {
+            // $string has utf-8 encoding
+            if (mb_strlen($string) > $length) {
+                $length -= min($length, mb_strlen($etc));
+                if (!$break_words && !$middle) {
+                    $string = preg_replace('/\s+?(\S+)?$/u', '', mb_substr($string, 0, $length + 1));
+                } 
+                if (!$middle) {
+                    return mb_substr($string, 0, $length) . $etc;
+                } else {
+                    return mb_substr($string, 0, $length / 2) . $etc . mb_substr($string, - $length / 2);
+                } 
+            } else {
+                return $string;
+            } 
+        } 
+    } 
+    // $string has no utf-8 encoding
+    if (strlen($string) > $length) {
+        $length -= min($length, strlen($etc));
+        if (!$break_words && !$middle) {
+            $string = preg_replace('/\s+?(\S+)?$/', '', substr($string, 0, $length + 1));
+        } 
+        if (!$middle) {
+            return substr($string, 0, $length) . $etc;
+        } else {
+            return substr($string, 0, $length / 2) . $etc . substr($string, - $length / 2);
+        } 
+    } else {
+        return $string;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.cat.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.cat.php
new file mode 100644 (file)
index 0000000..6937222
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty cat modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     cat<br>
+ * Date:     Feb 24, 2003
+ * Purpose:  catenate a value to a variable
+ * Input:    string to catenate
+ * Example:  {$var|cat:"foo"}
+ * @link http://smarty.php.net/manual/en/language.modifier.cat.php cat
+ *          (Smarty online manual)
+ * @author   Uwe Tews
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_cat($params, $compiler)
+{
+    return '('.implode(').(', $params).')';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.count_characters.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.count_characters.php
new file mode 100644 (file)
index 0000000..12a87c4
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty count_characters modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     count_characteres<br>
+ * Purpose:  count the number of characters in a text
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.count.characters.php count_characters (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_count_characters($params, $compiler)
+{
+    // mb_ functions available?
+    if (function_exists('mb_strlen')) {
+        // count also spaces?
+        if (isset($params[1]) && $params[1] == 'true') {
+            return '((mb_detect_encoding(' . $params[0] . ', \'UTF-8, ISO-8859-1\') === \'UTF-8\') ? mb_strlen(' . $params[0] . ', SMARTY_RESOURCE_CHAR_SET) : strlen(' . $params[0] . '))';
+        } 
+        return '((mb_detect_encoding(' . $params[0] . ', \'UTF-8, ISO-8859-1\') === \'UTF-8\') ? preg_match_all(\'#[^\s\pZ]#u\', ' . $params[0] . ', $tmp) : preg_match_all(\'/[^\s]/\',' . $params[0] . ', $tmp))';
+    } else {
+        // count also spaces?
+        if (isset($params[1]) && $params[1] == 'true') {
+            return 'strlen(' . $params[0] . ')';
+        } 
+        return 'preg_match_all(\'/[^\s]/\',' . $params[0] . ', $tmp)';
+    } 
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.count_paragraphs.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.count_paragraphs.php
new file mode 100644 (file)
index 0000000..d221e3d
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty count_paragraphs modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     count_paragraphs<br>
+ * Purpose:  count the number of paragraphs in a text
+ * @link http://smarty.php.net/manual/en/language.modifier.count.paragraphs.php
+ *          count_paragraphs (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_count_paragraphs($params, $compiler)
+{
+    // count \r or \n characters
+    return '(preg_match_all(\'#[\r\n]+#\', ' . $params[0] . ', $tmp)+1)';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.count_sentences.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.count_sentences.php
new file mode 100644 (file)
index 0000000..6d0bcf6
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty count_sentences modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     count_sentences
+ * Purpose:  count the number of sentences in a text
+ * @link http://smarty.php.net/manual/en/language.modifier.count.paragraphs.php
+ *          count_sentences (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_count_sentences($params, $compiler)
+{
+    // find periods with a word before but not after.
+    return 'preg_match_all(\'/[^\s]\.(?!\w)/\', ' . $params[0] . ', $tmp)';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.count_words.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.count_words.php
new file mode 100644 (file)
index 0000000..82bc478
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty count_words modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     count_words<br>
+ * Purpose:  count the number of words in a text
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.count.words.php count_words (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+*/
+function smarty_modifiercompiler_count_words($params, $compiler)
+{ 
+    // mb_ functions available?
+    if (function_exists('mb_strlen')) {
+        return '((mb_detect_encoding(' . $params[0] . ', \'UTF-8, ISO-8859-1\') === \'UTF-8\') ? preg_match_all(\'#[\w\pL]+#u\', ' . $params[0] . ', $tmp) : preg_match_all(\'#\w+#\',' . $params[0] . ', $tmp))';
+    } else {
+        return 'str_word_count(' . $params[0] . ')';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.default.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.default.php
new file mode 100644 (file)
index 0000000..efbbaef
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty default modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     default<br>
+ * Purpose:  designate default value for empty variables
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.default.php default (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_default ($params, $compiler)
+{
+    $output = $params[0];
+    if (!isset($params[1])) {
+        $params[1] = "''";
+    } 
+    for ($i = 1, $cnt = count($params); $i < $cnt; $i++) {
+        $output = '(($tmp = @' . $output . ')===null||$tmp===\'\' ? ' . $params[$i] . ' : $tmp)';
+    } 
+    return $output;
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.indent.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.indent.php
new file mode 100644 (file)
index 0000000..cb99120
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Smarty plugin
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty indent modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     indent<br>
+ * Purpose:  indent lines of text
+ * @link http://smarty.php.net/manual/en/language.modifier.indent.php
+ *          indent (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+
+function smarty_modifiercompiler_indent($params, $compiler)
+{
+    if (!isset($params[1])) {
+        $params[1] = 4;
+    } 
+    if (!isset($params[2])) {
+        $params[2] = "' '";
+    } 
+    return 'preg_replace(\'!^!m\',str_repeat(' . $params[2] . ',' . $params[1] . '),' . $params[0] . ')';
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.lower.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.lower.php
new file mode 100644 (file)
index 0000000..13858d4
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Smarty plugin
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty lower modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     lower<br>
+ * Purpose:  convert string to lowercase
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.lower.php lower (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+
+function smarty_modifiercompiler_lower($params, $compiler)
+{
+    if (function_exists('mb_strtolower')) {
+        return '((mb_detect_encoding(' . $params[0] . ', \'UTF-8, ISO-8859-1\') === \'UTF-8\') ? mb_strtolower(' . $params[0] . ',SMARTY_RESOURCE_CHAR_SET) : strtolower(' . $params[0] . '))' ;
+    } else {
+        return 'strtolower(' . $params[0] . ')';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.noprint.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.noprint.php
new file mode 100644 (file)
index 0000000..5fac7b6
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty noprint modifier plugin
+ *
+ * Type:     modifier<br>
+ * Name:     noprint<br>
+ * Purpose:  return an empty string
+ * @author   Uwe Tews
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_noprint($params, $compiler)
+{
+    return "''";
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.string_format.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.string_format.php
new file mode 100644 (file)
index 0000000..de4054e
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty string_format modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     string_format<br>
+ * Purpose:  format strings via sprintf
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.string.format.php string_format (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_string_format($params, $compiler)
+{
+    return 'sprintf(' . $params[1] . ',' . $params[0] . ')';
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.strip.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.strip.php
new file mode 100644 (file)
index 0000000..83c3639
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty strip modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     strip<br>
+ * Purpose:  Replace all repeated spaces, newlines, tabs
+ *              with a single space or supplied replacement string.<br>
+ * Example:  {$var|strip} {$var|strip:"&nbsp;"}
+ * Date:     September 25th, 2002
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.strip.php strip (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+
+function smarty_modifiercompiler_strip($params, $compiler)
+{
+    if (!isset($params[1])) {
+        $params[1] = "' '";
+    } 
+    return "preg_replace('!\s+!', {$params[1]},{$params[0]})";
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.strip_tags.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.strip_tags.php
new file mode 100644 (file)
index 0000000..ec1a965
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty strip_tags modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     strip_tags<br>
+ * Purpose:  strip html tags from text
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.strip.tags.php strip_tags (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+
+function smarty_modifiercompiler_strip_tags($params, $compiler)
+{
+   if (!isset($params[1])) {
+        $params[1] = true;
+    } 
+    if ($params[1] === true) {
+        return "preg_replace('!<[^>]*?>!', ' ', {$params[0]})";
+    } else {
+        return 'strip_tags(' . $params[0] . ')';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.upper.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.upper.php
new file mode 100644 (file)
index 0000000..f663502
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty upper modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     lower<br>
+ * Purpose:  convert string to uppercase
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.upper.php lower (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_upper($params, $compiler)
+{
+    if (function_exists('mb_strtoupper')) {
+        return '((mb_detect_encoding(' . $params[0] . ', \'UTF-8, ISO-8859-1\') === \'UTF-8\') ? mb_strtoupper(' . $params[0] . ',SMARTY_RESOURCE_CHAR_SET) : strtoupper(' . $params[0] . '))' ;
+    } else {
+        return 'strtoupper(' . $params[0] . ')';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.wordwrap.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.wordwrap.php
new file mode 100644 (file)
index 0000000..ff593c7
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsModifierCompiler
+ */
+
+/**
+ * Smarty wordwrap modifier plugin
+ * 
+ * Type:     modifier<br>
+ * Name:     wordwrap<br>
+ * Purpose:  wrap a string of text at a given length
+ * 
+ * @link http://smarty.php.net/manual/en/language.modifier.wordwrap.php wordwrap (Smarty online manual)
+ * @author Uwe Tews 
+ * @param array $params parameters
+ * @return string with compiled code
+ */
+function smarty_modifiercompiler_wordwrap($params, $compiler)
+{
+    if (!isset($params[1])) {
+        $params[1] = 80;
+    } 
+    if (!isset($params[2])) {
+        $params[2] = '"\n"';
+    } 
+    if (!isset($params[3])) {
+        $params[3] = 'false';
+    } 
+    return 'wordwrap(' . $params[0] . ',' . $params[1] . ',' . $params[2] . ',' . $params[3] . ')';
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/outputfilter.trimwhitespace.php b/WEB-INF/lib/smarty/plugins/outputfilter.trimwhitespace.php
new file mode 100644 (file)
index 0000000..20e9bb6
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Smarty plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsFilter
+ */
+
+/**
+ * Smarty trimwhitespace outputfilter plugin
+ *
+ * File:     outputfilter.trimwhitespace.php<br>
+ * Type:     outputfilter<br>
+ * Name:     trimwhitespace<br>
+ * Date:     Jan 25, 2003<br>
+ * Purpose:  trim leading white space and blank lines from
+ *           template source after it gets interpreted, cleaning
+ *           up code and saving bandwidth. Does not affect
+ *           <<PRE>></PRE> and <SCRIPT></SCRIPT> blocks.<br>
+ * Install:  Drop into the plugin directory, call
+ *           <code>$smarty->load_filter('output','trimwhitespace');</code>
+ *           from application.
+ * @author   Monte Ohrt <monte at ohrt dot com>
+ * @author Contributions from Lars Noschinski <lars@usenet.noschinski.de>
+ * @version  1.3
+ * @param string $source input string
+ * @param object &$smarty Smarty object
+ * @return string filtered output
+ */
+function smarty_outputfilter_trimwhitespace($source, $smarty)
+{
+    // Pull out the script blocks
+    preg_match_all("!<script[^>]*?>.*?</script>!is", $source, $match);
+    $_script_blocks = $match[0];
+    $source = preg_replace("!<script[^>]*?>.*?</script>!is",
+                           '@@@SMARTY:TRIM:SCRIPT@@@', $source);
+
+    // Pull out the pre blocks
+    preg_match_all("!<pre[^>]*?>.*?</pre>!is", $source, $match);
+    $_pre_blocks = $match[0];
+    $source = preg_replace("!<pre[^>]*?>.*?</pre>!is",
+                           '@@@SMARTY:TRIM:PRE@@@', $source);
+    
+    // Pull out the textarea blocks
+    preg_match_all("!<textarea[^>]*?>.*?</textarea>!is", $source, $match);
+    $_textarea_blocks = $match[0];
+    $source = preg_replace("!<textarea[^>]*?>.*?</textarea>!is",
+                           '@@@SMARTY:TRIM:TEXTAREA@@@', $source);
+
+    // remove all leading spaces, tabs and carriage returns NOT
+    // preceeded by a php close tag.
+    $source = trim(preg_replace('/((?<!\?>)\n)[\s]+/m', '\1', $source));
+
+    // replace textarea blocks
+    smarty_outputfilter_trimwhitespace_replace("@@@SMARTY:TRIM:TEXTAREA@@@",$_textarea_blocks, $source);
+
+    // replace pre blocks
+    smarty_outputfilter_trimwhitespace_replace("@@@SMARTY:TRIM:PRE@@@",$_pre_blocks, $source);
+
+    // replace script blocks
+    smarty_outputfilter_trimwhitespace_replace("@@@SMARTY:TRIM:SCRIPT@@@",$_script_blocks, $source);
+
+    return $source;
+}
+
+function smarty_outputfilter_trimwhitespace_replace($search_str, $replace, &$subject) {
+    $_len = strlen($search_str);
+    $_pos = 0;
+    for ($_i=0, $_count=count($replace); $_i<$_count; $_i++)
+        if (($_pos=strpos($subject, $search_str, $_pos))!==false)
+            $subject = substr_replace($subject, $replace[$_i], $_pos, $_len);
+        else
+            break;
+
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php b/WEB-INF/lib/smarty/plugins/shared.escape_special_chars.php
new file mode 100644 (file)
index 0000000..e36b2c8
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Smarty shared plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsShared
+ */
+
+/**
+ * escape_special_chars common function
+ *
+ * Function: smarty_function_escape_special_chars<br>
+ * Purpose:  used by other smarty functions to escape
+ *           special chars except for already escaped ones
+ * @author   Monte Ohrt <monte at ohrt dot com>
+ * @param string
+ * @return string
+ */
+function smarty_function_escape_special_chars($string)
+{
+    if(!is_array($string)) {
+        $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string);
+        $string = htmlspecialchars($string);
+        $string = str_replace(array('%%%SMARTY_START%%%','%%%SMARTY_END%%%'), array('&',';'), $string);
+    }
+    return $string;
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/shared.make_timestamp.php b/WEB-INF/lib/smarty/plugins/shared.make_timestamp.php
new file mode 100644 (file)
index 0000000..9789f53
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Smarty shared plugin
+ *
+ * @package Smarty
+ * @subpackage PluginsShared
+ */
+
+/**
+ * Function: smarty_make_timestamp<br>
+ * Purpose:  used by other smarty functions to make a timestamp
+ *           from a string.
+ * @author   Monte Ohrt <monte at ohrt dot com>
+ * @param string $string
+ * @return string
+ */
+
+function smarty_make_timestamp($string)
+{
+    if(empty($string)) {
+        // use "now":
+        return time();
+    } elseif ($string instanceof DateTime) {
+        return $string->getTimestamp();
+    } elseif (strlen($string)==14 && ctype_digit($string)) {
+        // it is mysql timestamp format of YYYYMMDDHHMMSS?            
+        return mktime(substr($string, 8, 2),substr($string, 10, 2),substr($string, 12, 2),
+                       substr($string, 4, 2),substr($string, 6, 2),substr($string, 0, 4));
+    } elseif (is_numeric($string)) {
+        // it is a numeric string, we handle it as timestamp
+        return (int)$string;
+    } else {
+        // strtotime should handle it
+        $time = strtotime($string);
+        if ($time == -1 || $time === false) {
+            // strtotime() was not able to parse $string, use "now":
+            return time();
+        }
+        return $time;
+    }
+}
+
+?>
diff --git a/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php b/WEB-INF/lib/smarty/plugins/shared.mb_str_replace.php
new file mode 100644 (file)
index 0000000..8bc156f
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+if(!function_exists('smarty_mb_str_replace')) {
+  function smarty_mb_str_replace($search, $replace, $subject, &$count=0) { 
+      if (!is_array($search) && is_array($replace)) { 
+          return false; 
+      } 
+      if (is_array($subject)) { 
+          // call mb_replace for each single string in $subject 
+          foreach ($subject as &$string) { 
+              $string = &smarty_mb_str_replace($search, $replace, $string, $c); 
+              $count += $c; 
+          } 
+      } elseif (is_array($search)) { 
+          if (!is_array($replace)) { 
+              foreach ($search as &$string) { 
+                  $subject = smarty_mb_str_replace($string, $replace, $subject, $c); 
+                  $count += $c; 
+              } 
+          } else { 
+              $n = max(count($search), count($replace)); 
+              while ($n--) { 
+                  $subject = smarty_mb_str_replace(current($search), current($replace), $subject, $c); 
+                  $count += $c; 
+                  next($search); 
+                  next($replace); 
+              } 
+          } 
+      } else { 
+          $parts = mb_split(preg_quote($search), $subject); 
+          $count = count($parts)-1; 
+          $subject = implode($replace, $parts); 
+      } 
+      return $subject; 
+  }
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/plugins/variablefilter.htmlspecialchars.php b/WEB-INF/lib/smarty/plugins/variablefilter.htmlspecialchars.php
new file mode 100644 (file)
index 0000000..4d3550c
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsFilter
+ */
+
+/**
+ * Smarty htmlspecialchars variablefilter plugin
+ * 
+ * @param string $source input string
+ * @param object $ &$smarty Smarty object
+ * @return string filtered output
+ */
+
+function smarty_variablefilter_htmlspecialchars($source, $smarty)
+{
+    return htmlspecialchars($source, ENT_QUOTES);
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_cacheresource_file.php
new file mode 100644 (file)
index 0000000..c2f9a72
--- /dev/null
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * Smarty Internal Plugin CacheResource File
+ * 
+ * Implements the file system as resource for the HTML cache
+ * Version ussing nocache inserts
+ * 
+ * @package Smarty
+ * @subpackage Cacher
+ * @author Uwe Tews 
+ */
+
+/**
+ * This class does contain all necessary methods for the HTML cache on file system
+ */
+class Smarty_Internal_CacheResource_File {
+    function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    /**
+     * Returns the filepath of the cached template output
+     * 
+     * @param object $_template current template
+     * @return string the cache filepath
+     */
+    public function getCachedFilepath($_template)
+    {
+        $_source_file_path = str_replace(':', '.', $_template->getTemplateFilepath());
+        $_cache_id = isset($_template->cache_id) ? preg_replace('![^\w\|]+!', '_', $_template->cache_id) : null;
+        $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
+        $_filepath = $_template->templateUid; 
+        // if use_sub_dirs, break file into directories
+        if ($this->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $this->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_cache_id)) {
+            $_cache_id = str_replace('|', $_compile_dir_sep, $_cache_id) . $_compile_dir_sep;
+        } else {
+            $_cache_id = '';
+        } 
+        if (isset($_compile_id)) {
+            $_compile_id = $_compile_id . $_compile_dir_sep;
+        } else {
+            $_compile_id = '';
+        } 
+        $_cache_dir = $this->smarty->cache_dir;
+        if (strpos('/\\', substr($_cache_dir, -1)) === false) {
+            $_cache_dir .= DS;
+        } 
+        return $_cache_dir . $_cache_id . $_compile_id . $_filepath . '.' . basename($_source_file_path) . '.php';
+    } 
+
+    /**
+     * Returns the timpestamp of the cached template output
+     * 
+     * @param object $_template current template
+     * @return integer |booelan the template timestamp or false if the file does not exist
+     */
+    public function getCachedTimestamp($_template)
+    { 
+        // return @filemtime ($_template->getCachedFilepath());
+        return ($_template->getCachedFilepath() && file_exists($_template->getCachedFilepath())) ? filemtime($_template->getCachedFilepath()) : false ;
+    } 
+
+    /**
+     * Returns the cached template output
+     * 
+     * @param object $_template current template
+     * @return string |booelan the template content or false if the file does not exist
+     */
+    public function getCachedContents($_template, $no_render = false)
+    {
+       if (!$no_render) {
+               ob_start();
+       }
+        $_smarty_tpl = $_template;
+        include $_template->getCachedFilepath();
+        if ($no_render) {
+               return null;
+        } else {
+          return ob_get_clean();
+        }
+    } 
+
+    /**
+     * Writes the rendered template output to cache file
+     * 
+     * @param object $_template current template
+     * @return boolean status
+     */
+    public function writeCachedContent($_template, $content)
+    {
+        if (!$_template->resource_object->isEvaluated) {
+            if (Smarty_Internal_Write_File::writeFile($_template->getCachedFilepath(), $content, $this->smarty) === true) {
+                $_template->cached_timestamp = filemtime($_template->getCachedFilepath());
+                return true;
+            } 
+        } 
+        return false;
+    } 
+
+    /**
+     * Empty cache folder
+     * 
+     * @param integer $exp_time expiration time
+     * @return integer number of cache files deleted
+     */
+    public function clearAll($exp_time = null)
+    {
+        return $this->clear(null, null, null, $exp_time);
+    } 
+    /**
+     * Empty cache for a specific template
+     * 
+     * @param string $resource_name template name
+     * @param string $cache_id cache id
+     * @param string $compile_id compile id
+     * @param integer $exp_time expiration time
+     * @return integer number of cache files deleted
+     */
+    public function clear($resource_name, $cache_id, $compile_id, $exp_time)
+    {
+        $_cache_id = isset($cache_id) ? preg_replace('![^\w\|]+!', '_', $cache_id) : null;
+        $_compile_id = isset($compile_id) ? preg_replace('![^\w\|]+!', '_', $compile_id) : null;
+        $_dir_sep = $this->smarty->use_sub_dirs ? '/' : '^';
+        $_compile_id_offset = $this->smarty->use_sub_dirs ? 3 : 0;
+        $_dir = rtrim($this->smarty->cache_dir, '/\\') . DS;
+        $_dir_length = strlen($_dir);
+        if (isset($_cache_id)) {
+            $_cache_id_parts = explode('|', $_cache_id);
+            $_cache_id_parts_count = count($_cache_id_parts);
+            if ($this->smarty->use_sub_dirs) {
+                foreach ($_cache_id_parts as $id_part) {
+                    $_dir .= $id_part . DS;
+                } 
+            } 
+        } 
+        if (isset($resource_name)) {
+            $_save_stat = $this->smarty->caching;
+            $this->smarty->caching = true;
+            $tpl = new $this->smarty->template_class($resource_name, $this->smarty); 
+            // remove from template cache
+            unset($this->smarty->template_objects[crc32($tpl->template_resource . $tpl->cache_id . $tpl->compile_id)]);
+            $this->smarty->caching = $_save_stat;
+            if ($tpl->isExisting()) {
+                $_resourcename_parts = basename(str_replace('^', '/', $tpl->getCachedFilepath()));
+            } else {
+                return 0;
+            } 
+        } 
+        $_count = 0;
+        if (file_exists($_dir)) {
+            $_cacheDirs = new RecursiveDirectoryIterator($_dir);
+            $_cache = new RecursiveIteratorIterator($_cacheDirs, RecursiveIteratorIterator::CHILD_FIRST);
+            foreach ($_cache as $_file) {
+                if (strpos($_file, '.svn') !== false) continue; 
+                // directory ?
+                if ($_file->isDir()) {
+                    if (!$_cache->isDot()) {
+                        // delete folder if empty
+                        @rmdir($_file->getPathname());
+                    } 
+                } else {
+                    $_parts = explode($_dir_sep, str_replace('\\', '/', substr((string)$_file, $_dir_length)));
+                    $_parts_count = count($_parts); 
+                    // check name
+                    if (isset($resource_name)) {
+                        if ($_parts[$_parts_count-1] != $_resourcename_parts) {
+                            continue;
+                        } 
+                    } 
+                    // check compile id
+                    if (isset($_compile_id) && (!isset($_parts[$_parts_count-2 - $_compile_id_offset]) || $_parts[$_parts_count-2 - $_compile_id_offset] != $_compile_id)) {
+                        continue;
+                    } 
+                    // check cache id
+                    if (isset($_cache_id)) {
+                        // count of cache id parts
+                        $_parts_count = (isset($_compile_id)) ? $_parts_count - 2 - $_compile_id_offset : $_parts_count - 1 - $_compile_id_offset;
+                        if ($_parts_count < $_cache_id_parts_count) {
+                            continue;
+                        } 
+                        for ($i = 0; $i < $_cache_id_parts_count; $i++) {
+                            if ($_parts[$i] != $_cache_id_parts[$i]) continue 2;
+                        } 
+                    } 
+                    // expired ?
+                    if (isset($exp_time) && time() - @filemtime($_file) < $exp_time) {
+                        continue;
+                    } 
+                    $_count += @unlink((string) $_file) ? 1 : 0;
+                } 
+            } 
+        } 
+        return $_count;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_append.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_append.php
new file mode 100644 (file)
index 0000000..98e696e
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Append
+ * 
+ * Compiles the {append} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Compile Append Class
+ */
+class Smarty_Internal_Compile_Append extends Smarty_Internal_Compile_Assign {
+
+    /**
+     * Compiles code for the {append} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // the following must be assigned at runtime because it will be overwritten in parent class
+        $this->required_attributes = array('var', 'value');
+        $this->shorttag_order = array('var', 'value');
+        $this->optional_attributes = array('scope','index');
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // map to compile assign attributes
+        if (isset($_attr['index'])) {
+            $_params['smarty_internal_index'] = '[' . $_attr['index'] . ']';
+            unset($_attr['index']);
+        } else {
+            $_params['smarty_internal_index'] = '[]';
+        }
+        $_new_attr = array();
+        foreach ($_attr as $key => $value) {
+            $_new_attr[] = array($key => $value);
+        } 
+        // call compile assign
+        return parent::compile($_new_attr, $compiler, $_params);
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_assign.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_assign.php
new file mode 100644 (file)
index 0000000..8d8c643
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Assign
+ * 
+ * Compiles the {assign} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Assign Class
+ */
+class Smarty_Internal_Compile_Assign extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {assign} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // the following must be assigned at runtime because it will be overwritten in Smarty_Internal_Compile_Append
+        $this->required_attributes = array('var', 'value');
+        $this->shorttag_order = array('var', 'value');
+        $this->optional_attributes = array('scope');
+        $_nocache = 'null';
+        $_scope = 'null'; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+               // nocache ?
+        if ($this->compiler->tag_nocache || $this->compiler->nocache) {
+            $_nocache = 'true'; 
+            // create nocache var to make it know for further compiling
+            $compiler->template->tpl_vars[trim($_attr['var'], "'")] = new Smarty_variable(null, true);
+        } 
+        // scope setup
+        if (isset($_attr['scope'])) {
+            $_attr['scope'] = trim($_attr['scope'], "'\"");
+            if ($_attr['scope'] == 'parent') {
+                $_scope = Smarty::SCOPE_PARENT;
+            } elseif ($_attr['scope'] == 'root') {
+                $_scope = Smarty::SCOPE_ROOT;
+            } elseif ($_attr['scope'] == 'global') {
+                $_scope = Smarty::SCOPE_GLOBAL;
+            } else {
+                $this->compiler->trigger_template_error('illegal value for "scope" attribute', $this->compiler->lex->taglineno);
+            } 
+        } 
+        // compiled output
+        if (isset($parameter['smarty_internal_index'])) {
+            return "<?php if (!isset(\$_smarty_tpl->tpl_vars[$_attr[var]]) || !is_array(\$_smarty_tpl->tpl_vars[$_attr[var]]->value)) \$_smarty_tpl->createLocalArrayVariable($_attr[var], $_nocache, $_scope);\n\$_smarty_tpl->tpl_vars[$_attr[var]]->value$parameter[smarty_internal_index] = $_attr[value];?>";
+        } else {
+            return "<?php \$_smarty_tpl->tpl_vars[$_attr[var]] = new Smarty_variable($_attr[value], $_nocache, $_scope);?>";
+        } 
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_block.php
new file mode 100644 (file)
index 0000000..cd1ca28
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Block
+ * 
+ * Compiles the {block}{/block} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Block Class
+ */
+class Smarty_Internal_Compile_Block extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('name');
+    public $shorttag_order = array('name');
+    /**
+     * Compiles code for the {block} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return boolean true
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        $save = array($_attr, $compiler->parser->current_buffer, $this->compiler->nocache, $this->compiler->smarty->merge_compiled_includes, $compiler->smarty->inheritance);
+        $this->_open_tag('block', $save);
+        if ($_attr['nocache'] == true) {
+            $compiler->nocache = true;
+        }
+        // set flag for {block} tag
+        $compiler->smarty->inheritance = true;
+        // must merge includes
+        $this->compiler->smarty->merge_compiled_includes = true; 
+
+        $compiler->parser->current_buffer = new _smarty_template_buffer($compiler->parser);
+        $compiler->has_code = false;
+        return true;
+    } 
+
+
+    static function saveBlockData($block_content, $block_tag, $template, $filepath)
+    {
+       $_rdl = preg_quote($template->smarty->right_delimiter);
+        $_ldl = preg_quote($template->smarty->left_delimiter);
+
+        if (0 == preg_match("!({$_ldl}block\s+)(name=)?(\w+|'.*'|\".*\")(\s*?)?((append|prepend|nocache)(=true)?)?(\s*{$_rdl})!", $block_tag, $_match)) {
+            $error_text = 'Syntax Error in template "' . $template->getTemplateFilepath() . '"   "' . htmlspecialchars($block_tag) . '" illegal options';
+            throw new SmartyCompilerException($error_text);
+        } else {
+            $_name = trim($_match[3], '\'"'); 
+            // replace {$smarty.block.child}
+            if (strpos($block_content, $template->smarty->left_delimiter . '$smarty.block.child' . $template->smarty->right_delimiter) !== false) {
+                if (isset($template->block_data[$_name])) {
+                    $block_content = str_replace($template->smarty->left_delimiter . '$smarty.block.child' . $template->smarty->right_delimiter,
+                        $template->block_data[$_name]['source'], $block_content);
+                    unset($template->block_data[$_name]);
+                } else {
+                    $block_content = str_replace($template->smarty->left_delimiter . '$smarty.block.child' . $template->smarty->right_delimiter,
+                        '', $block_content);
+                } 
+            } 
+            if (isset($template->block_data[$_name])) {
+                if (strpos($template->block_data[$_name]['source'], '%%%%SMARTY_PARENT%%%%') !== false) {
+                    $template->block_data[$_name]['source'] =
+                    str_replace('%%%%SMARTY_PARENT%%%%', $block_content, $template->block_data[$_name]['source']);
+                } elseif ($template->block_data[$_name]['mode'] == 'prepend') {
+                    $template->block_data[$_name]['source'] .= $block_content;
+                } elseif ($template->block_data[$_name]['mode'] == 'append') {
+                    $template->block_data[$_name]['source'] = $block_content . $template->block_data[$_name]['source'];
+                } 
+            } else {
+                $template->block_data[$_name]['source'] = $block_content;
+            } 
+            if ($_match[6] == 'append') {
+                $template->block_data[$_name]['mode'] = 'append';
+            } elseif ($_match[6] == 'prepend') {
+                $template->block_data[$_name]['mode'] = 'prepend';
+            } else {
+                $template->block_data[$_name]['mode'] = 'replace';
+            } 
+            $template->block_data[$_name]['file'] = $filepath;
+        }
+    }
+
+       static function compileChildBlock ($compiler, $_name = null)
+       {
+               $_output = '';
+        // if called by {$smarty.block.child} we must search the name of enclosing {block}
+               if ($_name == null) {
+               $stack_count = count($compiler->_tag_stack);
+            while (--$stack_count >= 0) {
+               if ($compiler->_tag_stack[$stack_count][0] == 'block') {
+                       $_name = trim($compiler->_tag_stack[$stack_count][1][0]['name'] ,"'\"");
+                       break;
+                }
+            }
+               // flag that child is already compile by {$smarty.block.child} inclusion
+        $compiler->template->block_data[$_name]['compiled'] = true;
+        }
+               if ($_name == null) {
+                       $compiler->trigger_template_error('{$smarty.block.child} used out of context', $this->compiler->lex->taglineno);
+               }
+               // undefined child?
+               if (!isset($compiler->template->block_data[$_name])) {
+                       return '';
+               }
+               $_tpl = new Smarty_Internal_template ('eval:' . $compiler->template->block_data[$_name]['source'], $compiler->smarty, $compiler->template, $compiler->template->cache_id, 
+                              $compiler->template->compile_id = null, $compiler->template->caching, $compiler->template->cache_lifetime);
+               $_tpl->properties['nocache_hash'] = $compiler->template->properties['nocache_hash'];
+               $_tpl->template_filepath = $compiler->template->block_data[$_name]['file'];
+               if ($compiler->nocache) {
+                       $_tpl->forceNocache = 2;
+               } else {
+                       $_tpl->forceNocache = 1;
+               }
+               $_tpl->suppressHeader = true;
+               $_tpl->suppressFileDependency = true;
+               if (strpos($compiler->template->block_data[$_name]['source'], '%%%%SMARTY_PARENT%%%%') !== false) {
+                       $_output = str_replace('%%%%SMARTY_PARENT%%%%', $compiler->parser->current_buffer->to_smarty_php(), $_tpl->getCompiledTemplate());
+               } elseif ($compiler->template->block_data[$_name]['mode'] == 'prepend') {
+                       $_output = $_tpl->getCompiledTemplate() . $compiler->parser->current_buffer->to_smarty_php();
+               } elseif ($compiler->template->block_data[$_name]['mode'] == 'append') {
+                       $_output = $compiler->parser->current_buffer->to_smarty_php() . $_tpl->getCompiledTemplate();
+               } elseif (!empty($compiler->template->block_data[$_name])) {
+                       $_output = $_tpl->getCompiledTemplate();
+               }
+               $compiler->template->properties['file_dependency'] = array_merge($compiler->template->properties['file_dependency'], $_tpl->properties['file_dependency']);
+               $compiler->template->properties['function'] = array_merge($compiler->template->properties['function'], $_tpl->properties['function']);
+               if ($_tpl->has_nocache_code) {
+                       $compiler->template->has_nocache_code = true;
+               }
+               foreach($_tpl->required_plugins as $code => $tmp1) {
+                       foreach($tmp1 as $name => $tmp) {
+                               foreach($tmp as $type => $data) {
+                                       $compiler->template->required_plugins[$code][$name][$type] = $data;
+                               }
+                       }
+               }
+               unset($_tpl);
+               return $_output;
+       }
+
+}
+
+/**
+ * Smarty Internal Plugin Compile BlockClose Class
+ */
+class Smarty_Internal_Compile_Blockclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/block} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        $this->smarty = $compiler->smarty;
+        $this->compiler->has_code = true; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        $saved_data = $this->_close_tag(array('block'));
+        $_name = trim($saved_data[0]['name'], "\"'");
+        if (isset($compiler->template->block_data[$_name]) && !isset($compiler->template->block_data[$_name]['compiled'])) {
+               $_output = Smarty_Internal_Compile_Block::compileChildBlock($compiler, $_name);
+        } else {
+            $_output = $compiler->parser->current_buffer->to_smarty_php();
+            unset ($compiler->template->block_data[$_name]['compiled']);
+        }
+        // reset flags
+        $compiler->parser->current_buffer = $saved_data[1];
+        $compiler->nocache = $saved_data[2];
+        $compiler->smarty->merge_compiled_includes = $saved_data[3];
+        $compiler->smarty->inheritance = $saved_data[4];
+        // $_output content has already nocache code processed
+        $compiler->suppressNocacheProcessing = true;
+        return $_output;
+    } 
+} 
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_break.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_break.php
new file mode 100644 (file)
index 0000000..0c0bee1
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Break
+ * 
+ * Compiles the {break} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Compile Break Class
+ */
+class Smarty_Internal_Compile_Break extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('levels'); 
+    public $shorttag_order = array('levels');
+
+
+    /**
+     * Compiles code for the {break} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {    
+        $this->compiler = $compiler;
+        $this->smarty = $compiler->smarty;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+
+        if (isset($_attr['levels'])) {
+            if (!is_numeric($_attr['levels'])) {
+                $this->compiler->trigger_template_error('level attribute must be a numeric constant', $this->compiler->lex->taglineno);
+            } 
+            $_levels = $_attr['levels'];
+        } else {
+            $_levels = 1;
+        } 
+        $level_count = $_levels;
+        $stack_count = count($compiler->_tag_stack) - 1;
+        while ($level_count > 0 && $stack_count >= 0) {
+            if (in_array($compiler->_tag_stack[$stack_count][0], array('for', 'foreach', 'while', 'section'))) {
+                $level_count--;
+            } 
+            $stack_count--;
+        } 
+        if ($level_count != 0) {
+            $this->compiler->trigger_template_error("cannot break {$_levels} level(s)", $this->compiler->lex->taglineno);
+        } 
+        // this tag does not return compiled code
+        $this->compiler->has_code = true;
+        return "<?php break {$_levels}?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_call.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_call.php
new file mode 100644 (file)
index 0000000..50ba632
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Function_Call
+ * 
+ * Compiles the calls of user defined tags defined by {function}
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Function_Call Class
+ */
+class Smarty_Internal_Compile_Call extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('name');
+    public $shorttag_order = array('name');
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles the calls of user defined tags defined by {function}
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        $this->smarty = $compiler->smarty;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // save possible attributes
+        if (isset($_attr['assign'])) {
+            // output will be stored in a smarty variable instead of beind displayed
+            $_assign = $_attr['assign'];
+        } 
+        $_name = $_attr['name'];
+        unset($_attr['name'], $_attr['assign'], $_attr['nocache']); 
+        // set flag (compiled code of {function} must be included in cache file
+        if ($compiler->nocache || $compiler->tag_nocache) {
+            $_nocache = 'true';
+        } else {
+            $_nocache = 'false';
+        } 
+        $_paramsArray = array();
+        foreach ($_attr as $_key => $_value) {
+            if (is_int($_key)) {
+                $_paramsArray[] = "$_key=>$_value";
+            } else {
+                $_paramsArray[] = "'$_key'=>$_value";
+            } 
+        }
+        if (isset($compiler->template->properties['function'][$_name]['parameter'])) {
+            foreach ($compiler->template->properties['function'][$_name]['parameter'] as $_key => $_value) {
+                if (!isset($_attr[$_key])) {
+                    if (is_int($_key)) {
+                        $_paramsArray[] = "$_key=>$_value";
+                    } else {
+                        $_paramsArray[] = "'$_key'=>$_value";
+                    } 
+                } 
+            } 
+        } elseif (isset($this->smarty->template_functions[$_name]['parameter'])) {
+           foreach ($this->smarty->template_functions[$_name]['parameter'] as $_key => $_value) {
+                if (!isset($_attr[$_key])) {
+                    if (is_int($_key)) {
+                        $_paramsArray[] = "$_key=>$_value";
+                    } else {
+                        $_paramsArray[] = "'$_key'=>$_value";
+                    } 
+                } 
+            } 
+        }
+        //varibale name?
+        if (!(strpos($_name,'$')===false)) {
+               $call_cache = $_name;
+               $call_function = '$tmp = "smarty_template_function_".'.$_name.'; $tmp';
+               } else {
+               $_name = trim($_name, "'\"");
+                       $call_cache = "'{$_name}'";
+               $call_function = 'smarty_template_function_'.$_name;
+        }
+       
+        $_params = 'array(' . implode(",", $_paramsArray) . ')';
+       $_hash = str_replace('-','_',$compiler->template->properties['nocache_hash']);
+        // was there an assign attribute
+        if (isset($_assign)) {
+            if ($compiler->template->caching) {
+                $_output = "<?php ob_start(); Smarty_Internal_Function_Call_Handler::call ({$call_cache},\$_smarty_tpl,{$_params},'{$_hash}',{$_nocache}); \$_smarty_tpl->assign({$_assign}, ob_get_clean());?>\n";
+            } else {
+                $_output = "<?php ob_start(); {$call_function}(\$_smarty_tpl,{$_params}); \$_smarty_tpl->assign({$_assign}, ob_get_clean());?>\n";
+            } 
+        } else {
+            if ($compiler->template->caching) {
+                $_output = "<?php Smarty_Internal_Function_Call_Handler::call ({$call_cache},\$_smarty_tpl,{$_params},'{$_hash}',{$_nocache});?>\n";
+            } else {
+                $_output = "<?php {$call_function}(\$_smarty_tpl,{$_params});?>\n";
+            } 
+        } 
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_capture.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_capture.php
new file mode 100644 (file)
index 0000000..74ade68
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Capture
+ * 
+ * Compiles the {capture} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Capture Class
+ */
+class Smarty_Internal_Compile_Capture extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $shorttag_order = array('name');
+    public $optional_attributes = array('name', 'assign', 'append'); 
+
+    /**
+     * Compiles code for the {capture} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        $buffer = isset($_attr['name']) ? $_attr['name'] : "'default'";
+        $assign = isset($_attr['assign']) ? $_attr['assign'] : null;
+        $append = isset($_attr['append']) ? $_attr['append'] : null;
+
+        $this->compiler->_capture_stack[] = array($buffer, $assign, $append, $this->compiler->nocache);
+        // maybe nocache because of nocache variables
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache; 
+        $_output = "<?php ob_start(); ?>";
+
+        return $_output;
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Captureclose Class
+ */
+class Smarty_Internal_Compile_CaptureClose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/capture} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        // must endblock be nocache?
+        if ($this->compiler->nocache) {
+            $this->compiler->tag_nocache = true;
+        } 
+
+        list($buffer, $assign, $append, $this->compiler->nocache) = array_pop($this->compiler->_capture_stack);
+
+        $_output = "<?php ";
+        if (isset($assign)) {
+            $_output .= " \$_smarty_tpl->assign($assign, ob_get_contents());";
+        } 
+        if (isset($append)) {
+            $_output .= " \$_smarty_tpl->append($append, ob_get_contents());";
+        } 
+        $_output .= " Smarty::\$_smarty_vars['capture'][$buffer]=ob_get_clean();?>";
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_config_load.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_config_load.php
new file mode 100644 (file)
index 0000000..733e1ac
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Config Load
+ * 
+ * Compiles the {config load} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Config Load Class
+ */
+class Smarty_Internal_Compile_Config_Load extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('file');
+    public $shorttag_order = array('file','section');
+    public $optional_attributes = array('section', 'scope'); 
+
+    /**
+     * Compiles code for the {config_load} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+
+         
+        // save posible attributes
+        $conf_file = $_attr['file'];
+        if (isset($_attr['section'])) {
+            $section = $_attr['section'];
+        } else {
+            $section = 'null';
+        } 
+        $scope = 'local';
+        // scope setup
+        if (isset($_attr['scope'])) {
+            $_attr['scope'] = trim($_attr['scope'], "'\"");
+            if (in_array($_attr['scope'],array('local','parent','root','global'))) {
+                $scope = $_attr['scope'];
+           } else {
+                $this->compiler->trigger_template_error('illegal value for "scope" attribute', $this->compiler->lex->taglineno);
+           } 
+        } 
+        // create config object
+        $_output = "<?php  \$_config = new Smarty_Internal_Config($conf_file, \$_smarty_tpl->smarty, \$_smarty_tpl);";
+        $_output .= "\$_config->loadConfigVars($section, '$scope'); ?>";
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_continue.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_continue.php
new file mode 100644 (file)
index 0000000..ddbbc81
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Continue
+ * 
+ * Compiles the {continue} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Compile Continue Class
+ */
+class Smarty_Internal_Compile_Continue extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('levels'); 
+    public $shorttag_order = array('levels');
+
+    /**
+     * Compiles code for the {continue} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        $this->smarty = $compiler->smarty;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+
+        if (isset($_attr['levels'])) {
+            if (!is_numeric($_attr['levels'])) {
+                $this->compiler->trigger_template_error('level attribute must be a numeric constant', $this->compiler->lex->taglineno);
+            } 
+            $_levels = $_attr['levels'];
+        } else {
+            $_levels = 1;
+        } 
+        $level_count = $_levels;
+        $stack_count = count($compiler->_tag_stack) - 1;
+        while ($level_count > 0 && $stack_count >= 0) {
+            if (in_array($compiler->_tag_stack[$stack_count][0], array('for', 'foreach', 'while', 'section'))) {
+                $level_count--;
+            } 
+            $stack_count--;
+        } 
+        if ($level_count != 0) {
+            $this->compiler->trigger_template_error("cannot continue {$_levels} level(s)", $this->compiler->lex->taglineno);
+        } 
+        // this tag does not return compiled code
+        $this->compiler->has_code = true;
+        return "<?php continue {$_levels}?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_debug.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_debug.php
new file mode 100644 (file)
index 0000000..f6189e7
--- /dev/null
@@ -0,0 +1,38 @@
+<?php 
+/**
+ * Smarty Internal Plugin Compile Debug
+ *
+ * Compiles the {debug} tag 
+ * It opens a window the the Smarty Debugging Console
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Debug Class
+ */ 
+class Smarty_Internal_Compile_Debug extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {debug} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+               // compile always as nocache
+               $this->compiler->tag_nocache = true;
+
+        // display debug template
+        $_output = "<?php \$_smarty_tpl->smarty->loadPlugin('Smarty_Internal_Debug'); Smarty_Internal_Debug::display_debug(\$_smarty_tpl); ?>";
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_eval.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_eval.php
new file mode 100644 (file)
index 0000000..aa3c258
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Eval
+ *
+ * Compiles the {eval} tag 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Eval Class
+ */ 
+class Smarty_Internal_Compile_Eval extends Smarty_Internal_CompileBase {
+    public $required_attributes = array('var');
+    public $optional_attributes = array('assign'); 
+    public $shorttag_order = array('var','assign');
+
+    /**
+     * Compiles code for the {eval} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        $this->required_attributes = array('var');
+        $this->optional_attributes = array('assign'); 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        if (isset($_attr['assign'])) {
+              // output will be stored in a smarty variable instead of beind displayed
+            $_assign = $_attr['assign'];
+        }
+  
+        // create template object
+        $_output = "\$_template = new {$compiler->smarty->template_class}('eval:'.".$_attr['var'].", \$_smarty_tpl->smarty, \$_smarty_tpl);"; 
+        //was there an assign attribute? 
+        if (isset($_assign)) {
+            $_output .= "\$_smarty_tpl->assign($_assign,\$_template->getRenderedTemplate());";
+        } else {
+            $_output .= "echo \$_template->getRenderedTemplate();";
+        } 
+        return "<?php $_output ?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_extends.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_extends.php
new file mode 100644 (file)
index 0000000..b62b531
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile extend
+ * 
+ * Compiles the {extends} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile extend Class
+ */
+class Smarty_Internal_Compile_Extends extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('file');
+    public $shorttag_order = array('file');
+
+    /**
+     * Compiles code for the {extends} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        $this->smarty = $compiler->smarty;
+        $this->_rdl = preg_quote($this->smarty->right_delimiter);
+        $this->_ldl = preg_quote($this->smarty->left_delimiter);
+        $filepath = $compiler->template->getTemplateFilepath();
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+
+        $_smarty_tpl = $compiler->template; 
+        $include_file = null;
+        if (strpos($_attr['file'],'$_tmp') !== false) {
+               $this->compiler->trigger_template_error('illegal value for file attribute', $this->compiler->lex->taglineno);
+        }
+        eval('$include_file = ' . $_attr['file'] . ';'); 
+        // create template object
+        $_template = new $compiler->smarty->template_class($include_file, $this->smarty, $compiler->template); 
+        // save file dependency
+        if (in_array($_template->resource_type,array('eval','string'))) {
+               $template_sha1 = sha1($include_file);
+       } else {
+               $template_sha1 = sha1($_template->getTemplateFilepath());
+       }
+        if (isset($compiler->template->properties['file_dependency'][$template_sha1])) {
+            $this->compiler->trigger_template_error("illegal recursive call of \"{$include_file}\"",$compiler->lex->line-1);
+        } 
+        $compiler->template->properties['file_dependency'][$template_sha1] = array($_template->getTemplateFilepath(), $_template->getTemplateTimestamp(),$_template->resource_type);
+        $_content = substr($compiler->template->template_source,$compiler->lex->counter-1);
+        if (preg_match_all("!({$this->_ldl}block\s(.+?){$this->_rdl})!", $_content, $s) !=
+                preg_match_all("!({$this->_ldl}/block{$this->_rdl})!", $_content, $c)) {
+            $this->compiler->trigger_template_error('unmatched {block} {/block} pairs');
+        } 
+        preg_match_all("!{$this->_ldl}block\s(.+?){$this->_rdl}|{$this->_ldl}/block{$this->_rdl}!", $_content, $_result, PREG_OFFSET_CAPTURE);
+        $_result_count = count($_result[0]);
+        $_start = 0;
+        while ($_start < $_result_count) {
+            $_end = 0;
+            $_level = 1;
+            while ($_level != 0) {
+                $_end++;
+                if (!strpos($_result[0][$_start + $_end][0], '/')) {
+                    $_level++;
+                } else {
+                    $_level--;
+                } 
+            } 
+            $_block_content = str_replace($this->smarty->left_delimiter . '$smarty.block.parent' . $this->smarty->right_delimiter, '%%%%SMARTY_PARENT%%%%',
+                substr($_content, $_result[0][$_start][1] + strlen($_result[0][$_start][0]), $_result[0][$_start + $_end][1] - $_result[0][$_start][1] - + strlen($_result[0][$_start][0])));
+            Smarty_Internal_Compile_Block::saveBlockData($_block_content, $_result[0][$_start][0], $compiler->template, $filepath);
+            $_start = $_start + $_end + 1;
+        } 
+        $compiler->template->template_source = $_template->getTemplateSource();
+        $compiler->template->template_filepath = $_template->getTemplateFilepath();
+        $compiler->abort_and_recompile = true;
+        return '';
+    } 
+
+} 
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_for.php
new file mode 100644 (file)
index 0000000..2e5d0a1
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile For
+ * 
+ * Compiles the {for} {forelse} {/for} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile For Class
+ */
+class Smarty_Internal_Compile_For extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {for} tag
+     * 
+     * Smarty 3 does implement two different sytaxes:
+     * 
+     * - {for $var in $array}
+     * For looping over arrays or iterators
+     * 
+     * - {for $x=0; $x<$y; $x++}
+     * For general loops
+     * 
+     * The parser is gereration different sets of attribute by which this compiler can 
+     * determin which syntax is used.
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        if ($parameter == 0) {
+               $this->required_attributes = array('start','to');
+               $this->optional_attributes = array('max','step');
+        } else {
+               $this->required_attributes = array('start','ifexp','var','step');
+               $this->optional_attributes = array();
+        }
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        $local_vars = array();
+
+        $output = "<?php ";
+        if ($parameter == 1) {
+            foreach ($_attr['start'] as $_statement) {
+                $output .= " \$_smarty_tpl->tpl_vars[$_statement[var]] = new Smarty_Variable;";
+                $output .= " \$_smarty_tpl->tpl_vars[$_statement[var]]->value = $_statement[value];\n";
+                $compiler->local_var[$_statement['var']] = true;
+                $local_vars[] = $_statement['var'];
+            } 
+            $output .= "  if ($_attr[ifexp]){ for (\$_foo=true;$_attr[ifexp]; \$_smarty_tpl->tpl_vars[$_attr[var]]->value$_attr[step]){\n";
+        } else {
+            $_statement = $_attr['start'];
+            $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]] = new Smarty_Variable;";
+            $compiler->local_var[$_statement['var']] = true;
+            $local_vars[] = $_statement['var'];
+            if (isset($_attr['step'])) {
+                $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->step = $_attr[step];";
+            } else {
+                $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->step = 1;";
+            } 
+            if (isset($_attr['max'])) {
+                $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->total = (int)min(ceil((\$_smarty_tpl->tpl_vars[$_statement[var]]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$_statement[var]]->step)),$_attr[max]);\n";
+            } else {
+                $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->total = (int)ceil((\$_smarty_tpl->tpl_vars[$_statement[var]]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$_statement[var]]->step));\n";
+            } 
+            $output .= "if (\$_smarty_tpl->tpl_vars[$_statement[var]]->total > 0){\n";
+            $output .= "for (\$_smarty_tpl->tpl_vars[$_statement[var]]->value = $_statement[value], \$_smarty_tpl->tpl_vars[$_statement[var]]->iteration = 1;\$_smarty_tpl->tpl_vars[$_statement[var]]->iteration <= \$_smarty_tpl->tpl_vars[$_statement[var]]->total;\$_smarty_tpl->tpl_vars[$_statement[var]]->value += \$_smarty_tpl->tpl_vars[$_statement[var]]->step, \$_smarty_tpl->tpl_vars[$_statement[var]]->iteration++){\n";
+            $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->first = \$_smarty_tpl->tpl_vars[$_statement[var]]->iteration == 1;";
+            $output .= "\$_smarty_tpl->tpl_vars[$_statement[var]]->last = \$_smarty_tpl->tpl_vars[$_statement[var]]->iteration == \$_smarty_tpl->tpl_vars[$_statement[var]]->total;";
+        } 
+        $output .= "?>";
+
+        $this->_open_tag('for', array('for', $this->compiler->nocache, $local_vars)); 
+        // maybe nocache because of nocache variables
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache; 
+        // return compiled code
+        return $output;
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Forelse Class
+ */
+class Smarty_Internal_Compile_Forelse extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {forelse} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr  = $this->_get_attributes($args);
+
+        list($_open_tag, $nocache, $local_vars) = $this->_close_tag(array('for'));
+        $this->_open_tag('forelse', array('forelse', $nocache, $local_vars));
+        return "<?php }} else { ?>";
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Forclose Class
+ */
+class Smarty_Internal_Compile_Forclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/for} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr  = $this->_get_attributes($args); 
+        // must endblock be nocache?
+        if ($this->compiler->nocache) {
+            $this->compiler->tag_nocache = true;
+        } 
+
+        list($_open_tag, $this->compiler->nocache, $local_vars) = $this->_close_tag(array('for', 'forelse'));
+
+        foreach ($local_vars as $var) {
+            unset($compiler->local_var[$var]);
+        } 
+        if ($_open_tag == 'forelse')
+            return "<?php }  ?>";
+        else
+            return "<?php }} ?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_foreach.php
new file mode 100644 (file)
index 0000000..6ad1e07
--- /dev/null
@@ -0,0 +1,219 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Foreach
+ * 
+ * Compiles the {foreach} {foreachelse} {/foreach} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Foreach Class
+ */
+class Smarty_Internal_Compile_Foreach extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('from', 'item');
+    public $optional_attributes = array('name', 'key'); 
+    public $shorttag_order = array('from','item','key','name');
+
+    /**
+     * Compiles code for the {foreach} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        $tpl = $compiler->template; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        $from = $_attr['from'];
+        $item = $_attr['item'];
+
+        if (substr_compare("\$_smarty_tpl->getVariable($item)", $from,0, strlen("\$_smarty_tpl->getVariable($item)")) == 0) {
+            $this->compiler->trigger_template_error("item variable {$item} may not be the same variable as at 'from'", $this->compiler->lex->taglineno);
+        } 
+
+        if (isset($_attr['key'])) {
+            $key = $_attr['key'];
+        } else {
+            $key = null;
+        } 
+
+        $this->_open_tag('foreach', array('foreach', $this->compiler->nocache, $item, $key)); 
+        // maybe nocache because of nocache variables
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache;
+
+        if (isset($_attr['name'])) {
+            $name = $_attr['name'];
+            $has_name = true;
+            $SmartyVarName = '$smarty.foreach.' . trim($name, '\'"') . '.';
+        } else {
+            $name = null;
+            $has_name = false;
+        } 
+        $ItemVarName = '$' . trim($item, '\'"') . '@'; 
+        // evaluates which Smarty variables and properties have to be computed
+        if ($has_name) {
+            $usesSmartyFirst = strpos($tpl->template_source, $SmartyVarName . 'first') !== false;
+            $usesSmartyLast = strpos($tpl->template_source, $SmartyVarName . 'last') !== false;
+            $usesSmartyIndex = strpos($tpl->template_source, $SmartyVarName . 'index') !== false;
+            $usesSmartyIteration = strpos($tpl->template_source, $SmartyVarName . 'iteration') !== false;
+            $usesSmartyShow = strpos($tpl->template_source, $SmartyVarName . 'show') !== false;
+            $usesSmartyTotal = strpos($tpl->template_source, $SmartyVarName . 'total') !== false;
+        } else {
+            $usesSmartyFirst = false;
+            $usesSmartyLast = false;
+            $usesSmartyTotal = false;
+            $usesSmartyShow = false;
+        } 
+
+        $usesPropFirst = $usesSmartyFirst || strpos($tpl->template_source, $ItemVarName . 'first') !== false;
+        $usesPropLast = $usesSmartyLast || strpos($tpl->template_source, $ItemVarName . 'last') !== false;
+        $usesPropIndex = $usesPropFirst || strpos($tpl->template_source, $ItemVarName . 'index') !== false;
+        $usesPropIteration = $usesPropLast || strpos($tpl->template_source, $ItemVarName . 'iteration') !== false;
+        $usesPropShow = strpos($tpl->template_source, $ItemVarName . 'show') !== false;
+        $usesPropTotal = $usesSmartyTotal || $usesSmartyShow || $usesPropShow || $usesPropLast || strpos($tpl->template_source, $ItemVarName . 'total') !== false; 
+        // generate output code
+        $output = "<?php ";
+        $output .= " \$_smarty_tpl->tpl_vars[$item] = new Smarty_Variable;\n";
+        $compiler->local_var[$item] = true;
+        if ($key != null) {
+            $output .= " \$_smarty_tpl->tpl_vars[$key] = new Smarty_Variable;\n";
+            $compiler->local_var[$key] = true;
+        } 
+        $output .= " \$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array');}\n";
+        if ($usesPropTotal) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->total= \$_smarty_tpl->_count(\$_from);\n";
+        } 
+        if ($usesPropIteration) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->iteration=0;\n";
+        } 
+        if ($usesPropIndex) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->index=-1;\n";
+        } 
+        if ($usesPropShow) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->show = (\$_smarty_tpl->tpl_vars[$item]->total > 0);\n";
+        } 
+        if ($has_name) {
+            if ($usesSmartyTotal) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['total'] = \$_smarty_tpl->tpl_vars[$item]->total;\n";
+            } 
+            if ($usesSmartyIteration) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['iteration']=0;\n";
+            } 
+            if ($usesSmartyIndex) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['index']=-1;\n";
+            } 
+            if ($usesSmartyShow) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['show']=(\$_smarty_tpl->tpl_vars[$item]->total > 0);\n";
+            } 
+        } 
+        if ($usesPropTotal) {
+                       $output .= "if (\$_smarty_tpl->tpl_vars[$item]->total > 0){\n";
+        } else {
+                       $output .= "if (\$_smarty_tpl->_count(\$_from) > 0){\n";
+               }
+               $output .= "    foreach (\$_from as \$_smarty_tpl->tpl_vars[$item]->key => \$_smarty_tpl->tpl_vars[$item]->value){\n";
+        if ($key != null) {
+            $output .= " \$_smarty_tpl->tpl_vars[$key]->value = \$_smarty_tpl->tpl_vars[$item]->key;\n";
+        } 
+        if ($usesPropIteration) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->iteration++;\n";
+        } 
+        if ($usesPropIndex) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->index++;\n";
+        } 
+        if ($usesPropFirst) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->first = \$_smarty_tpl->tpl_vars[$item]->index === 0;\n";
+        } 
+        if ($usesPropLast) {
+            $output .= " \$_smarty_tpl->tpl_vars[$item]->last = \$_smarty_tpl->tpl_vars[$item]->iteration === \$_smarty_tpl->tpl_vars[$item]->total;\n";
+        } 
+        if ($has_name) {
+            if ($usesSmartyFirst) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['first'] = \$_smarty_tpl->tpl_vars[$item]->first;\n";
+            } 
+            if ($usesSmartyIteration) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['iteration']++;\n";
+            } 
+            if ($usesSmartyIndex) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['index']++;\n";
+            } 
+            if ($usesSmartyLast) {
+                $output .= " \$_smarty_tpl->tpl_vars['smarty']->value['foreach'][$name]['last'] = \$_smarty_tpl->tpl_vars[$item]->last;\n";
+            } 
+        } 
+        $output .= "?>";
+
+        return $output;
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Foreachelse Class
+ */
+class Smarty_Internal_Compile_Foreachelse extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {foreachelse} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        list($_open_tag, $nocache, $item, $key) = $this->_close_tag(array('foreach'));
+        $this->_open_tag('foreachelse', array('foreachelse', $nocache, $item, $key));
+
+        return "<?php }} else { ?>";
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Foreachclose Class
+ */
+class Smarty_Internal_Compile_Foreachclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/foreach} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // must endblock be nocache?
+        if ($this->compiler->nocache) {
+            $this->compiler->tag_nocache = true;
+        } 
+
+        list($_open_tag, $this->compiler->nocache, $item, $key) = $this->_close_tag(array('foreach', 'foreachelse'));
+        unset($compiler->local_var[$item]);
+        if ($key != null) {
+            unset($compiler->local_var[$key]);
+        } 
+
+        if ($_open_tag == 'foreachelse')
+            return "<?php } ?>";
+        else
+            return "<?php }} ?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_function.php
new file mode 100644 (file)
index 0000000..db2409a
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Function
+ * 
+ * Compiles the {function} {/function} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Function Class
+ */
+class Smarty_Internal_Compile_Function extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('name');
+    public $shorttag_order = array('name');
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the {function} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+      * @param array $parameter array with compilation parameter
+    * @return boolean true
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+               unset($_attr['nocache']);
+        $save = array($_attr, $compiler->parser->current_buffer,
+            $compiler->template->has_nocache_code, $compiler->template->required_plugins);
+        $this->_open_tag('function', $save);
+        $_name = trim($_attr['name'], "'\"");
+        unset($_attr['name']);
+        $compiler->template->properties['function'][$_name]['parameter'] = array();
+               $_smarty_tpl = $compiler->template;
+        foreach ($_attr as $_key => $_data) {
+               eval ('$tmp='.$_data.';');
+            $compiler->template->properties['function'][$_name]['parameter'][$_key] = $tmp;
+        } 
+        $compiler->smarty->template_functions[$_name]['parameter'] = $compiler->template->properties['function'][$_name]['parameter'];
+        if ($compiler->template->caching) {
+            $output = '';
+        } else {
+            $output = "<?php if (!function_exists('smarty_template_function_{$_name}')) {
+    function smarty_template_function_{$_name}(\$_smarty_tpl,\$params) {
+    \$saved_tpl_vars = \$_smarty_tpl->tpl_vars;
+    foreach (\$_smarty_tpl->template_functions['{$_name}']['parameter'] as \$key => \$value) {\$_smarty_tpl->tpl_vars[\$key] = new Smarty_variable(\$value);};
+    foreach (\$params as \$key => \$value) {\$_smarty_tpl->tpl_vars[\$key] = new Smarty_variable(\$value);}?>";
+        } 
+        // Init temporay context
+        $compiler->template->required_plugins = array('compiled' => array(), 'nocache' => array());
+        $compiler->parser->current_buffer = new _smarty_template_buffer($compiler->parser);
+        $compiler->parser->current_buffer->append_subtree(new _smarty_tag($compiler->parser, $output));
+        $compiler->template->has_nocache_code = false;
+        $compiler->has_code = false;
+        $compiler->template->properties['function'][$_name]['compiled'] = '';
+        return true;
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Functionclose Class
+ */
+class Smarty_Internal_Compile_Functionclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/function} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return boolean true
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        $_attr = $this->_get_attributes($args);
+        $saved_data = $this->_close_tag(array('function'));
+        $_name = trim($saved_data[0]['name'], "'\""); 
+        // build plugin include code
+        $plugins_string = '';
+        if (!empty($compiler->template->required_plugins['compiled'])) {
+            $plugins_string = '<?php ';
+            foreach($compiler->template->required_plugins['compiled'] as $tmp) {
+                foreach($tmp as $data) {
+                    $plugins_string .= "if (!is_callable('{$data['function']}')) include '{$data['file']}';\n";
+                } 
+            } 
+            $plugins_string .= '?>';
+        } 
+        if (!empty($compiler->template->required_plugins['nocache'])) {
+            $plugins_string .= "<?php echo '/*%%SmartyNocache:{$compiler->template->properties['nocache_hash']}%%*/<?php ";
+            foreach($compiler->template->required_plugins['nocache'] as $tmp) {
+                foreach($tmp as $data) {
+                    $plugins_string .= "if (!is_callable(\'{$data['function']}\')) include \'{$data['file']}\';\n";
+                } 
+            } 
+            $plugins_string .= "?>/*/%%SmartyNocache:{$compiler->template->properties['nocache_hash']}%%*/';?>\n";
+        } 
+               // remove last line break from function definition
+               $last = count($compiler->parser->current_buffer->subtrees) - 1;
+               if ($compiler->parser->current_buffer->subtrees[$last] instanceof _smarty_linebreak) {
+                       unset($compiler->parser->current_buffer->subtrees[$last]);
+               }
+        // if caching save template function for possible nocache call
+        if ($compiler->template->caching) {
+            $compiler->template->properties['function'][$_name]['compiled'] .= $plugins_string
+             . $compiler->parser->current_buffer->to_smarty_php();
+            $compiler->template->properties['function'][$_name]['nocache_hash'] = $compiler->template->properties['nocache_hash'];
+            $compiler->template->properties['function'][$_name]['has_nocache_code'] = $compiler->template->has_nocache_code;
+            $compiler->smarty->template_functions[$_name] = $compiler->template->properties['function'][$_name];
+            $compiler->has_code = false;
+            $output = true;
+        } else {
+            $output = $plugins_string . $compiler->parser->current_buffer->to_smarty_php() . "<?php \$_smarty_tpl->tpl_vars = \$saved_tpl_vars;}}?>\n";
+        } 
+        // restore old compiler status
+        $compiler->parser->current_buffer = $saved_data[1];
+        $compiler->template->has_nocache_code = $compiler->template->has_nocache_code | $saved_data[2];
+        $compiler->template->required_plugins = $saved_data[3];
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_if.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_if.php
new file mode 100644 (file)
index 0000000..41e6597
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile If
+ * 
+ * Compiles the {if} {else} {elseif} {/if} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile If Class
+ */
+class Smarty_Internal_Compile_If extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {if} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        $this->_open_tag('if',array(1,$this->compiler->nocache));
+        // must whole block be nocache ?
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache; 
+        if (is_array($parameter['if condition'])) {
+               if ($this->compiler->nocache) {
+                       $_nocache = ',true';
+               // create nocache var to make it know for further compiling
+               if (is_array($parameter['if condition']['var'])) {
+                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var']['var'], "'")] = new Smarty_variable(null, true);
+               } else {
+                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var'], "'")] = new Smarty_variable(null, true);
+               }
+               } else {
+                       $_nocache = '';
+               }
+            if (is_array($parameter['if condition']['var'])) {
+               $_output = "<?php if (!isset(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]) || !is_array(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value)) \$_smarty_tpl->createLocalArrayVariable(".$parameter['if condition']['var']['var']."$_nocache);\n";
+                   $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value".$parameter['if condition']['var']['smarty_internal_index']." = ".$parameter['if condition']['value']."){?>";
+            } else {
+                   $_output = "<?php \$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."] = new Smarty_Variable(\$_smarty_tpl->getVariable(".$parameter['if condition']['var'].",null,true,false)->value{$_nocache});";           
+                   $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."]->value = ".$parameter['if condition']['value']."){?>";
+               }
+            return $_output;
+        } else {
+            return "<?php if ({$parameter['if condition']}){?>";
+        } 
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Else Class
+ */
+class Smarty_Internal_Compile_Else extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {else} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+        list($nesting, $compiler->tag_nocache) = $this->_close_tag(array('if', 'elseif'));
+        $this->_open_tag('else',array($nesting,$compiler->tag_nocache));
+
+        return "<?php }else{ ?>";
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile ElseIf Class
+ */
+class Smarty_Internal_Compile_Elseif extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {elseif} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        list($nesting, $compiler->tag_nocache) = $this->_close_tag(array('if', 'elseif'));
+
+               if (is_array($parameter['if condition'])) {
+                       $condition_by_assign = true;
+                       if ($this->compiler->nocache) {
+                       $_nocache = ',true';
+                               // create nocache var to make it know for further compiling
+                               if (is_array($parameter['if condition']['var'])) {
+                                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var']['var'], "'")] = new Smarty_variable(null, true);
+                               } else {
+                                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var'], "'")] = new Smarty_variable(null, true);
+                               }
+                       } else {
+                               $_nocache = '';
+                       }
+               } else {
+                       $condition_by_assign = false;
+               }
+
+        if (empty($this->compiler->prefix_code)) {
+               if ($condition_by_assign) {
+               $this->_open_tag('elseif', array($nesting + 1, $compiler->tag_nocache));
+               if (is_array($parameter['if condition']['var'])) {
+                       $_output = "<?php }else{ if (!isset(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]) || !is_array(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value)) \$_smarty_tpl->createLocalArrayVariable(".$parameter['if condition']['var']['var']."$_nocache);\n";
+                       $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value".$parameter['if condition']['var']['smarty_internal_index']." = ".$parameter['if condition']['value']."){?>";
+               } else {
+                       $_output = "<?php }else{ \$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."] = new Smarty_Variable(\$_smarty_tpl->getVariable(".$parameter['if condition']['var'].",null,true,false)->value{$_nocache});";          
+                       $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."]->value = ".$parameter['if condition']['value']."){?>";
+                               }
+               return $_output;
+               } else {
+               $this->_open_tag('elseif', array($nesting, $compiler->tag_nocache));
+               return "<?php }elseif({$parameter['if condition']}){?>";
+               }
+        } else {
+            $tmp = '';
+            foreach ($this->compiler->prefix_code as $code) $tmp .= $code;
+            $this->compiler->prefix_code = array();
+            $this->_open_tag('elseif', array($nesting + 1, $compiler->tag_nocache));
+               if ($condition_by_assign) {
+               if (is_array($parameter['if condition']['var'])) {
+                       $_output = "<?php }else{?>{$tmp}<?php  if (!isset(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]) || !is_array(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value)) \$_smarty_tpl->createLocalArrayVariable(".$parameter['if condition']['var']['var']."$_nocache);\n";
+                       $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value".$parameter['if condition']['var']['smarty_internal_index']." = ".$parameter['if condition']['value']."){?>";
+               } else {
+                       $_output = "<?php }else{?>{$tmp}<?php \$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."] = new Smarty_Variable(\$_smarty_tpl->getVariable(".$parameter['if condition']['var'].",null,true,false)->value{$_nocache});";           
+                       $_output .= "if (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."]->value = ".$parameter['if condition']['value']."){?>";
+                               }
+               return $_output;
+               } else {
+               return "<?php }else{?>{$tmp}<?php if ({$parameter['if condition']}){?>";
+               }
+        } 
+    } 
+} 
+
+/**
+* Smarty Internal Plugin Compile Ifclose Class
+*/
+class Smarty_Internal_Compile_Ifclose extends Smarty_Internal_CompileBase {
+    /**
+    * Compiles code for the {/if} tag
+    * 
+    * @param array $args array with attributes from parser
+    * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+    * @return string compiled code
+    */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler; 
+            // must endblock be nocache?
+            if ($this->compiler->nocache) {
+                $this->compiler->tag_nocache = true;
+            } 
+        list($nesting, $this->compiler->nocache) = $this->_close_tag(array('if', 'else', 'elseif'));
+        $tmp = '';
+        for ($i = 0; $i < $nesting ; $i++) $tmp .= '}';
+        return "<?php {$tmp}?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include.php
new file mode 100644 (file)
index 0000000..01d260e
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Include
+ * 
+ * Compiles the {include} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Include Class
+ */
+class Smarty_Internal_Compile_Include extends Smarty_Internal_CompileBase {
+       // caching mode to create nocache code but no cache file
+       const CACHING_NOCACHE_CODE = 9999;
+       // attribute definitions
+    public $required_attributes = array('file');
+       public $shorttag_order = array('file');
+    public $option_flags = array('nocache','inline','caching');
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the {include} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // save posible attributes
+        $include_file = $_attr['file'];
+        $has_compiled_template = false;
+        if ($compiler->smarty->merge_compiled_includes || $_attr['inline'] === true) {
+            // check if compiled code can be merged (contains no variable part)
+            if (!$compiler->has_variable_string && (substr_count($include_file, '"') == 2 or substr_count($include_file, "'") == 2) and substr_count($include_file, '(') == 0) {
+             $tmp = null;
+           eval("\$tmp = $include_file;");
+                if ($this->compiler->template->template_resource != $tmp) {
+                    $tpl = new $compiler->smarty->template_class ($tmp, $compiler->smarty, $compiler->template, $compiler->template->cache_id, $compiler->template->compile_id);
+                    // suppress writing of compiled file
+                    $tpl->write_compiled_code = false;
+                    if ($this->compiler->template->caching) {
+                        // needs code for cached page but no cache file
+                        $tpl->caching = self::CACHING_NOCACHE_CODE;
+                    } 
+//                    if ($this->compiler->template->mustCompile) {
+                        // make sure whole chain gest compiled
+                        $tpl->mustCompile = true;
+//                    } 
+                    if ($tpl->resource_object->usesCompiler && $tpl->isExisting()) {
+                        // get compiled code
+                        $compiled_tpl = $tpl->getCompiledTemplate(); 
+                        // merge compiled code for {function} tags
+                        $compiler->template->properties['function'] = array_merge($compiler->template->properties['function'], $tpl->properties['function']); 
+                        // merge filedependency by evaluating header code
+                        preg_match_all("/(<\?php \/\*%%SmartyHeaderCode:{$tpl->properties['nocache_hash']}%%\*\/(.+?)\/\*\/%%SmartyHeaderCode%%\*\/\?>\n)/s", $compiled_tpl, $result);
+                        $saved_has_nocache_code = $compiler->template->has_nocache_code;
+                        $saved_nocache_hash = $compiler->template->properties['nocache_hash'];
+                        $_smarty_tpl = $compiler->template;
+                        eval($result[2][0]);
+                        $compiler->template->properties['nocache_hash'] = $saved_nocache_hash;
+                        $compiler->template->has_nocache_code = $saved_has_nocache_code; 
+                        // remove header code
+                        $compiled_tpl = preg_replace("/(<\?php \/\*%%SmartyHeaderCode:{$tpl->properties['nocache_hash']}%%\*\/(.+?)\/\*\/%%SmartyHeaderCode%%\*\/\?>\n)/s", '', $compiled_tpl);
+                        if ($tpl->has_nocache_code) {
+                            // replace nocache_hash
+                            $compiled_tpl = preg_replace("/{$tpl->properties['nocache_hash']}/", $compiler->template->properties['nocache_hash'], $compiled_tpl);
+                            $compiler->template->has_nocache_code = true;
+                        } 
+                        $has_compiled_template = true;
+                    } 
+                } 
+            } 
+        } 
+
+        if (isset($_attr['assign'])) {
+            // output will be stored in a smarty variable instead of beind displayed
+            $_assign = $_attr['assign'];
+        } 
+
+        $_parent_scope = Smarty::SCOPE_LOCAL;
+        if (isset($_attr['scope'])) {
+            $_attr['scope'] = trim($_attr['scope'], "'\"");
+            if ($_attr['scope'] == 'parent') {
+                $_parent_scope = Smarty::SCOPE_PARENT;
+            } elseif ($_attr['scope'] == 'root') {
+                $_parent_scope = Smarty::SCOPE_ROOT;
+            } elseif ($_attr['scope'] == 'global') {
+                $_parent_scope = Smarty::SCOPE_GLOBAL;
+            } 
+        } 
+        $_caching = 'null';
+        if ($this->compiler->nocache || $this->compiler->tag_nocache) {
+            $_caching = Smarty::CACHING_OFF;
+        } 
+        // default for included templates
+        if ($this->compiler->template->caching && !$this->compiler->nocache && !$this->compiler->tag_nocache) {
+            $_caching = self::CACHING_NOCACHE_CODE;
+        } 
+        /*
+        * if the {include} tag provides individual parameter for caching
+        * it will not be included into the common cache file and treated like
+        * a nocache section
+        */
+        if (isset($_attr['cache_lifetime'])) {
+            $_cache_lifetime = $_attr['cache_lifetime'];
+            $this->compiler->tag_nocache = true;
+            $_caching = Smarty::CACHING_LIFETIME_CURRENT;
+        } else {
+            $_cache_lifetime = 'null';
+        } 
+        if (isset($_attr['cache_id'])) {
+            $_cache_id = $_attr['cache_id'];
+            $this->compiler->tag_nocache = true;
+            $_caching = Smarty::CACHING_LIFETIME_CURRENT;
+        } else {
+            $_cache_id = '$_smarty_tpl->cache_id';
+        } 
+        if (isset($_attr['compile_id'])) {
+            $_compile_id = $_attr['compile_id'];
+        } else {
+            $_compile_id = '$_smarty_tpl->compile_id';
+        } 
+        if ($_attr['caching'] === true) {
+            $_caching = Smarty::CACHING_LIFETIME_CURRENT;
+        } 
+        if ($_attr['nocache'] === true) {
+            $this->compiler->tag_nocache = true;
+            $_caching = Smarty::CACHING_OFF;
+        } 
+        // create template object
+        $_output = "<?php \$_template = new {$compiler->smarty->template_class}($include_file, \$_smarty_tpl->smarty, \$_smarty_tpl, $_cache_id, $_compile_id, $_caching, $_cache_lifetime);\n"; 
+        // delete {include} standard attributes
+        unset($_attr['file'], $_attr['assign'], $_attr['cache_id'], $_attr['compile_id'], $_attr['cache_lifetime'], $_attr['nocache'], $_attr['caching'], $_attr['scope'], $_attr['inline']); 
+        // remaining attributes must be assigned as smarty variable
+        if (!empty($_attr)) {
+            if ($_parent_scope == Smarty::SCOPE_LOCAL) {
+                // create variables
+                foreach ($_attr as $_key => $_value) {
+                    $_output .= "\$_template->assign('$_key',$_value);";
+                } 
+            } else {
+                $this->compiler->trigger_template_error('variable passing not allowed in parent/global scope', $this->compiler->lex->taglineno);
+            } 
+        } 
+        // was there an assign attribute
+        if (isset($_assign)) {
+            $_output .= "\$_smarty_tpl->assign($_assign,\$_template->getRenderedTemplate());?>";
+        } else {
+            if ($has_compiled_template && !($compiler->template->caching && ($this->compiler->tag_nocache || $this->compiler->nocache))) {
+                $_output .= "\$_template->properties['nocache_hash']  = '{$compiler->template->properties['nocache_hash']}';\n";
+                $_output .= "\$_tpl_stack[] = \$_smarty_tpl; \$_smarty_tpl = \$_template;?>\n";
+                $_output .= $compiled_tpl;
+                $_output .= "<?php \$_smarty_tpl->updateParentVariables($_parent_scope);?>\n";
+                $_output .= "<?php /*  End of included template \"" . $tpl->getTemplateFilepath() . "\" */ ?>\n";
+                $_output .= "<?php \$_smarty_tpl = array_pop(\$_tpl_stack);?>";
+            } else {
+                $_output .= " echo \$_template->getRenderedTemplate();?>";
+                if ($_parent_scope != Smarty::SCOPE_LOCAL) {
+                       $_output .= "<?php \$_template->updateParentVariables($_parent_scope);?>";
+               }
+            } 
+        } 
+        $_output .= "<?php unset(\$_template);?>";
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_include_php.php
new file mode 100644 (file)
index 0000000..19ee186
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Include PHP
+ * 
+ * Compiles the {include_php} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Insert Class
+ */
+class Smarty_Internal_Compile_Include_Php extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('file');
+       public $shorttag_order = array('file');
+    public $optional_attributes = array('once', 'assign'); 
+
+    /**
+     * Compiles code for the {include_php} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+       if (!$compiler->smarty->allow_php_tag) {
+               throw new SmartyException("{include_php} is deprecated, set allow_php_tag = true to enable");
+       } 
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        $_output = '<?php '; 
+
+        $_smarty_tpl = $compiler->template; 
+        $_filepath = false;
+        eval('$_file = ' . $_attr['file'] . ';'); 
+        if (!isset($this->compiler->smarty->security_policy) && file_exists($_file)) {
+               $_filepath = $_file;
+        } else {
+            if (isset($this->compiler->smarty->security_policy)) {
+                $_dir = $this->compiler->smarty->security_policy->trusted_dir;
+            } else {
+                $_dir = $this->compiler->smarty->trusted_dir;
+            } 
+            if (!empty($_dir)) {
+                foreach((array)$_dir as $_script_dir) {
+                    if (strpos('/\\', substr($_script_dir, -1)) === false) {
+                        $_script_dir .= DS;
+                    } 
+                    if (file_exists($_script_dir . $_file)) {
+                        $_filepath = $_script_dir .  $_file;
+                        break;
+                    } 
+                } 
+            } 
+        } 
+        if ($_filepath == false) {
+            $this->compiler->trigger_template_error("{include_php} file '{$_file}' is not readable", $this->compiler->lex->taglineno);
+        } 
+
+        if (isset($this->compiler->smarty->security_policy)) {
+            $this->compiler->smarty->security_policy->isTrustedPHPDir($_filepath);
+        } 
+
+        if (isset($_attr['assign'])) {
+            // output will be stored in a smarty variable instead of being displayed
+            $_assign = $_attr['assign'];
+        } 
+        $_once = '_once';
+        if (isset($_attr['once'])) {
+            if ($_attr['once'] == 'false') {
+                $_once = '';
+            } 
+        } 
+
+        if (isset($_assign)) {
+            return "<?php ob_start(); include{$_once} ('{$_filepath}'); \$_smarty_tpl->assign({$_assign},ob_get_contents()); ob_end_clean();?>";
+        } else {
+            return "<?php include{$_once} ('{$_filepath}');?>\n";
+        } 
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_insert.php
new file mode 100644 (file)
index 0000000..898e531
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Insert
+ * 
+ * Compiles the {insert} tag
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Insert Class
+ */
+class Smarty_Internal_Compile_Insert extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('name');
+       public $shorttag_order = array('name');
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the {insert} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // never compile as nocache code
+        $this->compiler->suppressNocacheProcessing = true;
+        $this->compiler->tag_nocache = true;
+        $_smarty_tpl = $compiler->template;
+        $_name = null;
+        $_script = null;
+
+        $_output = '<?php '; 
+        // save posible attributes
+        eval('$_name = ' . $_attr['name'] . ';');
+        if (isset($_attr['assign'])) {
+            // output will be stored in a smarty variable instead of being displayed
+            $_assign = $_attr['assign']; 
+            // create variable to make shure that the compiler knows about its nocache status
+            $this->compiler->template->tpl_vars[trim($_attr['assign'], "'")] = new Smarty_Variable(null, true);
+        } 
+        if (isset($_attr['script'])) {
+            // script which must be included
+            $_function = "smarty_insert_{$_name}";
+            $_smarty_tpl = $compiler->template;
+            $_filepath = false;
+            eval('$_script = ' . $_attr['script'] . ';');
+            if (!isset($this->compiler->smarty->security_policy) && file_exists($_script)) {
+                $_filepath = $_script;
+            } else {
+                if (isset($this->compiler->smarty->security_policy)) {
+                    $_dir = $this->compiler->smarty->security_policy->trusted_dir;
+                } else {
+                    $_dir = $this->compiler->smarty->trusted_dir;
+                } 
+                if (!empty($_dir)) {
+                    foreach((array)$_dir as $_script_dir) {
+                        if (strpos('/\\', substr($_script_dir, -1)) === false) {
+                            $_script_dir .= DS;
+                        } 
+                        if (file_exists($_script_dir . $_script)) {
+                            $_filepath = $_script_dir . $_script;
+                            break;
+                        } 
+                    } 
+                } 
+            } 
+            if ($_filepath == false) {
+                $this->compiler->trigger_template_error("{insert} missing script file '{$_script}'", $this->compiler->lex->taglineno);
+            } 
+            // code for script file loading
+            $_output .= "require_once '{$_filepath}' ;";
+            require_once $_filepath;
+            if (!is_callable($_function)) {
+                $this->compiler->trigger_template_error(" {insert} function '{$_function}' is not callable in script file '{$_script}'", $this->compiler->lex->taglineno);
+            } 
+        } else {
+            $_filepath = 'null';
+            $_function = "insert_{$_name}"; 
+            // function in PHP script ?
+            if (!is_callable($_function)) {
+                // try plugin
+                if (!$_function = $this->compiler->getPlugin($_name, 'insert')) {
+                    $this->compiler->trigger_template_error("{insert} no function or plugin found for '{$_name}'", $this->compiler->lex->taglineno);
+                } 
+            } 
+        } 
+        // delete {insert} standard attributes
+        unset($_attr['name'], $_attr['assign'], $_attr['script'], $_attr['nocache']); 
+        // convert attributes into parameter array string
+        $_paramsArray = array();
+        foreach ($_attr as $_key => $_value) {
+            $_paramsArray[] = "'$_key' => $_value";
+        } 
+        $_params = 'array(' . implode(", ", $_paramsArray) . ')'; 
+        // call insert
+        if (isset($_assign)) {
+            if ($_smarty_tpl->caching) {
+                $_output .= "echo Smarty_Internal_Nocache_Insert::compile ('{$_function}',{$_params}, \$_smarty_tpl, '{$_filepath}',{$_assign});?>";
+            } else {
+                $_output .= "\$_smarty_tpl->assign({$_assign} , {$_function} ({$_params},\$_smarty_tpl), true);?>";
+            } 
+        } else {
+            $this->compiler->has_output = true;
+            if ($_smarty_tpl->caching) {
+                $_output .= "echo Smarty_Internal_Nocache_Insert::compile ('{$_function}',{$_params}, \$_smarty_tpl, '{$_filepath}');?>";
+            } else {
+                $_output .= "echo {$_function}({$_params},\$_smarty_tpl);?>";
+            } 
+        } 
+        return $_output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_ldelim.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_ldelim.php
new file mode 100644 (file)
index 0000000..bdf86de
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Ldelim
+ *
+ * Compiles the {ldelim} tag 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Ldelim Class
+ */ 
+class Smarty_Internal_Compile_Ldelim extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {ldelim} tag
+     *
+     * This tag does output the left delimiter 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        $_attr = $this->_get_attributes($args);
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+        // this tag does not return compiled code
+        $this->compiler->has_code = true;
+        return $this->compiler->smarty->left_delimiter;
+    } 
+}
+
+?>
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_nocache.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_nocache.php
new file mode 100644 (file)
index 0000000..0c88b14
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Nocache
+ *
+ * Compiles the {nocache} {/nocache} tags 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Nocache Class
+ */ 
+class Smarty_Internal_Compile_Nocache extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {nocache} tag
+     *
+     * This tag does not generate compiled output. It only sets a compiler flag 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        $_attr = $this->_get_attributes($args);
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+        // enter nocache mode
+        $this->compiler->nocache = true;
+        // this tag does not return compiled code
+        $this->compiler->has_code = false;
+        return true;
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Nocacheclose Class
+ */ 
+class Smarty_Internal_Compile_Nocacheclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/nocache} tag
+     *
+     * This tag does not generate compiled output. It only sets a compiler flag 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        $_attr = $this->_get_attributes($args);
+        // leave nocache mode
+        $this->compiler->nocache = false;
+        // this tag does not return compiled code
+        $this->compiler->has_code = false;
+        return true;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_block_plugin.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_block_plugin.php
new file mode 100644 (file)
index 0000000..f61ff49
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Block Plugin
+ * 
+ * Compiles code for the execution of block plugin
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Block Plugin Class
+ */
+class Smarty_Internal_Compile_Private_Block_Plugin extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of block plugin
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of block plugin
+     * @param string $function PHP function name
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag, $function)
+    {
+        $this->compiler = $compiler;
+        if (strlen($tag) < 6 || substr($tag, -5) != 'close') {
+            // opening tag of block plugin
+               // check and get attributes
+               $_attr = $this->_get_attributes($args); 
+               if ($_attr['nocache'] === true) {
+               $this->compiler->tag_nocache = true;
+               }
+                       unset($_attr['nocache']);
+            // convert attributes into parameter array string
+            $_paramsArray = array();
+            foreach ($_attr as $_key => $_value) {
+                if (is_int($_key)) {
+                    $_paramsArray[] = "$_key=>$_value";
+                } else {
+                    $_paramsArray[] = "'$_key'=>$_value";
+                } 
+            } 
+            $_params = 'array(' . implode(",", $_paramsArray) . ')';
+
+            $this->_open_tag($tag, array($_params, $this->compiler->nocache)); 
+            // maybe nocache because of nocache variables or nocache plugin
+            $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache; 
+            // compile code
+            $output = "<?php \$_smarty_tpl->smarty->_tag_stack[] = array('{$tag}', {$_params}); \$_block_repeat=true; {$function}({$_params}, null, \$_smarty_tpl, \$_block_repeat);while (\$_block_repeat) { ob_start();?>";
+        } else {
+            // must endblock be nocache?
+            if ($this->compiler->nocache) {
+                $this->compiler->tag_nocache = true;
+            } 
+            // closing tag of block plugin, restore nocache
+            list($_params, $this->compiler->nocache) = $this->_close_tag(substr($tag, 0, -5)); 
+            // This tag does create output
+            $this->compiler->has_output = true; 
+            // compile code
+            if (!isset($parameter['modifier_list'])) {
+               $mod_pre = $mod_post ='';
+            } else {
+               $mod_pre = ' ob_start(); ';
+               $mod_post = 'echo '.$this->compiler->compileTag('private_modifier',array(),array('modifierlist'=>$parameter['modifier_list'],'value'=>'ob_get_clean()')).';';
+            }
+            $output = "<?php \$_block_content = ob_get_clean(); \$_block_repeat=false;".$mod_pre." echo {$function}({$_params}, \$_block_content, \$_smarty_tpl, \$_block_repeat); ".$mod_post." } array_pop(\$_smarty_tpl->smarty->_tag_stack);?>";
+        } 
+        return $output . "\n";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_function_plugin.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_function_plugin.php
new file mode 100644 (file)
index 0000000..965d696
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Function Plugin
+ * 
+ * Compiles code for the execution of function plugin
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Function Plugin Class
+ */
+class Smarty_Internal_Compile_Private_Function_Plugin extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array();
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of function plugin
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of function plugin
+     * @param string $function PHP function name
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag, $function)
+    {
+        $this->compiler = $compiler; 
+        // This tag does create output
+        $this->compiler->has_output = true;
+
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        if ($_attr['nocache'] === true) {
+            $this->compiler->tag_nocache = true;
+        }
+        unset($_attr['nocache']);
+        // convert attributes into parameter array string
+        $_paramsArray = array();
+        foreach ($_attr as $_key => $_value) {
+            if (is_int($_key)) {
+                $_paramsArray[] = "$_key=>$_value";
+            } else {
+                $_paramsArray[] = "'$_key'=>$_value";
+            } 
+        } 
+        $_params = 'array(' . implode(",", $_paramsArray) . ')'; 
+        // compile code
+        $output = "<?php echo {$function}({$_params},\$_smarty_tpl);?>\n";
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_modifier.php
new file mode 100644 (file)
index 0000000..49daf9e
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Modifier
+ * 
+ * Compiles code for modifier execution
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Modifier Class
+ */
+class Smarty_Internal_Compile_Private_Modifier extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for modifier execution
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        $this->smarty = $this->compiler->smarty;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        $output = $parameter['value']; 
+        // loop over list of modifiers
+        foreach ($parameter['modifierlist'] as $single_modifier) {
+            $modifier = $single_modifier[0];
+          $single_modifier[0] = $output;
+            $params = implode(',', $single_modifier); 
+            // check for registered modifier
+            if (isset($compiler->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$modifier])) {
+                $function = $compiler->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$modifier][0];
+                if (!is_array($function)) {
+                    $output = "{$function}({$params})";
+                } else {
+                    if (is_object($function[0])) {
+                        $output = '$_smarty_tpl->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][\'' . $modifier . '\'][0][0]->' . $function[1] . '(' . $params . ')';
+                    } else {
+                        $output = $function[0] . '::' . $function[1] . '(' . $params . ')';
+                    } 
+                } 
+                // check for plugin modifiercompiler
+            } else if ($compiler->smarty->loadPlugin('smarty_modifiercompiler_' . $modifier)) {
+                $plugin = 'smarty_modifiercompiler_' . $modifier;
+                $output = $plugin($single_modifier, $compiler); 
+                // check for plugin modifier
+            } else if ($function = $this->compiler->getPlugin($modifier, Smarty::PLUGIN_MODIFIER)) {
+                $output = "{$function}({$params})"; 
+                // check if trusted PHP function
+            } else if (is_callable($modifier)) {
+                // check if modifier allowed
+                if (!is_object($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedModifier($modifier, $this->compiler)) {
+                    $output = "{$modifier}({$params})";
+                } 
+            } else {
+                $this->compiler->trigger_template_error ("unknown modifier \"" . $modifier . "\"", $this->compiler->lex->taglineno);
+            } 
+        } 
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_block_function.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_block_function.php
new file mode 100644 (file)
index 0000000..97737e8
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Object Block Function
+ * 
+ * Compiles code for registered objects as block function
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Object Block Function Class
+ */
+class Smarty_Internal_Compile_Private_Object_Block_Function extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array();
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of block plugin
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of block object
+     * @param string $methode name of methode to call
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag, $methode)
+    {
+        $this->compiler = $compiler;
+        if (strlen($tag) < 5 || substr($tag, -5) != 'close') {
+            // opening tag of block plugin
+               // check and get attributes
+               $_attr = $this->_get_attributes($args); 
+               if ($_attr['nocache'] === true) {
+               $this->compiler->tag_nocache = true;
+               }
+                       unset($_attr['nocache']);
+            // convert attributes into parameter array string
+            $_paramsArray = array();
+            foreach ($_attr as $_key => $_value) {
+                if (is_int($_key)) {
+                    $_paramsArray[] = "$_key=>$_value";
+                } else {
+                    $_paramsArray[] = "'$_key'=>$_value";
+                } 
+            } 
+            $_params = 'array(' . implode(",", $_paramsArray) . ')';
+
+            $this->_open_tag($tag . '->' . $methode, array($_params, $this->compiler->nocache)); 
+            // maybe nocache because of nocache variables or nocache plugin
+            $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache; 
+            // compile code
+            $output = "<?php \$_smarty_tpl->smarty->_tag_stack[] = array('{$tag}->{$methode}', {$_params}); \$_block_repeat=true; \$_smarty_tpl->smarty->registered_objects['{$tag}'][0]->{$methode}({$_params}, null, \$_smarty_tpl, \$_block_repeat);while (\$_block_repeat) { ob_start();?>";
+        } else {
+            $base_tag = substr($tag, 0, -5); 
+            // must endblock be nocache?
+            if ($this->compiler->nocache) {
+                $this->compiler->tag_nocache = true;
+            } 
+            // closing tag of block plugin, restore nocache
+            list($_params, $this->compiler->nocache) = $this->_close_tag($base_tag . '->' . $methode); 
+            // This tag does create output
+            $this->compiler->has_output = true; 
+            // compile code
+            if (!isset($parameter['modifier_list'])) {
+               $mod_pre = $mod_post ='';
+            } else {
+               $mod_pre = ' ob_start(); ';
+               $mod_post = 'echo '.$this->compiler->compileTag('private_modifier',array(),array('modifierlist'=>$parameter['modifier_list'],'value'=>'ob_get_clean()')).';';
+            }
+            $output = "<?php \$_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;".$mod_pre." echo \$_smarty_tpl->smarty->registered_objects['{$base_tag}'][0]->{$methode}({$_params}, \$_block_content, \$_smarty_tpl, \$_block_repeat); ".$mod_post."  } array_pop(\$_smarty_tpl->smarty->_tag_stack);?>";
+        } 
+        return $output."\n";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_function.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_object_function.php
new file mode 100644 (file)
index 0000000..f0755f0
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Object Funtion
+ * 
+ * Compiles code for registered objects as function
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Object Function Class
+ */
+class Smarty_Internal_Compile_Private_Object_Function extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array();
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of function plugin
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of function
+     * @param string $methode name of methode to call
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag, $methode)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        if ($_attr['nocache'] === true) {
+            $this->compiler->tag_nocache = true;
+        }
+        unset($_attr['nocache']);
+        $_assign = null;
+        if (isset($_attr['assign'])) {
+            $_assign = $_attr['assign'];
+            unset($_attr['assign']);
+        } 
+        // convert attributes into parameter array string
+        if ($this->compiler->smarty->registered_objects[$tag][2]) {
+            $_paramsArray = array();
+            foreach ($_attr as $_key => $_value) {
+                if (is_int($_key)) {
+                    $_paramsArray[] = "$_key=>$_value";
+                } else {
+                    $_paramsArray[] = "'$_key'=>$_value";
+                } 
+            } 
+            $_params = 'array(' . implode(",", $_paramsArray) . ')';
+            $return = "\$_smarty_tpl->smarty->registered_objects['{$tag}'][0]->{$methode}({$_params},\$_smarty_tpl)";
+        } else {
+            $_params = implode(",", $_attr);
+            $return = "\$_smarty_tpl->smarty->registered_objects['{$tag}'][0]->{$methode}({$_params})";
+        } 
+        if (empty($_assign)) {
+            // This tag does create output
+            $this->compiler->has_output = true;
+            $output = "<?php echo {$return};?>\n";
+        } else {
+            $output = "<?php \$_smarty_tpl->assign({$_assign},{$return});?>\n";
+    }
+        return $output;
+    } 
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_print_expression.php
new file mode 100644 (file)
index 0000000..5a9c931
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Print Expression
+ * 
+ * Compiles any tag which will output an expression or variable
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Print Expression Class
+ */
+class Smarty_Internal_Compile_Private_Print_Expression extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('assign'); 
+    public $option_flags = array('nocache', 'nofilter'); 
+
+    /**
+     * Compiles code for gererting output from any expression
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        // nocache option
+        if ($_attr['nocache'] === true) {
+            $this->compiler->tag_nocache = true;
+        } 
+        // filter handling
+        if ($_attr['nofilter'] === true) {
+            $_filter = 'false';
+        } else {
+            $_filter = 'true';
+        } 
+        // compiled output
+        // compiled output
+        if (isset($_attr['assign'])) {
+            // assign output to variable
+            $output = "<?php \$_smarty_tpl->assign({$_attr['assign']},{$parameter['value']});?>";
+        } else {
+            // display value
+            if (!$_attr['nofilter'] && isset($this->compiler->smarty->registered_filters['variable'])) {
+                $output = "Smarty_Internal_Filter_Handler::runFilter('variable', {$parameter['value']}, \$_smarty_tpl, {$_filter})";
+            } else {
+                $output = $parameter['value'];
+            } 
+            if (!$_attr['nofilter'] && !empty($this->compiler->smarty->default_modifiers)) {
+                $modifierlist = array();
+                foreach ($this->compiler->smarty->default_modifiers as $key => $single_default_modifier) {
+                    preg_match_all('/(\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|:|[^:]+)/', $single_default_modifier, $mod_array);
+                    for ($i = 0, $count = count($mod_array[0]);$i < $count;$i++) {
+                        if ($mod_array[0][$i] != ':') {
+                            $modifierlist[$key][] = $mod_array[0][$i];
+                        } 
+                    } 
+                } 
+                $output = $this->compiler->compileTag('private_modifier', array(), array('modifierlist' => $modifierlist, 'value' => $output));
+            } 
+            if (!empty($parameter['modifierlist'])) {
+                $output = $this->compiler->compileTag('private_modifier', array(), array('modifierlist' => $parameter['modifierlist'], 'value' => $output));
+            } 
+            $this->compiler->has_output = true;
+            $output = "<?php echo {$output};?>";
+        } 
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_block.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_block.php
new file mode 100644 (file)
index 0000000..b8f239d
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Registered Block
+ * 
+ * Compiles code for the execution of a registered block function
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Registered Block Class
+ */
+class Smarty_Internal_Compile_Private_Registered_Block extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of a block function
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of block function
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag)
+    {
+        $this->compiler = $compiler;
+        if (strlen($tag) < 6 || substr($tag,-5) != 'close') {
+            // opening tag of block plugin
+               // check and get attributes
+               $_attr = $this->_get_attributes($args); 
+               if ($_attr['nocache']) {
+               $this->compiler->tag_nocache = true;
+               }
+                       unset($_attr['nocache']);
+            // convert attributes into parameter array string
+            $_paramsArray = array();
+            foreach ($_attr as $_key => $_value) {
+                if (is_int($_key)) {
+                    $_paramsArray[] = "$_key=>$_value";
+               } elseif ($this->compiler->template->caching && in_array($_key,$compiler->smarty->registered_plugins[Smarty::PLUGIN_BLOCK][$tag][2])) {
+                                       $_value = str_replace("'","^#^",$_value);
+                       $_paramsArray[] = "'$_key'=>^#^.var_export($_value,true).^#^";
+                } else {
+                    $_paramsArray[] = "'$_key'=>$_value";
+                } 
+            } 
+            $_params = 'array(' . implode(",", $_paramsArray) . ')';
+
+            $this->_open_tag($tag, array($_params, $this->compiler->nocache)); 
+            // maybe nocache because of nocache variables or nocache plugin
+            $this->compiler->nocache = !$compiler->smarty->registered_plugins[Smarty::PLUGIN_BLOCK][$tag][1] | $this->compiler->nocache | $this->compiler->tag_nocache;
+            $function = $compiler->smarty->registered_plugins[Smarty::PLUGIN_BLOCK][$tag][0]; 
+            // compile code
+            if (!is_array($function)) {
+                $output = "<?php \$_smarty_tpl->smarty->_tag_stack[] = array('{$tag}', {$_params}); \$_block_repeat=true; {$function}({$_params}, null, \$_smarty_tpl, \$_block_repeat);while (\$_block_repeat) { ob_start();?>";
+            } else if (is_object($function[0])) {
+                $output = "<?php \$_smarty_tpl->smarty->_tag_stack[] = array('{$tag}', {$_params}); \$_block_repeat=true; \$_smarty_tpl->smarty->registered_plugins['block']['{$tag}'][0][0]->{$function[1]}({$_params}, null, \$_smarty_tpl, \$_block_repeat);while (\$_block_repeat) { ob_start();?>";
+            } else {
+                $output = "<?php \$_smarty_tpl->smarty->_tag_stack[] = array('{$tag}', {$_params}); \$_block_repeat=true; {$function[0]}::{$function[1]}({$_params}, null, \$_smarty_tpl, \$_block_repeat);while (\$_block_repeat) { ob_start();?>";
+            } 
+        } else {
+            // must endblock be nocache?
+            if ($this->compiler->nocache) {
+                $this->compiler->tag_nocache = true;
+            } 
+            $base_tag = substr($tag, 0, -5); 
+            // closing tag of block plugin, restore nocache
+            list($_params, $this->compiler->nocache) = $this->_close_tag($base_tag); 
+            // This tag does create output
+            $this->compiler->has_output = true;
+            $function = $compiler->smarty->registered_plugins[Smarty::PLUGIN_BLOCK][$base_tag][0]; 
+            // compile code
+            if (!isset($parameter['modifier_list'])) {
+               $mod_pre = $mod_post ='';
+            } else {
+               $mod_pre = ' ob_start(); ';
+               $mod_post = 'echo '.$this->compiler->compileTag('private_modifier',array(),array('modifierlist'=>$parameter['modifier_list'],'value'=>'ob_get_clean()')).';';
+            }
+            if (!is_array($function)) {
+                $output = "<?php \$_block_content = ob_get_clean(); \$_block_repeat=false;".$mod_pre." echo {$function}({$_params}, \$_block_content, \$_smarty_tpl, \$_block_repeat);".$mod_post." } array_pop(\$_smarty_tpl->smarty->_tag_stack);?>";
+            } else if (is_object($function[0])) {
+                $output = "<?php \$_block_content = ob_get_clean(); \$_block_repeat=false;".$mod_pre." echo \$_smarty_tpl->smarty->registered_plugins['block']['{$base_tag}'][0][0]->{$function[1]}({$_params}, \$_block_content, \$_smarty_tpl, \$_block_repeat); ".$mod_post."} array_pop(\$_smarty_tpl->smarty->_tag_stack);?>";
+            } else {
+                $output = "<?php \$_block_content = ob_get_clean(); \$_block_repeat=false;".$mod_pre." echo {$function[0]}::{$function[1]}({$_params}, \$_block_content, \$_smarty_tpl, \$_block_repeat); ".$mod_post."} array_pop(\$_smarty_tpl->smarty->_tag_stack);?>";
+            } 
+        } 
+        return $output."\n";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_function.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_registered_function.php
new file mode 100644 (file)
index 0000000..96236fa
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Registered Function
+ * 
+ * Compiles code for the execution of a registered function
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Compile Registered Function Class
+ */
+class Smarty_Internal_Compile_Private_Registered_Function extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $optional_attributes = array('_any'); 
+
+    /**
+     * Compiles code for the execution of a registered function
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @param array $parameter array with compilation parameter
+     * @param string $tag name of function
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter, $tag)
+    {
+        $this->compiler = $compiler; 
+        // This tag does create output
+        $this->compiler->has_output = true;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args); 
+        if ($_attr['nocache']) {
+            $this->compiler->tag_nocache = true;
+        }
+        unset($_attr['nocache']);
+        // not cachable?
+        $this->compiler->tag_nocache =  $this->compiler->tag_nocache || !$compiler->smarty->registered_plugins[Smarty::PLUGIN_FUNCTION][$tag][1]; 
+        // convert attributes into parameter array string
+        $_paramsArray = array();
+        foreach ($_attr as $_key => $_value) {
+            if (is_int($_key)) {
+                $_paramsArray[] = "$_key=>$_value";
+            } elseif ($this->compiler->template->caching && in_array($_key,$compiler->smarty->registered_plugins[Smarty::PLUGIN_FUNCTION][$tag][2])) {
+                               $_value = str_replace("'","^#^",$_value);
+                $_paramsArray[] = "'$_key'=>^#^.var_export($_value,true).^#^";
+            } else {
+                $_paramsArray[] = "'$_key'=>$_value";
+            } 
+        } 
+        $_params = 'array(' . implode(",", $_paramsArray) . ')'; 
+        $function = $compiler->smarty->registered_plugins[Smarty::PLUGIN_FUNCTION][$tag][0]; 
+        // compile code
+        if (!is_array($function)) {
+            $output = "<?php echo {$function}({$_params},\$_smarty_tpl);?>\n";
+        } else if (is_object($function[0])) {
+            $output = "<?php echo \$_smarty_tpl->smarty->registered_plugins[Smarty::PLUGIN_FUNCTION]['{$tag}'][0][0]->{$function[1]}({$_params},\$_smarty_tpl);?>\n";
+        } else {
+            $output = "<?php echo {$function[0]}::{$function[1]}({$_params},\$_smarty_tpl);?>\n";
+        } 
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_private_special_variable.php
new file mode 100644 (file)
index 0000000..5d6ae80
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Special Smarty Variable
+ * 
+ * Compiles the special $smarty variables
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile special Smarty Variable Class
+ */
+class Smarty_Internal_Compile_Private_Special_Variable extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the speical $smarty variables
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler, $parameter)
+    {
+        $_index = preg_split("/\]\[/",substr($parameter, 1, strlen($parameter)-2));
+        $compiled_ref = ' ';
+        $variable = trim($_index[0], "'");
+        switch ($variable) {
+            case 'foreach':
+                return "\$_smarty_tpl->getVariable('smarty')->value$parameter";
+            case 'section':
+                return "\$_smarty_tpl->getVariable('smarty')->value$parameter";
+            case 'capture':
+                return "Smarty::\$_smarty_vars$parameter";
+            case 'now':
+                return 'time()';
+            case 'cookies':
+                if (isset($compiler->smarty->security_policy) && !$compiler->smarty->security_policy->allow_super_globals) {
+                    $compiler->trigger_template_error("(secure mode) super globals not permitted");
+                    break;
+                } 
+                $compiled_ref = '$_COOKIE';
+                break;
+
+            case 'get':
+            case 'post':
+            case 'env':
+            case 'server':
+            case 'session':
+            case 'request':
+                if (isset($compiler->smarty->security_policy) && !$compiler->smarty->security_policy->allow_super_globals) {
+                    $compiler->trigger_template_error("(secure mode) super globals not permitted");
+                    break;
+                } 
+                $compiled_ref = '$_'.strtoupper($variable);
+                break;
+
+            case 'template':
+                return 'basename($_smarty_tpl->getTemplateFilepath())';
+
+            case 'current_dir':
+                return 'dirname($_smarty_tpl->getTemplateFilepath())';
+
+            case 'version':
+                $_version = Smarty::SMARTY_VERSION;
+                return "'$_version'";
+
+            case 'const':
+                if (isset($compiler->smarty->security_policy) && !$compiler->smarty->security_policy->allow_constants) {
+                    $compiler->trigger_template_error("(secure mode) constants not permitted");
+                    break;
+                } 
+                return '@' . trim($_index[1], "'");
+
+            case 'config':
+                return "\$_smarty_tpl->getConfigVariable($_index[1])";
+            case 'ldelim':
+                $_ldelim = $compiler->smarty->left_delimiter;
+                return "'$_ldelim'";
+
+            case 'rdelim':
+                $_rdelim = $compiler->smarty->right_delimiter;
+                return "'$_rdelim'";
+
+            default:
+                $compiler->trigger_template_error('$smarty.' . trim($_index[0], "'") . ' is invalid');
+                break;
+        } 
+        if (isset($_index[1])) {
+            array_shift($_index);
+            foreach ($_index as $_ind) {
+                $compiled_ref = $compiled_ref . "[$_ind]";
+            } 
+        } 
+        return $compiled_ref;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_rdelim.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_rdelim.php
new file mode 100644 (file)
index 0000000..6436bfd
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Compile Rdelim
+ *
+ * Compiles the {rdelim} tag 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Rdelim Class
+ */ 
+class Smarty_Internal_Compile_Rdelim extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {rdelim} tag
+     *
+     * This tag does output the right delimiter 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        $_attr = $this->_get_attributes($args);
+        if ($_attr['nocache'] === true) {
+               $this->compiler->trigger_template_error('nocache option not allowed', $this->compiler->lex->taglineno);
+        }
+        // this tag does not return compiled code
+        $this->compiler->has_code = true;
+        return $this->compiler->smarty->right_delimiter;
+    } 
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_section.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_section.php
new file mode 100644 (file)
index 0000000..0768b02
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Section
+ * 
+ * Compiles the {section} {sectionelse} {/section} tags
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Section Class
+ */
+class Smarty_Internal_Compile_Section extends Smarty_Internal_CompileBase {
+       // attribute definitions
+    public $required_attributes = array('name', 'loop');
+       public $shorttag_order = array('name', 'loop');
+    public $optional_attributes = array('start', 'step', 'max', 'show'); 
+
+    /**
+     * Compiles code for the {section} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        $this->_open_tag('section', array('section',$this->compiler->nocache));
+        // maybe nocache because of nocache variables
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache;
+
+        $output = "<?php ";
+
+        $section_name = $_attr['name'];
+        
+        $output .= "unset(\$_smarty_tpl->tpl_vars['smarty']->value['section'][$section_name]);\n";
+        $section_props = "\$_smarty_tpl->tpl_vars['smarty']->value['section'][$section_name]";
+
+        foreach ($_attr as $attr_name => $attr_value) {
+            switch ($attr_name) {
+                case 'loop':
+                    $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
+                    break;
+
+                case 'show':
+                    if (is_bool($attr_value))
+                        $show_attr_value = $attr_value ? 'true' : 'false';
+                    else
+                        $show_attr_value = "(bool)$attr_value";
+                    $output .= "{$section_props}['show'] = $show_attr_value;\n";
+                    break;
+
+                case 'name':
+                    $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
+                    break;
+
+                case 'max':
+                case 'start':
+                    $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
+                    break;
+
+                case 'step':
+                    $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
+                    break;
+            } 
+        } 
+
+        if (!isset($_attr['show']))
+            $output .= "{$section_props}['show'] = true;\n";
+
+        if (!isset($_attr['loop']))
+            $output .= "{$section_props}['loop'] = 1;\n";
+
+        if (!isset($_attr['max']))
+            $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
+        else
+            $output .= "if ({$section_props}['max'] < 0)\n" . "    {$section_props}['max'] = {$section_props}['loop'];\n";
+
+        if (!isset($_attr['step']))
+            $output .= "{$section_props}['step'] = 1;\n";
+
+        if (!isset($_attr['start']))
+            $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
+        else {
+            $output .= "if ({$section_props}['start'] < 0)\n" . "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" . "else\n" . "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
+        } 
+
+        $output .= "if ({$section_props}['show']) {\n";
+        if (!isset($_attr['start']) && !isset($_attr['step']) && !isset($_attr['max'])) {
+            $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
+        } else {
+            $output .= "    {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
+        } 
+        $output .= "    if ({$section_props}['total'] == 0)\n" . "        {$section_props}['show'] = false;\n" . "} else\n" . "    {$section_props}['total'] = 0;\n";
+
+        $output .= "if ({$section_props}['show']):\n";
+        $output .= "
+            for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
+                 {$section_props}['iteration'] <= {$section_props}['total'];
+                 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
+        $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
+        $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
+        $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
+        $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
+        $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
+
+        $output .= "?>";
+        return $output;
+    } 
+} 
+
+/**
+* Smarty Internal Plugin Compile Sectionelse Class
+*/
+class Smarty_Internal_Compile_Sectionelse extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {sectionelse} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        list($_open_tag, $nocache) = $this->_close_tag(array('section'));
+        $this->_open_tag('sectionelse',array('sectionelse', $nocache));
+
+        return "<?php endfor; else: ?>";
+    } 
+} 
+
+/**
+ * Smarty Internal Plugin Compile Sectionclose Class
+ */
+class Smarty_Internal_Compile_Sectionclose extends Smarty_Internal_CompileBase {
+    /**
+     * Compiles code for the {/section} tag
+     * 
+     * @param array $args array with attributes from parser
+     * @param object $compiler compiler object
+     * @return string compiled code
+     */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+
+        // must endblock be nocache?
+        if ($this->compiler->nocache) {
+                 $this->compiler->tag_nocache = true;
+        }
+
+        list($_open_tag, $this->compiler->nocache) = $this->_close_tag(array('section', 'sectionelse'));
+
+        if ($_open_tag == 'sectionelse')
+            return "<?php endif; ?>";
+        else
+            return "<?php endfor; endif; ?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_while.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compile_while.php
new file mode 100644 (file)
index 0000000..7e87a22
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/**
+* Smarty Internal Plugin Compile While
+* 
+* Compiles the {while} tag
+* 
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews 
+*/
+
+/**
+* Smarty Internal Plugin Compile While Class
+*/
+class Smarty_Internal_Compile_While extends Smarty_Internal_CompileBase {
+    /**
+    * Compiles code for the {while} tag
+    * 
+    * @param array $args array with attributes from parser
+    * @param object $compiler compiler object
+    * @param array $parameter array with compilation parameter
+    * @return string compiled code
+    */
+    public function compile($args, $compiler, $parameter)
+    {
+        $this->compiler = $compiler;
+        // check and get attributes
+        $_attr = $this->_get_attributes($args);
+        $this->_open_tag('while', $this->compiler->nocache);
+
+        // maybe nocache because of nocache variables
+        $this->compiler->nocache = $this->compiler->nocache | $this->compiler->tag_nocache;
+        if (is_array($parameter['if condition'])) {
+               if ($this->compiler->nocache) {
+                       $_nocache = ',true';
+               // create nocache var to make it know for further compiling
+               if (is_array($parameter['if condition']['var'])) {
+                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var']['var'], "'")] = new Smarty_variable(null, true);
+               } else {
+                       $this->compiler->template->tpl_vars[trim($parameter['if condition']['var'], "'")] = new Smarty_variable(null, true);
+               }
+               } else {
+                       $_nocache = '';
+               }
+            if (is_array($parameter['if condition']['var'])) {
+               $_output = "<?php if (!isset(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]) || !is_array(\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value)) \$_smarty_tpl->createLocalArrayVariable(".$parameter['if condition']['var']['var']."$_nocache);\n";
+                   $_output .= "while (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']['var']."]->value".$parameter['if condition']['var']['smarty_internal_index']." = ".$parameter['if condition']['value']."){?>";
+            } else {
+                   $_output = "<?php \$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."] = new Smarty_Variable(\$_smarty_tpl->getVariable(".$parameter['if condition']['var'].",null,true,false)->value{$_nocache});";            
+                   $_output .= "while (\$_smarty_tpl->tpl_vars[".$parameter['if condition']['var']."]->value = ".$parameter['if condition']['value']."){?>";
+               }
+            return $_output;
+        } else {
+            return "<?php while ({$parameter['if condition']}){?>";
+        } 
+    } 
+} 
+
+/**
+* Smarty Internal Plugin Compile Whileclose Class
+*/
+class Smarty_Internal_Compile_Whileclose extends Smarty_Internal_CompileBase {
+    /**
+    * Compiles code for the {/while} tag
+    * 
+    * @param array $args array with attributes from parser
+    * @param object $compiler compiler object
+    * @return string compiled code
+    */
+    public function compile($args, $compiler)
+    {
+        $this->compiler = $compiler; 
+        // must endblock be nocache?
+        if ($this->compiler->nocache) {
+                 $this->compiler->tag_nocache = true;
+        }
+        $this->compiler->nocache = $this->_close_tag(array('while'));
+        return "<?php }?>";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_compilebase.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_compilebase.php
new file mode 100644 (file)
index 0000000..6418acc
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * Smarty Internal Plugin CompileBase
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * This class does extend all internal compile plugins
+ */
+// abstract class Smarty_Internal_CompileBase implements TagCompilerInterface
+class Smarty_Internal_CompileBase {
+       public $required_attributes = array();
+    public $optional_attributes = array();
+    public $shorttag_order = array();
+    public $option_flags = array('nocache');
+
+
+    /**
+     * This function checks if the attributes passed are valid
+     * 
+     * The attributes passed for the tag to compile are checked against the list of required and 
+     * optional attributes. Required attributes must be present. Optional attributes are check against
+     * against the corresponding list. The keyword '_any' specifies that any attribute will be accepted 
+     * as valid
+     * 
+     * @param array $attributes attributes applied to the tag
+     * @return array of mapped attributes for further processing
+     */
+    function _get_attributes ($attributes)
+    {
+        $_indexed_attr = array(); 
+        // loop over attributes
+        foreach ($attributes as $key => $mixed) {
+            // shorthand ?
+            if (!is_array($mixed)) {
+                // option flag ?
+                if (in_array(trim($mixed, '\'"'), $this->option_flags)) {
+                    $_indexed_attr[trim($mixed, '\'"')] = true; 
+                    // shorthand attribute ?
+                } else if (isset($this->shorttag_order[$key])) {
+                    $_indexed_attr[$this->shorttag_order[$key]] = $mixed;
+                } else {
+                    // too many shorthands
+                    $this->compiler->trigger_template_error('too many shorthand attributes', $this->compiler->lex->taglineno);
+                } 
+                // named attribute
+            } else {
+                $kv = each($mixed); 
+                // option flag?
+                if (in_array($kv['key'], $this->option_flags)) {
+                    if (is_bool($kv['value'])) {
+                        $_indexed_attr[$kv['key']] = $kv['value'];
+                    } else if (is_string($kv['value']) && in_array(trim($kv['value'], '\'"'), array('true', 'false'))) {
+                        if (trim($kv['value']) == 'true') {
+                            $_indexed_attr[$kv['key']] = true;
+                        } else {
+                            $_indexed_attr[$kv['key']] = false;
+                        } 
+                    } else if (is_numeric($kv['value']) && in_array($kv['value'], array(0, 1))) {
+                        if ($kv['value'] == 1) {
+                            $_indexed_attr[$kv['key']] = true;
+                        } else {
+                            $_indexed_attr[$kv['key']] = false;
+                        } 
+                    } else {
+                        $this->compiler->trigger_template_error("illegal value of option flag \"{$kv['key']}\"", $this->compiler->lex->taglineno);
+                    } 
+                    // must be named attribute
+                } else {
+                       reset($mixed);
+                    $_indexed_attr[key($mixed)] = $mixed[key($mixed)];
+                } 
+            } 
+        } 
+        // check if all required attributes present
+        foreach ($this->required_attributes as $attr) {
+            if (!array_key_exists($attr, $_indexed_attr)) {
+                $this->compiler->trigger_template_error("missing \"" . $attr . "\" attribute", $this->compiler->lex->taglineno);
+            } 
+        } 
+        // check for unallowed attributes
+        if ($this->optional_attributes != array('_any')) {
+            $tmp_array = array_merge($this->required_attributes, $this->optional_attributes, $this->option_flags);
+            foreach ($_indexed_attr as $key => $dummy) {
+                if (!in_array($key, $tmp_array) && $key !== 0) {
+                    $this->compiler->trigger_template_error("unexpected \"" . $key . "\" attribute", $this->compiler->lex->taglineno);
+                } 
+            } 
+        } 
+        // default 'false' for all option flags not set
+        foreach ($this->option_flags as $flag) {
+            if (!isset($_indexed_attr[$flag])) {
+                $_indexed_attr[$flag] = false;
+            } 
+        } 
+
+        return $_indexed_attr;
+    } 
+
+    /**
+     * Push opening tag name on stack
+     * 
+     * Optionally additional data can be saved on stack
+     * 
+     * @param string $open_tag the opening tag's name
+     * @param anytype $data optional data which shall be saved on stack
+     */
+    function _open_tag($open_tag, $data = null)
+    {
+        array_push($this->compiler->_tag_stack, array($open_tag, $data));
+    } 
+
+    /**
+     * Pop closing tag
+     * 
+     * Raise an error if this stack-top doesn't match with expected opening tags
+     * 
+     * @param array $ |string $expected_tag the expected opening tag names
+     * @return anytype the opening tag's name or saved data
+     */
+    function _close_tag($expected_tag)
+    {
+        if (count($this->compiler->_tag_stack) > 0) {
+            // get stacked info
+            list($_open_tag, $_data) = array_pop($this->compiler->_tag_stack); 
+            // open tag must match with the expected ones
+            if (in_array($_open_tag, (array)$expected_tag)) {
+                if (is_null($_data)) {
+                    // return opening tag
+                    return $_open_tag;
+                } else {
+                    // return restored data
+                    return $_data;
+                } 
+            } 
+            // wrong nesting of tags
+            $this->compiler->trigger_template_error("unclosed {" . $_open_tag . "} tag");
+            return;
+        } 
+        // wrong nesting of tags
+        $this->compiler->trigger_template_error("unexpected closing tag", $this->compiler->lex->taglineno);
+        return;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_config.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_config.php
new file mode 100644 (file)
index 0000000..06ae70b
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+/**
+ * Smarty Internal Plugin Config
+ * 
+ * Main class for config variables
+ * 
+ * @ignore 
+ * @package Smarty
+ * @subpackage Config
+ * @author Uwe Tews 
+ */
+class Smarty_Internal_Config {
+    static $config_objects = array();
+
+    public function __construct($config_resource, $smarty, $data = null)
+    {
+        $this->data = $data;
+        $this->smarty = $smarty;
+        $this->config_resource = $config_resource;
+        $this->config_resource_type = null;
+        $this->config_resource_name = null;
+        $this->config_filepath = null;
+        $this->config_timestamp = null;
+        $this->config_source = null;
+        $this->compiled_config = null;
+        $this->compiled_filepath = null;
+        $this->compiled_timestamp = null;
+        $this->mustCompile = null;
+        $this->compiler_object = null; 
+        // parse config resource name
+        if (!$this->parseConfigResourceName ($config_resource)) {
+            throw new SmartyException ("Unable to parse config resource '{$config_resource}'");
+        } 
+    } 
+
+    public function getConfigFilepath ()
+    {
+        return $this->config_filepath === null ?
+        $this->config_filepath = $this->buildConfigFilepath() :
+        $this->config_filepath;
+    } 
+
+    public function getTimestamp ()
+    {
+        return $this->config_timestamp === null ?
+        $this->config_timestamp = filemtime($this->getConfigFilepath()) :
+        $this->config_timestamp;
+    } 
+
+    private function parseConfigResourceName($config_resource)
+    {
+        if (empty($config_resource))
+            return false;
+        if (strpos($config_resource, ':') === false) {
+            // no resource given, use default
+            $this->config_resource_type = $this->smarty->default_config_type;
+            $this->config_resource_name = $config_resource;
+        } else {
+            // get type and name from path
+            list($this->config_resource_type, $this->config_resource_name) = explode(':', $config_resource, 2);
+            if (strlen($this->config_resource_type) == 1) {
+                // 1 char is not resource type, but part of filepath
+                $this->config_resource_type = $this->smarty->default_config_type;
+                $this->config_resource_name = $config_resource;
+            } else {
+                $this->config_resource_type = strtolower($this->config_resource_type);
+            } 
+        } 
+        return true;
+    } 
+
+    /*
+     * get system filepath to config
+     */
+    public function buildConfigFilepath ()
+    {
+        foreach((array)$this->smarty->config_dir as $_config_dir) {
+            if (strpos('/\\', substr($_config_dir, -1)) === false) {
+                $_config_dir .= DS;
+            } 
+
+            $_filepath = $_config_dir . $this->config_resource_name;
+            if (file_exists($_filepath))
+                return $_filepath;
+        } 
+        // check for absolute path
+        if (file_exists($this->config_resource_name))
+            return $this->config_resource_name; 
+        // no tpl file found
+        throw new SmartyException("Unable to load config file \"{$this->config_resource_name}\"");
+        return false;
+    } 
+    /**
+     * Read config file source
+     * 
+     * @return string content of source file
+     */
+    /**
+     * Returns the template source code
+     * 
+     * The template source is being read by the actual resource handler
+     * 
+     * @return string the template source
+     */
+    public function getConfigSource ()
+    {
+        if ($this->config_source === null) {
+            if ($this->readConfigSource($this) === false) {
+                throw new SmartyException("Unable to load config file \"{$this->config_resource_name}\"");
+            } 
+        } 
+        return $this->config_source;
+    } 
+    public function readConfigSource()
+    { 
+        // read source file
+        if (file_exists($this->getConfigFilepath())) {
+            $this->config_source = file_get_contents($this->getConfigFilepath());
+            return true;
+        } else {
+            return false;
+        } 
+    } 
+
+    /**
+     * Returns the compiled  filepath
+     * 
+     * @return string the compiled filepath
+     */
+    public function getCompiledFilepath ()
+    {
+        return $this->compiled_filepath === null ?
+        ($this->compiled_filepath = $this->buildCompiledFilepath()) :
+        $this->compiled_filepath;
+    } 
+    public function buildCompiledFilepath()
+    {
+        $_compile_id = isset($this->smarty->compile_id) ? preg_replace('![^\w\|]+!', '_', $this->smarty->compile_id) : null;
+        $_flag = (int)$this->smarty->config_read_hidden + (int)$this->smarty->config_booleanize * 2 +
+        (int)$this->smarty->config_overwrite * 4;
+        $_filepath = sha1($this->config_resource_name . $_flag); 
+        // if use_sub_dirs, break file into directories
+        if ($this->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $this->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_compile_id)) {
+            $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
+        } 
+        $_compile_dir = $this->smarty->compile_dir;
+        if (substr($_compile_dir, -1) != DS) {
+            $_compile_dir .= DS;
+        } 
+        return $_compile_dir . $_filepath . '.' . basename($this->config_resource_name) . '.config' . '.php';
+    } 
+    /**
+     * Returns the timpestamp of the compiled file
+     * 
+     * @return integer the file timestamp
+     */
+    public function getCompiledTimestamp ()
+    {
+        return $this->compiled_timestamp === null ?
+        ($this->compiled_timestamp = (file_exists($this->getCompiledFilepath())) ? filemtime($this->getCompiledFilepath()) : false) :
+        $this->compiled_timestamp;
+    } 
+    /**
+     * Returns if the current config file must be compiled 
+     * 
+     * It does compare the timestamps of config source and the compiled config and checks the force compile configuration
+     * 
+     * @return boolean true if the file must be compiled
+     */
+    public function mustCompile ()
+    {
+        return $this->mustCompile === null ?
+        $this->mustCompile = ($this->smarty->force_compile || $this->getCompiledTimestamp () === false || $this->smarty->compile_check && $this->getCompiledTimestamp () < $this->getTimestamp ()):
+        $this->mustCompile;
+    } 
+    /**
+     * Returns the compiled config file 
+     * 
+     * It checks if the config file must be compiled or just read the compiled version
+     * 
+     * @return string the compiled config file
+     */
+    public function getCompiledConfig ()
+    {
+        if ($this->compiled_config === null) {
+            // see if template needs compiling.
+            if ($this->mustCompile()) {
+                $this->compileConfigSource();
+            } else {
+                $this->compiled_config = file_get_contents($this->getCompiledFilepath());
+            } 
+        } 
+        return $this->compiled_config;
+    } 
+
+    /**
+     * Compiles the config files
+     */
+    public function compileConfigSource ()
+    { 
+        // compile template
+        if (!is_object($this->compiler_object)) {
+            // load compiler
+            $this->compiler_object = new Smarty_Internal_Config_File_Compiler($this->smarty);
+        } 
+        // compile locking
+        if ($this->smarty->compile_locking) {
+            if ($saved_timestamp = $this->getCompiledTimestamp()) {
+                touch($this->getCompiledFilepath());
+            } 
+        } 
+        // call compiler
+        try {
+            $this->compiler_object->compileSource($this);
+        } 
+        catch (Exception $e) {
+            // restore old timestamp in case of error
+            if ($this->smarty->compile_locking && $saved_timestamp) {
+                touch($this->getCompiledFilepath(), $saved_timestamp);
+            } 
+            throw $e;
+        } 
+        // compiling succeded
+        // write compiled template
+        Smarty_Internal_Write_File::writeFile($this->getCompiledFilepath(), $this->getCompiledConfig(), $this->smarty);
+    } 
+
+    /*
+     * load config variables
+    *
+    * @param mixed $sections array of section names, single section or null
+    * @param object $scope global,parent or local
+    */
+    public function loadConfigVars ($sections = null, $scope = 'local')
+    {
+        if ($this->data instanceof Smarty_Internal_Template) {
+            $this->data->properties['file_dependency'][sha1($this->getConfigFilepath())] = array($this->getConfigFilepath(), $this->getTimestamp(),'file');
+        } 
+        if ($this->mustCompile()) {
+            $this->compileConfigSource();
+        }
+        // pointer to scope
+        if ($scope == 'local') {
+               $scope_ptr = $this->data;
+        } elseif ($scope == 'parent') {
+               if (isset($this->data->parent)) {
+                       $scope_ptr = $this->data->parent;
+               } else {
+                       $scope_ptr = $this->data;
+               }                       
+        } elseif ($scope == 'root' || $scope == 'global') {
+               $scope_ptr = $this->data;
+               while (isset($scope_ptr->parent)) {
+                       $scope_ptr = $scope_ptr->parent;
+               } 
+        }
+        $_config_vars = array();
+        include($this->getCompiledFilepath ());
+        // copy global config vars
+        foreach ($_config_vars['vars'] as $variable => $value) {
+            if ($this->smarty->config_overwrite || !isset($scope_ptr->config_vars[$variable])) {
+                $scope_ptr->config_vars[$variable] = $value;
+            } else {
+                $scope_ptr->config_vars[$variable] = array_merge((array)$scope_ptr->config_vars[$variable], (array)$value);
+            } 
+        } 
+        // scan sections
+        foreach ($_config_vars['sections'] as $this_section => $dummy) {
+            if ($sections == null || in_array($this_section, (array)$sections)) {
+                foreach ($_config_vars['sections'][$this_section]['vars'] as $variable => $value) {
+                    if ($this->smarty->config_overwrite || !isset($scope_ptr->config_vars[$variable])) {
+                        $scope_ptr->config_vars[$variable] = $value;
+                    } else {
+                        $scope_ptr->config_vars[$variable] = array_merge((array)$scope_ptr->config_vars[$variable], (array)$value);
+                    } 
+                } 
+            } 
+        }
+    } 
+} 
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_config_file_compiler.php
new file mode 100644 (file)
index 0000000..e9dfbdb
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Config File Compiler
+ * 
+ * This is the config file compiler class. It calls the lexer and parser to
+ * perform the compiling.
+ * 
+ * @package Smarty
+ * @subpackage Config
+ * @author Uwe Tews 
+ */
+/**
+ * Main config file compiler class
+ */
+class Smarty_Internal_Config_File_Compiler {
+    /**
+     * Initialize compiler
+     */
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty; 
+        // get required plugins
+        $this->smarty->loadPlugin('Smarty_Internal_Configfilelexer');
+               $this->smarty->loadPlugin('Smarty_Internal_Configfileparser');
+        $this->config_data['sections'] = array();
+        $this->config_data['vars'] = array();
+    } 
+
+    /**
+     * Methode to compile a Smarty template
+     * 
+     * @param  $template template object to compile
+     * @return bool true if compiling succeeded, false if it failed
+     */
+    public function compileSource($config)
+    {
+        /* here is where the compiling takes place. Smarty
+       tags in the templates are replaces with PHP code,
+       then written to compiled files. */
+        $this->config = $config; 
+        // get config file source
+        $_content = $config->getConfigSource() . "\n"; 
+        // on empty template just return
+        if ($_content == '') {
+            return true;
+        } 
+        // init the lexer/parser to compile the config file
+        $lex = new Smarty_Internal_Configfilelexer($_content, $this->smarty);
+        $parser = new Smarty_Internal_Configfileparser($lex, $this);
+        if (isset($this->smarty->_parserdebug)) $parser->PrintTrace(); 
+        // get tokens from lexer and parse them
+        while ($lex->yylex()) {
+            if (isset($this->smarty->_parserdebug)) echo "<br>Parsing  {$parser->yyTokenName[$lex->token]} Token {$lex->value} Line {$lex->line} \n";
+            $parser->doParse($lex->token, $lex->value);
+        } 
+        // finish parsing process
+        $parser->doParse(0, 0);
+        $config->compiled_config = '<?php $_config_vars = ' . var_export($this->config_data, true) . '; ?>';
+    } 
+    /**
+     * display compiler error messages without dying
+     * 
+     * If parameter $args is empty it is a parser detected syntax error.
+     * In this case the parser is called to obtain information about exspected tokens.
+     * 
+     * If parameter $args contains a string this is used as error message
+     * 
+     * @todo output exact position of parse error in source line
+     * @param  $args string individual error message or null
+     */
+    public function trigger_config_file_error($args = null)
+    {
+        $this->lex = Smarty_Internal_Configfilelexer::instance();
+        $this->parser = Smarty_Internal_Configfileparser::instance(); 
+        // get template source line which has error
+        $line = $this->lex->line;
+        if (isset($args)) {
+            // $line--;
+        } 
+        $match = preg_split("/\n/", $this->lex->data);
+        $error_text = "Syntax error in config file '{$this->config->getConfigFilepath()}' on line {$line} '{$match[$line-1]}' ";
+        if (isset($args)) {
+            // individual error message
+            $error_text .= $args;
+        } else {
+            // exspected token from parser
+            foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
+                $exp_token = $this->parser->yyTokenName[$token];
+                if (isset($this->lex->smarty_token_names[$exp_token])) {
+                    // token type from lexer
+                    $expect[] = '"' . $this->lex->smarty_token_names[$exp_token] . '"';
+                } else {
+                    // otherwise internal token name
+                    $expect[] = $this->parser->yyTokenName[$token];
+                } 
+            } 
+            // output parser error message
+            $error_text .= ' - Unexpected "' . $this->lex->value . '", expected one of: ' . implode(' , ', $expect);
+        } 
+        throw new SmartyCompilerException($error_text);
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_configfilelexer.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_configfilelexer.php
new file mode 100644 (file)
index 0000000..9186fe2
--- /dev/null
@@ -0,0 +1,526 @@
+<?php
+/**
+* Smarty Internal Plugin Configfilelexer
+*
+* This is the lexer to break the config file source into tokens 
+* @package Smarty
+* @subpackage Config
+* @author Uwe Tews 
+*/
+/**
+* Smarty Internal Plugin Configfilelexer
+*/
+class Smarty_Internal_Configfilelexer
+{
+
+    public $data;
+    public $counter;
+    public $token;
+    public $value;
+    public $node;
+    public $line;
+    private $state = 1;
+    public $smarty_token_names = array (               // Text for parser error messages
+                               );
+                               
+                               
+    function __construct($data, $smarty)
+    {
+        // set instance object
+        self::instance($this); 
+        $this->data = $data . "\n"; //now all lines are \n-terminated
+        $this->counter = 0;
+        $this->line = 1;
+        $this->smarty = $smarty; 
+     }
+    public static function &instance($new_instance = null)
+    {
+        static $instance = null;
+        if (isset($new_instance) && is_object($new_instance))
+            $instance = $new_instance;
+        return $instance;
+    } 
+
+
+
+    private $_yy_state = 1;
+    private $_yy_stack = array();
+
+    function yylex()
+    {
+        return $this->{'yylex' . $this->_yy_state}();
+    }
+
+    function yypushstate($state)
+    {
+        array_push($this->_yy_stack, $this->_yy_state);
+        $this->_yy_state = $state;
+    }
+
+    function yypopstate()
+    {
+        $this->_yy_state = array_pop($this->_yy_stack);
+    }
+
+    function yybegin($state)
+    {
+        $this->_yy_state = $state;
+    }
+
+
+
+
+    function yylex1()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 0,
+              4 => 0,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(#)|^(\\[)|^(\\])|^(=)|^([ \t\r]+)|^(\n)|^([0-9]*[a-zA-Z_]\\w*)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state START');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r1_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const START = 1;
+    function yy_r1_1($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_COMMENTSTART;
+    $this->yypushstate(self::COMMENT);
+    }
+    function yy_r1_2($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_OPENB;
+    $this->yypushstate(self::SECTION);
+    }
+    function yy_r1_3($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_CLOSEB;
+    }
+    function yy_r1_4($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_EQUAL;
+    $this->yypushstate(self::VALUE);
+    }
+    function yy_r1_5($yy_subpatterns)
+    {
+
+    return false;
+    }
+    function yy_r1_6($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NEWLINE;
+    }
+    function yy_r1_7($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_ID;
+    }
+
+
+
+    function yylex2()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 0,
+              4 => 0,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+              8 => 0,
+              9 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^([ \t\r]+)|^(\\d+\\.\\d+(?=[ \t\r]*[\n#]))|^(\\d+(?=[ \t\r]*[\n#]))|^('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'(?=[ \t\r]*[\n#]))|^(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"(?=[ \t\r]*[\n#]))|^(\"\"\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"\"\"(?=[ \t\r]*[\n#]))|^([a-zA-Z]+(?=[ \t\r]*[\n#]))|^([^\n]+?(?=[ \t\r]*\n))|^(\n)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state VALUE');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r2_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const VALUE = 2;
+    function yy_r2_1($yy_subpatterns)
+    {
+
+    return false;
+    }
+    function yy_r2_2($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_FLOAT;
+    $this->yypopstate();
+    }
+    function yy_r2_3($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_INT;
+    $this->yypopstate();
+    }
+    function yy_r2_4($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_SINGLE_QUOTED_STRING;
+    $this->yypopstate();
+    }
+    function yy_r2_5($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_DOUBLE_QUOTED_STRING;
+    $this->yypopstate();
+    }
+    function yy_r2_6($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_TRIPPLE_DOUBLE_QUOTED_STRING;
+    $this->yypopstate();
+    }
+    function yy_r2_7($yy_subpatterns)
+    {
+
+    if (!$this->smarty->config_booleanize || !in_array(strtolower($this->value), Array("true", "false", "on", "off", "yes", "no")) ) {
+        $this->yypopstate();
+        $this->yypushstate(self::NAKED_STRING_VALUE);
+        return true; //reprocess in new state
+    } else {
+        $this->token = Smarty_Internal_Configfileparser::TPC_BOOL;
+        $this->yypopstate();
+    }
+    }
+    function yy_r2_8($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NAKED_STRING;
+    $this->yypopstate();
+    }
+    function yy_r2_9($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NAKED_STRING;
+    $this->value = "";
+    $this->yypopstate();
+    }
+
+
+
+    function yylex3()
+    {
+        $tokenMap = array (
+              1 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^([^\n]+?(?=[ \t\r]*\n))/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state NAKED_STRING_VALUE');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r3_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const NAKED_STRING_VALUE = 3;
+    function yy_r3_1($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NAKED_STRING;
+    $this->yypopstate();
+    }
+
+
+
+    function yylex4()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^([ \t\r]+)|^([^\n]+?(?=[ \t\r]*\n))|^(\n)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state COMMENT');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r4_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const COMMENT = 4;
+    function yy_r4_1($yy_subpatterns)
+    {
+
+    return false;
+    }
+    function yy_r4_2($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NAKED_STRING;
+    }
+    function yy_r4_3($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_NEWLINE;
+    $this->yypopstate();
+    }
+
+
+
+    function yylex5()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(\\.)|^(.*?(?=[\.=[\]\r\n]))/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state SECTION');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r5_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const SECTION = 5;
+    function yy_r5_1($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_DOT;
+    }
+    function yy_r5_2($yy_subpatterns)
+    {
+
+    $this->token = Smarty_Internal_Configfileparser::TPC_SECTION;
+    $this->yypopstate();
+    }
+
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_configfileparser.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_configfileparser.php
new file mode 100644 (file)
index 0000000..0b5a573
--- /dev/null
@@ -0,0 +1,870 @@
+<?php
+/**
+* Smarty Internal Plugin Configfileparser
+*
+* This is the config file parser.
+* It is generated from the internal.configfileparser.y file
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+
+class TPC_yyToken implements ArrayAccess
+{
+    public $string = '';
+    public $metadata = array();
+
+    function __construct($s, $m = array())
+    {
+        if ($s instanceof TPC_yyToken) {
+            $this->string = $s->string;
+            $this->metadata = $s->metadata;
+        } else {
+            $this->string = (string) $s;
+            if ($m instanceof TPC_yyToken) {
+                $this->metadata = $m->metadata;
+            } elseif (is_array($m)) {
+                $this->metadata = $m;
+            }
+        }
+    }
+
+    function __toString()
+    {
+        return $this->_string;
+    }
+
+    function offsetExists($offset)
+    {
+        return isset($this->metadata[$offset]);
+    }
+
+    function offsetGet($offset)
+    {
+        return $this->metadata[$offset];
+    }
+
+    function offsetSet($offset, $value)
+    {
+        if ($offset === null) {
+            if (isset($value[0])) {
+                $x = ($value instanceof TPC_yyToken) ?
+                    $value->metadata : $value;
+                $this->metadata = array_merge($this->metadata, $x);
+                return;
+            }
+            $offset = count($this->metadata);
+        }
+        if ($value === null) {
+            return;
+        }
+        if ($value instanceof TPC_yyToken) {
+            if ($value->metadata) {
+                $this->metadata[$offset] = $value->metadata;
+            }
+        } elseif ($value) {
+            $this->metadata[$offset] = $value;
+        }
+    }
+
+    function offsetUnset($offset)
+    {
+        unset($this->metadata[$offset]);
+    }
+}
+
+class TPC_yyStackEntry
+{
+    public $stateno;       /* The state-number */
+    public $major;         /* The major token value.  This is the code
+                     ** number for the token at this stack level */
+    public $minor; /* The user-supplied minor token value.  This
+                     ** is the value of the token  */
+};
+
+
+#line 12 "smarty_internal_configfileparser.y"
+class Smarty_Internal_Configfileparser#line 79 "smarty_internal_configfileparser.php"
+{
+#line 14 "smarty_internal_configfileparser.y"
+
+    // states whether the parse was successful or not
+    public $successful = true;
+    public $retvalue = 0;
+    private $lex;
+    private $internalError = false;
+
+    function __construct($lex, $compiler) {
+        // set instance object
+        self::instance($this); 
+        $this->lex = $lex;
+        $this->smarty = $compiler->smarty; 
+        $this->compiler = $compiler;
+    }
+    public static function &instance($new_instance = null)
+    {
+        static $instance = null;
+        if (isset($new_instance) && is_object($new_instance))
+            $instance = $new_instance;
+        return $instance;
+    }
+
+    private function parse_bool($str) {
+        if (in_array(strtolower($str) ,array('on','yes','true'))) {
+            $res = true;
+        } else {
+            $res = false;
+        }
+        return $res;
+    }
+
+    private static $escapes_single = Array('\\' => '\\',
+                                          '\'' => '\'');
+    private static function parse_single_quoted_string($qstr) {
+        $escaped_string = substr($qstr, 1, strlen($qstr)-2); //remove outer quotes
+
+        $ss = preg_split('/(\\\\.)/', $escaped_string, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+        $str = "";
+        foreach ($ss as $s) {
+            if (strlen($s) === 2 && $s[0] === '\\') {
+                if (isset(self::$escapes_single[$s[1]])) {
+                    $s = self::$escapes_single[$s[1]];
+                }
+             }
+
+             $str .= $s;
+        }
+
+        return $str;
+    }
+
+    private static function parse_double_quoted_string($qstr) {
+        $inner_str = substr($qstr, 1, strlen($qstr)-2);
+        return stripcslashes($inner_str);
+    }
+
+    private static function parse_tripple_double_quoted_string($qstr) {
+        $inner_str = substr($qstr, 3, strlen($qstr)-6);
+        return stripcslashes($inner_str);
+    }
+
+    private function set_var(Array $var, Array &$target_array) {
+        $key = $var["key"];
+        $value = $var["value"];
+
+        if ($this->smarty->config_overwrite || !isset($target_array['vars'][$key])) {
+            $target_array['vars'][$key] = $value;
+        } else {
+            settype($target_array['vars'][$key], 'array');
+            $target_array['vars'][$key][] = $value;
+        }
+    }
+
+    private function add_global_vars(Array $vars) {
+        if (!isset($this->compiler->config_data['vars'])) {
+           $this->compiler->config_data['vars'] = Array();
+        }
+        foreach ($vars as $var) {
+            $this->set_var($var, $this->compiler->config_data);
+        }
+    }
+
+    private function add_section_vars($section_name, Array $vars) {
+        if (!isset($this->compiler->config_data['sections'][$section_name]['vars'])) {
+            $this->compiler->config_data['sections'][$section_name]['vars'] = Array();
+        }
+        foreach ($vars as $var) {
+            $this->set_var($var, $this->compiler->config_data['sections'][$section_name]);
+        }
+    }
+#line 174 "smarty_internal_configfileparser.php"
+
+    const TPC_OPENB                          =  1;
+    const TPC_SECTION                        =  2;
+    const TPC_CLOSEB                         =  3;
+    const TPC_DOT                            =  4;
+    const TPC_ID                             =  5;
+    const TPC_EQUAL                          =  6;
+    const TPC_FLOAT                          =  7;
+    const TPC_INT                            =  8;
+    const TPC_BOOL                           =  9;
+    const TPC_SINGLE_QUOTED_STRING           = 10;
+    const TPC_DOUBLE_QUOTED_STRING           = 11;
+    const TPC_TRIPPLE_DOUBLE_QUOTED_STRING   = 12;
+    const TPC_NAKED_STRING                   = 13;
+    const TPC_NEWLINE                        = 14;
+    const TPC_COMMENTSTART                   = 15;
+    const YY_NO_ACTION = 54;
+    const YY_ACCEPT_ACTION = 53;
+    const YY_ERROR_ACTION = 52;
+
+    const YY_SZ_ACTTAB = 35;
+static public $yy_action = array(
+ /*     0 */    26,   27,   21,   30,   29,   28,   31,   16,   53,    8,
+ /*    10 */    19,    2,   20,   11,   24,   23,   20,   11,   17,   15,
+ /*    20 */     3,   14,   13,   18,    4,    6,    5,    1,   12,   22,
+ /*    30 */     9,   47,   10,   25,    7,
+    );
+    static public $yy_lookahead = array(
+ /*     0 */     7,    8,    9,   10,   11,   12,   13,    5,   17,   18,
+ /*    10 */    14,   20,   14,   15,   22,   23,   14,   15,    2,    2,
+ /*    20 */    20,    4,   13,   14,    6,    3,    3,   20,    1,   24,
+ /*    30 */    22,   25,   22,   21,   19,
+);
+    const YY_SHIFT_USE_DFLT = -8;
+    const YY_SHIFT_MAX = 17;
+    static public $yy_shift_ofst = array(
+ /*     0 */    -8,    2,    2,    2,   -7,   -2,   -2,   27,   -8,   -8,
+ /*    10 */    -8,    9,   17,   -4,   16,   23,   18,   22,
+);
+    const YY_REDUCE_USE_DFLT = -10;
+    const YY_REDUCE_MAX = 10;
+    static public $yy_reduce_ofst = array(
+ /*     0 */    -9,   -8,   -8,   -8,    5,   10,    8,   12,   15,    0,
+ /*    10 */     7,
+);
+    static public $yyExpectedTokens = array(
+        /* 0 */ array(),
+        /* 1 */ array(5, 14, 15, ),
+        /* 2 */ array(5, 14, 15, ),
+        /* 3 */ array(5, 14, 15, ),
+        /* 4 */ array(7, 8, 9, 10, 11, 12, 13, ),
+        /* 5 */ array(14, 15, ),
+        /* 6 */ array(14, 15, ),
+        /* 7 */ array(1, ),
+        /* 8 */ array(),
+        /* 9 */ array(),
+        /* 10 */ array(),
+        /* 11 */ array(13, 14, ),
+        /* 12 */ array(2, 4, ),
+        /* 13 */ array(14, ),
+        /* 14 */ array(2, ),
+        /* 15 */ array(3, ),
+        /* 16 */ array(6, ),
+        /* 17 */ array(3, ),
+        /* 18 */ array(),
+        /* 19 */ array(),
+        /* 20 */ array(),
+        /* 21 */ array(),
+        /* 22 */ array(),
+        /* 23 */ array(),
+        /* 24 */ array(),
+        /* 25 */ array(),
+        /* 26 */ array(),
+        /* 27 */ array(),
+        /* 28 */ array(),
+        /* 29 */ array(),
+        /* 30 */ array(),
+        /* 31 */ array(),
+);
+    static public $yy_default = array(
+ /*     0 */    40,   36,   33,   37,   52,   52,   52,   32,   35,   40,
+ /*    10 */    40,   52,   52,   52,   52,   52,   52,   52,   50,   51,
+ /*    20 */    49,   44,   41,   39,   38,   34,   42,   43,   47,   46,
+ /*    30 */    45,   48,
+);
+    const YYNOCODE = 26;
+    const YYSTACKDEPTH = 100;
+    const YYNSTATE = 32;
+    const YYNRULE = 20;
+    const YYERRORSYMBOL = 16;
+    const YYERRSYMDT = 'yy0';
+    const YYFALLBACK = 0;
+    static public $yyFallback = array(
+    );
+    static function Trace($TraceFILE, $zTracePrompt)
+    {
+        if (!$TraceFILE) {
+            $zTracePrompt = 0;
+        } elseif (!$zTracePrompt) {
+            $TraceFILE = 0;
+        }
+        self::$yyTraceFILE = $TraceFILE;
+        self::$yyTracePrompt = $zTracePrompt;
+    }
+
+    static function PrintTrace()
+    {
+        self::$yyTraceFILE = fopen('php://output', 'w');
+        self::$yyTracePrompt = '<br>';
+    }
+
+    static public $yyTraceFILE;
+    static public $yyTracePrompt;
+    public $yyidx;                    /* Index of top element in stack */
+    public $yyerrcnt;                 /* Shifts left before out of the error */
+    public $yystack = array();  /* The parser's stack */
+
+    public $yyTokenName = array( 
+  '$',             'OPENB',         'SECTION',       'CLOSEB',      
+  'DOT',           'ID',            'EQUAL',         'FLOAT',       
+  'INT',           'BOOL',          'SINGLE_QUOTED_STRING',  'DOUBLE_QUOTED_STRING',
+  'TRIPPLE_DOUBLE_QUOTED_STRING',  'NAKED_STRING',  'NEWLINE',       'COMMENTSTART',
+  'error',         'start',         'global_vars',   'sections',    
+  'var_list',      'section',       'newline',       'var',         
+  'value',       
+    );
+
+    static public $yyRuleName = array(
+ /*   0 */ "start ::= global_vars sections",
+ /*   1 */ "global_vars ::= var_list",
+ /*   2 */ "sections ::= sections section",
+ /*   3 */ "sections ::=",
+ /*   4 */ "section ::= OPENB SECTION CLOSEB newline var_list",
+ /*   5 */ "section ::= OPENB DOT SECTION CLOSEB newline var_list",
+ /*   6 */ "var_list ::= var_list newline",
+ /*   7 */ "var_list ::= var_list var",
+ /*   8 */ "var_list ::=",
+ /*   9 */ "var ::= ID EQUAL value",
+ /*  10 */ "value ::= FLOAT",
+ /*  11 */ "value ::= INT",
+ /*  12 */ "value ::= BOOL",
+ /*  13 */ "value ::= SINGLE_QUOTED_STRING",
+ /*  14 */ "value ::= DOUBLE_QUOTED_STRING",
+ /*  15 */ "value ::= TRIPPLE_DOUBLE_QUOTED_STRING",
+ /*  16 */ "value ::= NAKED_STRING",
+ /*  17 */ "newline ::= NEWLINE",
+ /*  18 */ "newline ::= COMMENTSTART NEWLINE",
+ /*  19 */ "newline ::= COMMENTSTART NAKED_STRING NEWLINE",
+    );
+
+    function tokenName($tokenType)
+    {
+        if ($tokenType === 0) {
+            return 'End of Input';
+        }
+        if ($tokenType > 0 && $tokenType < count($this->yyTokenName)) {
+            return $this->yyTokenName[$tokenType];
+        } else {
+            return "Unknown";
+        }
+    }
+
+    static function yy_destructor($yymajor, $yypminor)
+    {
+        switch ($yymajor) {
+            default:  break;   /* If no destructor action specified: do nothing */
+        }
+    }
+
+    function yy_pop_parser_stack()
+    {
+        if (!count($this->yystack)) {
+            return;
+        }
+        $yytos = array_pop($this->yystack);
+        if (self::$yyTraceFILE && $this->yyidx >= 0) {
+            fwrite(self::$yyTraceFILE,
+                self::$yyTracePrompt . 'Popping ' . $this->yyTokenName[$yytos->major] .
+                    "\n");
+        }
+        $yymajor = $yytos->major;
+        self::yy_destructor($yymajor, $yytos->minor);
+        $this->yyidx--;
+        return $yymajor;
+    }
+
+    function __destruct()
+    {
+        while ($this->yystack !== Array()) {
+            $this->yy_pop_parser_stack();
+        }
+        if (is_resource(self::$yyTraceFILE)) {
+            fclose(self::$yyTraceFILE);
+        }
+    }
+
+    function yy_get_expected_tokens($token)
+    {
+        $state = $this->yystack[$this->yyidx]->stateno;
+        $expected = self::$yyExpectedTokens[$state];
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return $expected;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return array_unique($expected);
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate])) {
+                       $expected = array_merge($expected, self::$yyExpectedTokens[$nextstate]);
+                            if (in_array($token,
+                                  self::$yyExpectedTokens[$nextstate], true)) {
+                            $this->yyidx = $yyidx;
+                            $this->yystack = $stack;
+                            return array_unique($expected);
+                        }
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new TPC_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return array_unique($expected);
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return $expected;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+       $this->yyidx = $yyidx;
+       $this->yystack = $stack;
+        return array_unique($expected);
+    }
+
+    function yy_is_expected_token($token)
+    {
+        if ($token === 0) {
+            return true; // 0 is not part of this
+        }
+        $state = $this->yystack[$this->yyidx]->stateno;
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return true;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return true;
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate]) &&
+                          in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        return true;
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new TPC_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        if (!$token) {
+                            // end of input: this is valid
+                            return true;
+                        }
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return false;
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return true;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+        $this->yyidx = $yyidx;
+        $this->yystack = $stack;
+        return true;
+    }
+
+   function yy_find_shift_action($iLookAhead)
+    {
+        $stateno = $this->yystack[$this->yyidx]->stateno;
+     
+        /* if ($this->yyidx < 0) return self::YY_NO_ACTION;  */
+        if (!isset(self::$yy_shift_ofst[$stateno])) {
+            // no shift actions
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_shift_ofst[$stateno];
+        if ($i === self::YY_SHIFT_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
+                   && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
+                if (self::$yyTraceFILE) {
+                    fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
+                        $this->yyTokenName[$iLookAhead] . " => " .
+                        $this->yyTokenName[$iFallback] . "\n");
+                }
+                return $this->yy_find_shift_action($iFallback);
+            }
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    function yy_find_reduce_action($stateno, $iLookAhead)
+    {
+        /* $stateno = $this->yystack[$this->yyidx]->stateno; */
+
+        if (!isset(self::$yy_reduce_ofst[$stateno])) {
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_reduce_ofst[$stateno];
+        if ($i == self::YY_REDUCE_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    function yy_shift($yyNewState, $yyMajor, $yypMinor)
+    {
+        $this->yyidx++;
+        if ($this->yyidx >= self::YYSTACKDEPTH) {
+            $this->yyidx--;
+            if (self::$yyTraceFILE) {
+                fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
+            }
+            while ($this->yyidx >= 0) {
+                $this->yy_pop_parser_stack();
+            }
+#line 126 "smarty_internal_configfileparser.y"
+
+    $this->internalError = true;
+    $this->compiler->trigger_config_file_error("Stack overflow in configfile parser");
+#line 585 "smarty_internal_configfileparser.php"
+            return;
+        }
+        $yytos = new TPC_yyStackEntry;
+        $yytos->stateno = $yyNewState;
+        $yytos->major = $yyMajor;
+        $yytos->minor = $yypMinor;
+        array_push($this->yystack, $yytos);
+        if (self::$yyTraceFILE && $this->yyidx > 0) {
+            fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
+                $yyNewState);
+            fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
+            for($i = 1; $i <= $this->yyidx; $i++) {
+                fprintf(self::$yyTraceFILE, " %s",
+                    $this->yyTokenName[$this->yystack[$i]->major]);
+            }
+            fwrite(self::$yyTraceFILE,"\n");
+        }
+    }
+
+    static public $yyRuleInfo = array(
+  array( 'lhs' => 17, 'rhs' => 2 ),
+  array( 'lhs' => 18, 'rhs' => 1 ),
+  array( 'lhs' => 19, 'rhs' => 2 ),
+  array( 'lhs' => 19, 'rhs' => 0 ),
+  array( 'lhs' => 21, 'rhs' => 5 ),
+  array( 'lhs' => 21, 'rhs' => 6 ),
+  array( 'lhs' => 20, 'rhs' => 2 ),
+  array( 'lhs' => 20, 'rhs' => 2 ),
+  array( 'lhs' => 20, 'rhs' => 0 ),
+  array( 'lhs' => 23, 'rhs' => 3 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 24, 'rhs' => 1 ),
+  array( 'lhs' => 22, 'rhs' => 1 ),
+  array( 'lhs' => 22, 'rhs' => 2 ),
+  array( 'lhs' => 22, 'rhs' => 3 ),
+    );
+
+    static public $yyReduceMap = array(
+        0 => 0,
+        2 => 0,
+        3 => 0,
+        17 => 0,
+        18 => 0,
+        19 => 0,
+        1 => 1,
+        4 => 4,
+        5 => 5,
+        6 => 6,
+        7 => 7,
+        8 => 8,
+        9 => 9,
+        10 => 10,
+        11 => 11,
+        12 => 12,
+        13 => 13,
+        14 => 14,
+        15 => 15,
+        16 => 16,
+    );
+#line 132 "smarty_internal_configfileparser.y"
+    function yy_r0(){ $this->_retvalue = null;     }
+#line 652 "smarty_internal_configfileparser.php"
+#line 135 "smarty_internal_configfileparser.y"
+    function yy_r1(){ $this->add_global_vars($this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = null;     }
+#line 655 "smarty_internal_configfileparser.php"
+#line 141 "smarty_internal_configfileparser.y"
+    function yy_r4(){ $this->add_section_vars($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = null;     }
+#line 658 "smarty_internal_configfileparser.php"
+#line 142 "smarty_internal_configfileparser.y"
+    function yy_r5(){ if ($this->smarty->config_read_hidden) { $this->add_section_vars($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + 0]->minor); } $this->_retvalue = null;     }
+#line 661 "smarty_internal_configfileparser.php"
+#line 145 "smarty_internal_configfileparser.y"
+    function yy_r6(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;     }
+#line 664 "smarty_internal_configfileparser.php"
+#line 146 "smarty_internal_configfileparser.y"
+    function yy_r7(){ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -1]->minor, Array($this->yystack[$this->yyidx + 0]->minor));     }
+#line 667 "smarty_internal_configfileparser.php"
+#line 147 "smarty_internal_configfileparser.y"
+    function yy_r8(){ $this->_retvalue = Array();     }
+#line 670 "smarty_internal_configfileparser.php"
+#line 151 "smarty_internal_configfileparser.y"
+    function yy_r9(){ $this->_retvalue = Array("key" => $this->yystack[$this->yyidx + -2]->minor, "value" => $this->yystack[$this->yyidx + 0]->minor);     }
+#line 673 "smarty_internal_configfileparser.php"
+#line 153 "smarty_internal_configfileparser.y"
+    function yy_r10(){ $this->_retvalue = (float) $this->yystack[$this->yyidx + 0]->minor;     }
+#line 676 "smarty_internal_configfileparser.php"
+#line 154 "smarty_internal_configfileparser.y"
+    function yy_r11(){ $this->_retvalue = (int) $this->yystack[$this->yyidx + 0]->minor;     }
+#line 679 "smarty_internal_configfileparser.php"
+#line 155 "smarty_internal_configfileparser.y"
+    function yy_r12(){ $this->_retvalue = $this->parse_bool($this->yystack[$this->yyidx + 0]->minor);     }
+#line 682 "smarty_internal_configfileparser.php"
+#line 156 "smarty_internal_configfileparser.y"
+    function yy_r13(){ $this->_retvalue = self::parse_single_quoted_string($this->yystack[$this->yyidx + 0]->minor);     }
+#line 685 "smarty_internal_configfileparser.php"
+#line 157 "smarty_internal_configfileparser.y"
+    function yy_r14(){ $this->_retvalue = self::parse_double_quoted_string($this->yystack[$this->yyidx + 0]->minor);     }
+#line 688 "smarty_internal_configfileparser.php"
+#line 158 "smarty_internal_configfileparser.y"
+    function yy_r15(){ $this->_retvalue = self::parse_tripple_double_quoted_string($this->yystack[$this->yyidx + 0]->minor);     }
+#line 691 "smarty_internal_configfileparser.php"
+#line 159 "smarty_internal_configfileparser.y"
+    function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;     }
+#line 694 "smarty_internal_configfileparser.php"
+
+    private $_retvalue;
+
+    function yy_reduce($yyruleno)
+    {
+        $yymsp = $this->yystack[$this->yyidx];
+        if (self::$yyTraceFILE && $yyruleno >= 0 
+              && $yyruleno < count(self::$yyRuleName)) {
+            fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
+                self::$yyTracePrompt, $yyruleno,
+                self::$yyRuleName[$yyruleno]);
+        }
+
+        $this->_retvalue = $yy_lefthand_side = null;
+        if (array_key_exists($yyruleno, self::$yyReduceMap)) {
+            // call the action
+            $this->_retvalue = null;
+            $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
+            $yy_lefthand_side = $this->_retvalue;
+        }
+        $yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
+        $yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
+        $this->yyidx -= $yysize;
+        for($i = $yysize; $i; $i--) {
+            // pop all of the right-hand side parameters
+            array_pop($this->yystack);
+        }
+        $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
+        if ($yyact < self::YYNSTATE) {
+            if (!self::$yyTraceFILE && $yysize) {
+                $this->yyidx++;
+                $x = new TPC_yyStackEntry;
+                $x->stateno = $yyact;
+                $x->major = $yygoto;
+                $x->minor = $yy_lefthand_side;
+                $this->yystack[$this->yyidx] = $x;
+            } else {
+                $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
+            }
+        } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
+            $this->yy_accept();
+        }
+    }
+
+    function yy_parse_failed()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $this->yy_pop_parser_stack();
+        }
+    }
+
+    function yy_syntax_error($yymajor, $TOKEN)
+    {
+#line 119 "smarty_internal_configfileparser.y"
+
+    $this->internalError = true;
+    $this->yymajor = $yymajor;
+    $this->compiler->trigger_config_file_error();
+#line 757 "smarty_internal_configfileparser.php"
+    }
+
+    function yy_accept()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $stack = $this->yy_pop_parser_stack();
+        }
+#line 111 "smarty_internal_configfileparser.y"
+
+    $this->successful = !$this->internalError;
+    $this->internalError = false;
+    $this->retvalue = $this->_retvalue;
+    //echo $this->retvalue."\n\n";
+#line 775 "smarty_internal_configfileparser.php"
+    }
+
+    function doParse($yymajor, $yytokenvalue)
+    {
+        $yyerrorhit = 0;   /* True if yymajor has invoked an error */
+        
+        if ($this->yyidx === null || $this->yyidx < 0) {
+            $this->yyidx = 0;
+            $this->yyerrcnt = -1;
+            $x = new TPC_yyStackEntry;
+            $x->stateno = 0;
+            $x->major = 0;
+            $this->yystack = array();
+            array_push($this->yystack, $x);
+        }
+        $yyendofinput = ($yymajor==0);
+        
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sInput %s\n",
+                self::$yyTracePrompt, $this->yyTokenName[$yymajor]);
+        }
+        
+        do {
+            $yyact = $this->yy_find_shift_action($yymajor);
+            if ($yymajor < self::YYERRORSYMBOL &&
+                  !$this->yy_is_expected_token($yymajor)) {
+                // force a syntax error
+                $yyact = self::YY_ERROR_ACTION;
+            }
+            if ($yyact < self::YYNSTATE) {
+                $this->yy_shift($yyact, $yymajor, $yytokenvalue);
+                $this->yyerrcnt--;
+                if ($yyendofinput && $this->yyidx >= 0) {
+                    $yymajor = 0;
+                } else {
+                    $yymajor = self::YYNOCODE;
+                }
+            } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
+                $this->yy_reduce($yyact - self::YYNSTATE);
+            } elseif ($yyact == self::YY_ERROR_ACTION) {
+                if (self::$yyTraceFILE) {
+                    fprintf(self::$yyTraceFILE, "%sSyntax Error!\n",
+                        self::$yyTracePrompt);
+                }
+                if (self::YYERRORSYMBOL) {
+                    if ($this->yyerrcnt < 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $yymx = $this->yystack[$this->yyidx]->major;
+                    if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){
+                        if (self::$yyTraceFILE) {
+                            fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n",
+                                self::$yyTracePrompt, $this->yyTokenName[$yymajor]);
+                        }
+                        $this->yy_destructor($yymajor, $yytokenvalue);
+                        $yymajor = self::YYNOCODE;
+                    } else {
+                        while ($this->yyidx >= 0 &&
+                                 $yymx != self::YYERRORSYMBOL &&
+        ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
+                              ){
+                            $this->yy_pop_parser_stack();
+                        }
+                        if ($this->yyidx < 0 || $yymajor==0) {
+                            $this->yy_destructor($yymajor, $yytokenvalue);
+                            $this->yy_parse_failed();
+                            $yymajor = self::YYNOCODE;
+                        } elseif ($yymx != self::YYERRORSYMBOL) {
+                            $u2 = 0;
+                            $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
+                        }
+                    }
+                    $this->yyerrcnt = 3;
+                    $yyerrorhit = 1;
+                } else {
+                    if ($this->yyerrcnt <= 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $this->yyerrcnt = 3;
+                    $this->yy_destructor($yymajor, $yytokenvalue);
+                    if ($yyendofinput) {
+                        $this->yy_parse_failed();
+                    }
+                    $yymajor = self::YYNOCODE;
+                }
+            } else {
+                $this->yy_accept();
+                $yymajor = self::YYNOCODE;
+            }            
+        } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_data.php
new file mode 100644 (file)
index 0000000..609376e
--- /dev/null
@@ -0,0 +1,479 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Data
+ * 
+ * This file contains the basic classes and methodes for template and variable creation
+ * 
+ * @package Smarty
+ * @subpackage Templates
+ * @author Uwe Tews 
+ */
+
+/**
+ * Base class with template and variable methodes
+ */
+class Smarty_Internal_Data {
+    // class used for templates
+    public $template_class = 'Smarty_Internal_Template';
+
+    /**
+     * assigns a Smarty variable
+     * 
+     * @param array $ |string $tpl_var the template variable name(s)
+     * @param mixed $value the value to assign
+     * @param boolean $nocache if true any output of this variable will be not cached
+     * @param boolean $scope the scope the variable will have  (local,parent or root)
+     */
+    public function assign($tpl_var, $value = null, $nocache = false)
+    {
+        if (is_array($tpl_var)) {
+            foreach ($tpl_var as $_key => $_val) {
+                if ($_key != '') {
+                    $this->tpl_vars[$_key] = new Smarty_variable($_val, $nocache);
+                } 
+            } 
+        } else {
+            if ($tpl_var != '') {
+                $this->tpl_vars[$tpl_var] = new Smarty_variable($value, $nocache);
+            } 
+        } 
+    } 
+    /**
+     * assigns a global Smarty variable
+     * 
+     * @param string $varname the global variable name
+     * @param mixed $value the value to assign
+     * @param boolean $nocache if true any output of this variable will be not cached
+     */
+    public function assignGlobal($varname, $value = null, $nocache = false)
+    {
+        if ($varname != '') {
+            Smarty::$global_tpl_vars[$varname] = new Smarty_variable($value, $nocache);
+        } 
+    } 
+    /**
+     * assigns values to template variables by reference
+     * 
+     * @param string $tpl_var the template variable name
+     * @param mixed $ &$value the referenced value to assign
+     * @param boolean $nocache if true any output of this variable will be not cached
+     */
+    public function assignByRef($tpl_var, &$value, $nocache = false)
+    {
+        if ($tpl_var != '') {
+            $this->tpl_vars[$tpl_var] = new Smarty_variable(null, $nocache);
+            $this->tpl_vars[$tpl_var]->value = &$value;
+        } 
+    } 
+
+    /**
+     * wrapper function for Smarty 2 BC
+     * 
+     * @param string $tpl_var the template variable name
+     * @param mixed $ &$value the referenced value to assign
+     */
+    public function assign_by_ref($tpl_var, &$value)
+    {
+               if($this->smarty->deprecation_notices)
+               trigger_error("function call 'assign_by_ref' is unknown or deprecated, use 'assignByRef'", E_USER_NOTICE);
+        $this->assignByRef($tpl_var, $value);
+    } 
+    /**
+     * appends values to template variables
+     * 
+     * @param array $ |string $tpl_var the template variable name(s)
+     * @param mixed $value the value to append
+     * @param boolean $merge flag if array elements shall be merged
+     * @param boolean $nocache if true any output of this variable will be not cached
+     */
+    public function append($tpl_var, $value = null, $merge = false, $nocache = false)
+    {
+        if (is_array($tpl_var)) {
+            // $tpl_var is an array, ignore $value
+            foreach ($tpl_var as $_key => $_val) {
+                if ($_key != '') {
+                    if (!isset($this->tpl_vars[$_key])) {
+                        $tpl_var_inst = $this->getVariable($_key, null, true, false);
+                        if ($tpl_var_inst instanceof Undefined_Smarty_Variable) {
+                            $this->tpl_vars[$_key] = new Smarty_variable(null, $nocache);
+                        } else {
+                            $this->tpl_vars[$_key] = clone $tpl_var_inst;
+                        } 
+                    } 
+                    if (!(is_array($this->tpl_vars[$_key]->value) || $this->tpl_vars[$_key]->value instanceof ArrayAccess)) {
+                        settype($this->tpl_vars[$_key]->value, 'array');
+                    } 
+                    if ($merge && is_array($_val)) {
+                        foreach($_val as $_mkey => $_mval) {
+                            $this->tpl_vars[$_key]->value[$_mkey] = $_mval;
+                        } 
+                    } else {
+                        $this->tpl_vars[$_key]->value[] = $_val;
+                    } 
+                } 
+            } 
+        } else {
+            if ($tpl_var != '' && isset($value)) {
+                if (!isset($this->tpl_vars[$tpl_var])) {
+                    $tpl_var_inst = $this->getVariable($tpl_var, null, true, false);
+                    if ($tpl_var_inst instanceof Undefined_Smarty_Variable) {
+                        $this->tpl_vars[$tpl_var] = new Smarty_variable(null, $nocache);
+                    } else {
+                        $this->tpl_vars[$tpl_var] = clone $tpl_var_inst;
+                    } 
+                } 
+                if (!(is_array($this->tpl_vars[$tpl_var]->value) || $this->tpl_vars[$tpl_var]->value instanceof ArrayAccess)) {
+                    settype($this->tpl_vars[$tpl_var]->value, 'array');
+                } 
+                if ($merge && is_array($value)) {
+                    foreach($value as $_mkey => $_mval) {
+                        $this->tpl_vars[$tpl_var]->value[$_mkey] = $_mval;
+                    } 
+                } else {
+                    $this->tpl_vars[$tpl_var]->value[] = $value;
+                } 
+            } 
+        } 
+    } 
+
+    /**
+     * appends values to template variables by reference
+     * 
+     * @param string $tpl_var the template variable name
+     * @param mixed $ &$value the referenced value to append
+     * @param boolean $merge flag if array elements shall be merged
+     */
+    public function appendByRef($tpl_var, &$value, $merge = false)
+    {
+        if ($tpl_var != '' && isset($value)) {
+            if (!isset($this->tpl_vars[$tpl_var])) {
+                $this->tpl_vars[$tpl_var] = new Smarty_variable();
+            } 
+            if (!@is_array($this->tpl_vars[$tpl_var]->value)) {
+                settype($this->tpl_vars[$tpl_var]->value, 'array');
+            } 
+            if ($merge && is_array($value)) {
+                foreach($value as $_key => $_val) {
+                    $this->tpl_vars[$tpl_var]->value[$_key] = &$value[$_key];
+                } 
+            } else {
+                $this->tpl_vars[$tpl_var]->value[] = &$value;
+            } 
+        } 
+    } 
+     /**
+     * 
+     * @param string $tpl_var the template variable name
+     * @param mixed $ &$value the referenced value to append
+     * @param boolean $merge flag if array elements shall be merged
+     */
+    public function append_by_ref($tpl_var, &$value, $merge = false)
+    {
+               if($this->smarty->deprecation_notices)
+               trigger_error("function call 'append_by_ref' is unknown or deprecated, use 'appendByRef'", E_USER_NOTICE);
+        $this->appendByRef($tpl_var, $value, $merge);
+    } 
+    /**
+     * Returns a single or all template variables
+     * 
+     * @param string $varname variable name or null
+     * @return string variable value or or array of variables
+     */
+    function getTemplateVars($varname = null, $_ptr = null, $search_parents = true)
+    {
+        if (isset($varname)) {
+            $_var = $this->getVariable($varname, $_ptr, $search_parents, false);
+            if (is_object($_var)) {
+                return $_var->value;
+            } else {
+                return null;
+            } 
+        } else {
+            $_result = array();
+            if ($_ptr === null) {
+                $_ptr = $this;
+            } while ($_ptr !== null) {
+                foreach ($_ptr->tpl_vars AS $key => $var) {
+                    if (!array_key_exists($key, $_result)) {
+                        $_result[$key] = $var->value;
+                    }
+                } 
+                // not found, try at parent
+                if ($search_parents) {
+                    $_ptr = $_ptr->parent;
+                } else {
+                    $_ptr = null;
+                } 
+            } 
+            if ($search_parents && isset(Smarty::$global_tpl_vars)) {
+                foreach (Smarty::$global_tpl_vars AS $key => $var) {
+                    if (!array_key_exists($key, $_result)) {
+                        $_result[$key] = $var->value;
+                    }
+                } 
+            } 
+            return $_result;
+        } 
+    } 
+
+    /**
+     * clear the given assigned template variable.
+     * 
+     * @param string $ |array $tpl_var the template variable(s) to clear
+     */
+    public function clearAssign($tpl_var)
+    {
+        if (is_array($tpl_var)) {
+            foreach ($tpl_var as $curr_var) {
+                unset($this->tpl_vars[$curr_var]);
+            } 
+        } else {
+            unset($this->tpl_vars[$tpl_var]);
+        } 
+    } 
+
+    /**
+     * clear all the assigned template variables.
+     */
+    public function clearAllAssign()
+    {
+        $this->tpl_vars = array();
+    } 
+
+    /**
+     * load a config file, optionally load just selected sections
+     * 
+     * @param string $config_file filename
+     * @param mixed $sections array of section names, single section or null
+     */
+    public function configLoad($config_file, $sections = null)
+    { 
+        // load Config class
+        $config = new Smarty_Internal_Config($config_file, $this->smarty, $this);
+        $config->loadConfigVars($sections);
+    } 
+
+    /**
+     * gets the object of a Smarty variable
+     * 
+     * @param string $variable the name of the Smarty variable
+     * @param object $_ptr optional pointer to data object
+     * @param boolean $search_parents search also in parent data
+     * @return object the object of the variable
+     */
+    public function getVariable($_variable, $_ptr = null, $search_parents = true, $error_enable = true)
+    {
+        if ($_ptr === null) {
+            $_ptr = $this;
+        } while ($_ptr !== null) {
+            if (isset($_ptr->tpl_vars[$_variable])) {
+                // found it, return it
+                return $_ptr->tpl_vars[$_variable];
+            } 
+            // not found, try at parent
+            if ($search_parents) {
+                $_ptr = $_ptr->parent;
+            } else {
+                $_ptr = null;
+            } 
+        } 
+        if (isset(Smarty::$global_tpl_vars[$_variable])) {
+            // found it, return it
+            return Smarty::$global_tpl_vars[$_variable];
+        } 
+        if ($this->smarty->error_unassigned && $error_enable) {
+            throw new SmartyException('Undefined Smarty variable "' . $_variable . '"');
+        } else {
+               if ($error_enable) {
+                               // force a notice
+                               $x = $$_variable;
+               }
+            return new Undefined_Smarty_Variable;
+        } 
+    } 
+    /**
+     * gets  a config variable
+     * 
+     * @param string $variable the name of the config variable
+     * @return mixed the value of the config variable
+     */
+    public function getConfigVariable($_variable)
+    {
+        $_ptr = $this;
+        while ($_ptr !== null) {
+            if (isset($_ptr->config_vars[$_variable])) {
+                // found it, return it
+                return $_ptr->config_vars[$_variable];
+            } 
+            // not found, try at parent
+            $_ptr = $_ptr->parent;
+        } 
+        if ($this->smarty->error_unassigned) {
+            throw new SmartyException('Undefined config variable "' . $_variable . '"');
+        } else {
+                       // force a notice
+                       $x = $$_variable;
+            return null;
+        } 
+    } 
+
+    /**
+     * gets  a stream variable
+     * 
+     * @param string $variable the stream of the variable
+     * @return mixed the value of the stream variable
+     */
+    public function getStreamVariable($variable)
+    {
+        $_result = '';
+        if ($fp = fopen($variable, 'r+')) {
+            while (!feof($fp)) {
+                $_result .= fgets($fp);
+            } 
+            fclose($fp);
+            return $_result;
+        } 
+
+        if ($this->smarty->error_unassigned) {
+            throw new SmartyException('Undefined stream variable "' . $variable . '"');
+        } else {
+            return null;
+        } 
+    } 
+
+    /**
+     * Returns a single or all config variables
+     * 
+     * @param string $varname variable name or null
+     * @return string variable value or or array of variables
+     */
+    function getConfigVars($varname = null, $search_parents = true)
+    {
+ //    var_dump($this);
+        $_ptr = $this;
+        $var_array = array();
+        while ($_ptr !== null) {
+               if (isset($varname)) {
+               if (isset($_ptr->config_vars[$varname])) {
+                       return $_ptr->config_vars[$varname];
+                }
+            } else {
+               $var_array = array_merge($_ptr->config_vars, $var_array);
+               } 
+             // not found, try at parent
+            if ($search_parents) {
+                $_ptr = $_ptr->parent;
+            } else {
+                $_ptr = null;
+            } 
+       } 
+        if (isset($varname)) {
+               return '';
+        } else {
+            return $var_array;
+        } 
+    } 
+
+    /**
+     * Deassigns a single or all config variables
+     * 
+     * @param string $varname variable name or null
+     */
+    function clearConfig($varname = null)
+    {
+        if (isset($varname)) {
+            unset($this->config_vars[$varname]);
+            return;
+        } else {
+            $this->config_vars = array();
+            return;
+        } 
+    } 
+
+} 
+
+/**
+ * class for the Smarty data object
+ * 
+ * The Smarty data object will hold Smarty variables in the current scope
+ * 
+ * @param object $parent tpl_vars next higher level of Smarty variables
+ */
+class Smarty_Data extends Smarty_Internal_Data {
+    // array of variable objects
+    public $tpl_vars = array(); 
+    // back pointer to parent object
+    public $parent = null; 
+    // config vars
+    public $config_vars = array(); 
+    // Smarty object
+    public $smarty = null;
+    /**
+     * create Smarty data object
+     */
+    public function __construct ($_parent = null, $smarty = null)
+    {
+        $this->smarty = $smarty;
+        if (is_object($_parent)) {
+            // when object set up back pointer
+            $this->parent = $_parent;
+        } elseif (is_array($_parent)) {
+            // set up variable values
+            foreach ($_parent as $_key => $_val) {
+                $this->tpl_vars[$_key] = new Smarty_variable($_val);
+            } 
+        } elseif ($_parent != null) {
+            throw new SmartyException("Wrong type for template variables");
+        } 
+    } 
+} 
+/**
+ * class for the Smarty variable object
+ * 
+ * This class defines the Smarty variable object
+ */
+class Smarty_Variable {
+    // template variable
+    public $value;
+    public $nocache;
+    public $scope;
+    /**
+     * create Smarty variable object
+     * 
+     * @param mixed $value the value to assign
+     * @param boolean $nocache if true any output of this variable will be not cached
+     * @param boolean $scope the scope the variable will have  (local,parent or root)
+     */
+    public function __construct ($value = null, $nocache = false, $scope = Smarty::SCOPE_LOCAL)
+    {
+        $this->value = $value;
+        $this->nocache = $nocache;
+        $this->scope = $scope;
+    } 
+
+    public function __toString ()
+    {
+        return $this->value;
+    } 
+} 
+
+/**
+ * class for undefined variable object
+ * 
+ * This class defines an object for undefined variable handling
+ */
+class Undefined_Smarty_Variable {
+    // return always false
+    public function __get ($name)
+    {
+        if ($name == 'nocache') {
+            return false;
+        } else {
+            return null;
+        } 
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_debug.php
new file mode 100644 (file)
index 0000000..0fd22a6
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+
+/**
+* Smarty Internal Plugin Debug
+*
+* Class to collect data for the Smarty Debugging Consol
+*
+* @package Smarty
+* @subpackage Debug
+* @author Uwe Tews
+*/
+
+/**
+* Smarty Internal Plugin Debug Class
+*/
+class Smarty_Internal_Debug extends Smarty_Internal_Data {
+       // template data
+       static $template_data = array();
+
+       /**
+       * Start logging of compile time
+       */
+       public static function start_compile($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['start_time'] = microtime(true);
+       }
+
+       /**
+       * End logging of compile time
+       */
+       public static function end_compile($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['compile_time'] += microtime(true) - self::$template_data[$key]['start_time'];
+       }
+
+       /**
+       * Start logging of render time
+       */
+       public static function start_render($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['start_time'] = microtime(true);
+       }
+
+       /**
+       * End logging of compile time
+       */
+       public static function end_render($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['render_time'] += microtime(true) - self::$template_data[$key]['start_time'];
+       }
+
+       /**
+       * Start logging of cache time
+       */
+       public static function start_cache($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['start_time'] = microtime(true);
+       }
+
+       /**
+       * End logging of cache time
+       */
+       public static function end_cache($template)
+       {
+               $key = self::get_key($template);
+               self::$template_data[$key]['cache_time'] += microtime(true) - self::$template_data[$key]['start_time'];
+       }
+       /**
+       * Opens a window for the Smarty Debugging Consol and display the data
+       */
+       public static function display_debug($obj)
+       {
+               // prepare information of assigned variables
+               $ptr = self::get_debug_vars($obj);
+               if ($obj instanceof Smarty) {
+                       $smarty = $obj;
+               } else {
+                       $smarty = $obj->smarty;
+               }
+               $_assigned_vars = $ptr->tpl_vars;
+               ksort($_assigned_vars);
+               $_config_vars = $ptr->config_vars;
+               ksort($_config_vars);
+               $ldelim = $smarty->left_delimiter;
+               $rdelim = $smarty->right_delimiter;
+               $smarty->left_delimiter = '{';
+               $smarty->right_delimiter = '}';
+               $_template = new Smarty_Internal_Template ($smarty->debug_tpl, $smarty);
+               $_template->caching = false;
+               $_template->force_compile = false;
+               $_template->disableSecurity();
+               $_template->cache_id = null;
+               $_template->compile_id = null;
+               if ($obj instanceof Smarty_Internal_Template) {
+                       $_template->assign('template_name',$obj->resource_type.':'.$obj->resource_name);
+               }
+               if ($obj instanceof Smarty) {
+                       $_template->assign('template_data', self::$template_data);
+               } else {
+                       $_template->assign('template_data', null);
+               }
+               $_template->assign('assigned_vars', $_assigned_vars);
+               $_template->assign('config_vars', $_config_vars);
+               $_template->assign('execution_time', microtime(true) - $smarty->start_time);
+               echo $_template->getRenderedTemplate();
+               $smarty->left_delimiter = $ldelim;
+               $smarty->right_delimiter = $rdelim;
+       }
+       /*
+       * Recursively gets variables from all template/data scopes
+       */
+       public static function get_debug_vars($obj)
+       {
+               $config_vars = $obj->config_vars;
+               $tpl_vars = array();
+               foreach ($obj->tpl_vars as $key => $var) {
+                       $tpl_vars[$key] = clone $var;
+                       if ($obj instanceof Smarty_Internal_Template) {
+                               $tpl_vars[$key]->scope = $obj->resource_type.':'.$obj->resource_name;
+                       } elseif ($obj instanceof Smarty_Data) {
+                               $tpl_vars[$key]->scope = 'Data object';
+                       } else {
+                               $tpl_vars[$key]->scope = 'Smarty root';
+                       }
+               }
+
+               if (isset($obj->parent)) {
+                       $parent = self::get_debug_vars($obj->parent);
+                       $tpl_vars = array_merge($parent->tpl_vars, $tpl_vars);
+                       $config_vars = array_merge($parent->config_vars, $config_vars);
+               } else {
+                       foreach (Smarty::$global_tpl_vars as $name => $var) {
+                               if (!array_key_exists($name, $tpl_vars)) {
+                                       $clone = clone $var;
+                                       $clone->scope = 'Global';
+                                       $tpl_vars[$name] = $clone;
+                               }
+                       }
+               }
+               return (object) array('tpl_vars' => $tpl_vars, 'config_vars' => $config_vars);
+       }
+
+       /**
+       * get_key
+       */
+       static function get_key($template)
+       {
+               // calculate Uid if not already done
+               if ($template->templateUid == '') {
+                       $template->getTemplateFilepath();
+               }
+               $key = $template->templateUid;
+               if (isset(self::$template_data[$key])) {
+                       return $key;
+               } else {
+                       self::$template_data[$key]['name'] = $template->getTemplateFilepath();
+                       self::$template_data[$key]['compile_time'] = 0;
+                       self::$template_data[$key]['render_time'] = 0;
+                       self::$template_data[$key]['cache_time'] = 0;
+                       return $key;
+               }
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_filter.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_filter.php
new file mode 100644 (file)
index 0000000..90214ad
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Filter
+ * 
+ * External Smarty filter methods
+ * 
+ * @package Smarty
+ * @author Uwe Tews 
+ */
+
+/**
+ * Class for filter methods
+ */
+class Smarty_Internal_Filter {
+
+    function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    /**
+     * Registers a filter function
+     * 
+     * @param string $type filter type
+     * @param callback $callback 
+     */
+       public function registerFilter($type, $callback)
+       {
+               $this->smarty->registered_filters[$type][$this->_get_filter_name($callback)] = $callback;
+       }
+
+    /**
+     * Unregisters a filter function
+     * 
+     * @param string $type filter type
+     * @param callback $callback 
+     */
+       public function unregisterFilter($type, $callback)
+       {
+               $name = $this->_get_filter_name($callback);
+               if(isset($this->smarty->registered_filters[$type][$name])) {
+               unset($this->smarty->registered_filters[$type][$name]);
+               }
+       }
+
+
+    /**
+     * Return internal filter name
+     * 
+     * @param callback $function_name 
+     */
+    public function _get_filter_name($function_name)
+    {
+        if (is_array($function_name)) {
+            $_class_name = (is_object($function_name[0]) ?
+                get_class($function_name[0]) : $function_name[0]);
+            return $_class_name . '_' . $function_name[1];
+        } else {
+            return $function_name;
+        } 
+    } 
+
+
+    /**
+     * load a filter of specified type and name
+     * 
+     * @param string $type filter type
+     * @param string $name filter name
+     * @return bool 
+     */
+    function loadFilter($type, $name)
+    {
+        $_plugin = "smarty_{$type}filter_{$name}";
+        $_filter_name = $_plugin;
+        if ($this->smarty->loadPlugin($_plugin)) {
+            if (class_exists($_plugin, false)) {
+                $_plugin = array($_plugin, 'execute');
+            } 
+            if (is_callable($_plugin)) {
+                return $this->smarty->registered_filters[$type][$_filter_name] = $_plugin;
+            } 
+        } 
+        throw new SmartyException("{$type}filter \"{$name}\" not callable");
+        return false;
+    } 
+
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_filter_handler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_filter_handler.php
new file mode 100644 (file)
index 0000000..fbd8846
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Filter Handler
+ * 
+ * Smarty filter handler class
+ * 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @author Uwe Tews 
+ */
+
+/**
+ * Class for filter processing
+ */
+class Smarty_Internal_Filter_Handler {
+    /**
+     * Run filters over content
+     * 
+     * The filters will be lazy loaded if required
+     * class name format: Smarty_FilterType_FilterName
+     * plugin filename format: filtertype.filtername.php
+     * Smarty2 filter plugins could be used
+     * 
+     * @param string $type the type of filter ('pre','post','output' or 'variable') which shall run
+     * @param string $content the content which shall be processed by the filters
+     * @return string the filtered content
+     */
+    static function runFilter($type, $content, $template, $flag = null)
+    {
+        $output = $content;
+        if ($type != 'variable' || ($template->smarty->variable_filter && $flag !== false) || $flag === true) {
+            // loop over autoload filters of specified type
+            if (!empty($template->smarty->autoload_filters[$type])) {
+                foreach ((array)$template->smarty->autoload_filters[$type] as $name) {
+                    $plugin_name = "Smarty_{$type}filter_{$name}";
+                    if ($template->smarty->loadPlugin($plugin_name)) {
+                        if (function_exists($plugin_name)) {
+                            // use loaded Smarty2 style plugin
+                            $output = $plugin_name($output, $template);
+                        } elseif (class_exists($plugin_name, false)) {
+                            // loaded class of filter plugin
+                            $output = call_user_func(array($plugin_name, 'execute'), $output, $template);
+                        } 
+                    } else {
+                        // nothing found, throw exception
+                        throw new SmartyException("Unable to load filter {$plugin_name}");
+                    } 
+                } 
+            } 
+            // loop over registerd filters of specified type
+            if (!empty($template->smarty->registered_filters[$type])) {
+                foreach ($template->smarty->registered_filters[$type] as $key => $name) {
+                    if (is_array($template->smarty->registered_filters[$type][$key])) {
+                        $output = call_user_func($template->smarty->registered_filters[$type][$key], $output, $template);
+                    } else {
+                        $output = $template->smarty->registered_filters[$type][$key]($output, $template);
+                    } 
+                } 
+            } 
+        } 
+        // return filtered output
+        return $output;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_function_call_handler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_function_call_handler.php
new file mode 100644 (file)
index 0000000..f9860dc
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Smarty Internal Plugin Function Call Handler
+ * 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @author Uwe Tews 
+ */
+
+/**
+ * This class does call function defined with the {function} tag
+ */
+class Smarty_Internal_Function_Call_Handler extends Smarty_Internal_Template {
+    static function call ($_name, $_template, $_params, $_hash, $_nocache)
+    {
+        if ($_nocache) {
+            $_function = "smarty_template_function_{$_name}_nocache";
+            $_template->smarty->template_functions[$_name]['called_nocache'] = true;
+        } else {
+            $_function = "smarty_template_function_{$_hash}_{$_name}";
+        } 
+        if (!is_callable($_function)) {
+            $_code = "function {$_function}(\$_smarty_tpl,\$params) {
+    \$saved_tpl_vars = \$_smarty_tpl->tpl_vars;
+    foreach (\$_smarty_tpl->template_functions['{$_name}']['parameter'] as \$key => \$value) {\$_smarty_tpl->tpl_vars[\$key] = new Smarty_variable(trim(\$value,'\''));};
+    foreach (\$params as \$key => \$value) {\$_smarty_tpl->tpl_vars[\$key] = new Smarty_variable(\$value);}?>";
+            if ($_nocache) {
+                $_code .= preg_replace(array("!<\?php echo \\'/\*%%SmartyNocache:{$_template->smarty->template_functions[$_name]['nocache_hash']}%%\*/|/\*/%%SmartyNocache:{$_template->smarty->template_functions[$_name]['nocache_hash']}%%\*/\\';\?>!",
+                        "!\\\'!"), array('', "'"), $_template->smarty->template_functions[$_name]['compiled']);
+            } else {
+                $_code .= preg_replace("/{$_template->smarty->template_functions[$_name]['nocache_hash']}/", $_template->properties['nocache_hash'], $_template->smarty->template_functions[$_name]['compiled']);
+            } 
+            $_code .= "<?php \$_smarty_tpl->tpl_vars = \$saved_tpl_vars;}";
+            eval($_code);
+        } 
+        $_function($_template, $_params);
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_get_include_path.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_get_include_path.php
new file mode 100644 (file)
index 0000000..23c85a2
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Smarty read include path plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @author Monte Ohrt 
+ */
+
+/**
+ * Smarty Internal Read Include Path Class
+ */
+class Smarty_Internal_Get_Include_Path {
+    /**
+     * Return full file path from PHP include_path
+     * 
+     * @param string $filepath filepath
+     * @return mixed full filepath or false
+     */
+    public static function getIncludePath($filepath)
+    {
+    static $_path_array = null;
+
+    if(!isset($_path_array)) {
+        $_ini_include_path = ini_get('include_path');
+
+        if(strstr($_ini_include_path,';')) {
+            // windows pathnames
+            $_path_array = explode(';',$_ini_include_path);
+        } else {
+            $_path_array = explode(':',$_ini_include_path);
+        }
+    }
+    foreach ($_path_array as $_include_path) {
+        if (file_exists($_include_path . DS . $filepath)) {
+            return $_include_path . DS . $filepath;
+        }
+    }
+    return false;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_nocache_insert.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_nocache_insert.php
new file mode 100644 (file)
index 0000000..1b25e80
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Nocache Insert
+ * 
+ * Compiles the {insert} tag into the cache file
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Compile Insert Class
+ */
+class Smarty_Internal_Nocache_Insert {
+    /**
+     * Compiles code for the {insert} tag into cache file
+     * 
+     * @param string $_function insert function name
+     * @param array $_attr array with paramter
+     * @param object $template template object
+     * @param string $_script script name to load or 'null'
+     * @param string $_assign soptinal variable name
+     * @return string compiled code
+     */
+    static function compile($_function, $_attr, $_template, $_script, $_assign = null)
+    {
+        $_output = '<?php ';
+        if ($_script != 'null') {
+            // script which must be included
+            // code for script file loading
+            $_output .= "require_once '{$_script}';";
+        } 
+        // call insert
+        if (isset($_assign)) {
+            $_output .= "\$_smarty_tpl->assign('{$_assign}' , {$_function} (" . var_export($_attr, true) . ",\$_smarty_tpl), true);?>";
+        } else {
+            $_output .= "echo {$_function}(" . var_export($_attr, true) . ",\$_smarty_tpl);?>";
+        } 
+        $_tpl = $_template;
+        while ($_tpl->parent instanceof Smarty_Internal_Template) {
+            $_tpl = $_tpl->parent;
+        } 
+        return "/*%%SmartyNocache:{$_tpl->properties['nocache_hash']}%%*/" . $_output . "/*/%%SmartyNocache:{$_tpl->properties['nocache_hash']}%%*/";
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_parsetree.php
new file mode 100644 (file)
index 0000000..9eba594
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Smarty Internal Plugin Templateparser Parsetrees
+ * 
+ * These are classes to build parsetrees in the template parser
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Thue Kristensen 
+ * @author Uwe Tews 
+ */
+abstract class _smarty_parsetree {
+  abstract public function to_smarty_php();
+}
+
+/**
+ * A complete smarty tag.
+ */
+class _smarty_tag extends _smarty_parsetree
+{
+    public $parser;
+    public $data;
+    public $saved_block_nesting;
+    function __construct($parser, $data)
+    {
+        $this->parser = $parser;
+        $this->data = $data;
+        $this->saved_block_nesting = $parser->block_nesting_level;
+    } 
+
+    public function to_smarty_php()
+    {
+        return $this->data;
+    } 
+
+    public function assign_to_var()
+    {
+        $var = sprintf('$_tmp%d', ++$this->parser->prefix_number);
+        $this->parser->compiler->prefix_code[] = sprintf('<?php ob_start();?>%s<?php %s=ob_get_clean();?>',
+            $this->data, $var);
+        return $var;
+    } 
+} 
+
+/**
+ * Code fragment inside a tag.
+ */
+class _smarty_code extends _smarty_parsetree {
+    public $parser;
+    public $data;
+    function __construct($parser, $data)
+    {
+        $this->parser = $parser;
+        $this->data = $data;
+    } 
+
+    public function to_smarty_php()
+    {
+        return sprintf("(%s)", $this->data);
+    } 
+} 
+
+/**
+ * Double quoted string inside a tag.
+ */
+class _smarty_doublequoted extends _smarty_parsetree {
+    public $parser;
+    public $subtrees = Array();
+    function __construct($parser, _smarty_parsetree $subtree)
+    {
+        $this->parser = $parser;
+        $this->subtrees[] = $subtree;
+        if ($subtree instanceof _smarty_tag) {
+            $this->parser->block_nesting_level = count($this->parser->compiler->_tag_stack);
+        } 
+    } 
+
+    function append_subtree(_smarty_parsetree $subtree)
+    {
+        $last_subtree = count($this->subtrees)-1;
+        if ($last_subtree >= 0 && $this->subtrees[$last_subtree] instanceof _smarty_tag && $this->subtrees[$last_subtree]->saved_block_nesting < $this->parser->block_nesting_level) {
+            if ($subtree instanceof _smarty_code) {
+                $this->subtrees[$last_subtree]->data .= '<?php echo ' . $subtree->data . ';?>';
+            } elseif ($subtree instanceof _smarty_dq_content) {
+                $this->subtrees[$last_subtree]->data .= '<?php echo "' . $subtree->data . '";?>';
+            } else {
+                $this->subtrees[$last_subtree]->data .= $subtree->data;
+            } 
+        } else {
+            $this->subtrees[] = $subtree;
+        } 
+        if ($subtree instanceof _smarty_tag) {
+            $this->parser->block_nesting_level = count($this->parser->compiler->_tag_stack);
+        } 
+    } 
+
+    public function to_smarty_php()
+    {
+        $code = '';
+        foreach ($this->subtrees as $subtree) {
+            if ($code !== "") {
+                $code .= ".";
+            } 
+            if ($subtree instanceof _smarty_tag) {
+                $more_php = $subtree->assign_to_var();
+            } else {
+                $more_php = $subtree->to_smarty_php();
+            } 
+
+            $code .= $more_php;
+
+            if (!$subtree instanceof _smarty_dq_content) {
+                $this->parser->compiler->has_variable_string = true;
+            } 
+        } 
+        return $code;
+    } 
+} 
+
+/**
+ * Raw chars as part of a double quoted string.
+ */
+class _smarty_dq_content extends _smarty_parsetree {
+    public $data;
+    function __construct($parser, $data)
+    {
+        $this->parser = $parser;
+        $this->data = $data;
+    } 
+
+    public function to_smarty_php()
+    {
+        return '"' . $this->data . '"';
+    } 
+} 
+
+/**
+ * Template element
+ */
+class _smarty_template_buffer extends _smarty_parsetree {
+    public $subtrees = Array();
+    function __construct($parser)
+    {
+        $this->parser = $parser;
+    } 
+
+    function append_subtree(_smarty_parsetree $subtree)
+    {
+        $this->subtrees[] = $subtree;
+    } 
+
+    public function to_smarty_php()
+    {
+        $code = '';
+        for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) {
+            if ($key + 2 < $cnt) {
+                if ($this->subtrees[$key] instanceof _smarty_linebreak && $this->subtrees[$key + 1] instanceof _smarty_tag && $this->subtrees[$key + 1]->data == '' && $this->subtrees[$key + 2] instanceof _smarty_linebreak) {
+                    $key = $key + 1;
+                    continue;
+                } 
+                if (substr($this->subtrees[$key]->data, -1) == '<' && $this->subtrees[$key + 1]->data == '' && substr($this->subtrees[$key + 2]->data, -1) == '?') {
+                    $key = $key + 2;
+                    continue;
+                } 
+            } 
+            if (substr($code, -1) == '<') {
+                $subtree = $this->subtrees[$key]->to_smarty_php();
+                if (substr($subtree, 0, 1) == '?') {
+                    $code = substr($code, 0, strlen($code)-1) . '<<?php ?>?' . substr($subtree, 1);
+                } elseif ($this->parser->asp_tags && substr($subtree, 0, 1) == '%') {
+                    $code = substr($code, 0, strlen($code)-1) . '<<?php ?>%' . substr($subtree, 1);
+           } else {
+                    $code .= $subtree;
+                } 
+                continue;
+            } 
+            if ($this->parser->asp_tags && substr($code, -1) == '%') {
+                $subtree = $this->subtrees[$key]->to_smarty_php();
+                if (substr($subtree, 0, 1) == '>') {
+                    $code = substr($code, 0, strlen($code)-1) . '%<?php ?>>' . substr($subtree, 1);
+           } else {
+                    $code .= $subtree;
+                } 
+                continue;
+            } 
+            if (substr($code, -1) == '?') {
+                $subtree = $this->subtrees[$key]->to_smarty_php();
+                if (substr($subtree, 0, 1) == '>') {
+                    $code = substr($code, 0, strlen($code)-1) . '?<?php ?>>' . substr($subtree, 1);
+           } else {
+                    $code .= $subtree;
+                } 
+                continue;
+            } 
+            $code .= $this->subtrees[$key]->to_smarty_php();
+        } 
+        return $code;
+    } 
+}
+
+/**
+ * template text
+ */
+class _smarty_text extends _smarty_parsetree {
+    public $data;
+    function __construct($parser, $data)
+    {
+        $this->parser = $parser;
+        $this->data = $data;
+    } 
+
+    public function to_smarty_php()
+    {
+        return $this->data;
+    } 
+} 
+
+/**
+ * template linebreaks
+ */
+class _smarty_linebreak extends _smarty_parsetree {
+    public $data;
+    function __construct($parser, $data)
+    {
+        $this->parser = $parser;
+        $this->data = $data;
+    } 
+
+    public function to_smarty_php()
+    {
+        return $this->data;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_register.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_register.php
new file mode 100644 (file)
index 0000000..edaa7ff
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Register
+ * 
+ * External Smarty methods register/unregister
+ * 
+ * @package Smarty
+ * @author Uwe Tews 
+ */
+
+/**
+ * Class for register/unregister methods
+ */
+class Smarty_Internal_Register {
+
+    function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    /**
+     * Registers plugin to be used in templates
+     * 
+     * @param string $type plugin type
+     * @param string $tag name of template tag
+     * @param callback $callback PHP callback to register
+     * @param boolean $cacheable if true (default) this fuction is cachable
+     * @param array $cache_attr caching attributes if any
+     */
+
+       public function registerPlugin($type, $tag, $callback, $cacheable = true, $cache_attr = null)
+       {
+               if (isset($this->smarty->registered_plugins[$type][$tag])) {
+               throw new Exception("Plugin tag \"{$tag}\" already registered");
+       } elseif (!is_callable($callback)) {
+               throw new Exception("Plugin \"{$tag}\" not callable");
+       } else {
+                       $this->smarty->registered_plugins[$type][$tag] = array($callback, (bool) $cacheable, (array) $cache_attr);
+       }
+       } 
+
+    /**
+     * Unregister Plugin
+     * 
+     * @param string $type of plugin
+     * @param string $tag name of plugin
+     */
+    function unregisterPlugin($type, $tag)
+    {
+        if (isset($this->smarty->registered_plugins[$type][$tag])) {
+            unset($this->smarty->registered_plugins[$type][$tag]);
+        } 
+    } 
+    /**
+     * Registers a resource to fetch a template
+     * 
+     * @param string $type name of resource type
+     * @param array $callback array of callbacks to handle resource
+     */
+       public function registerResource($type, $callback)
+       {
+               $this->smarty->registered_resources[$type] = array($callback, false);
+    }
+
+    /**
+     * Unregisters a resource 
+     * 
+     * @param string $type name of resource type
+     */
+   function unregisterResource($type)
+    {
+        if (isset($this->smarty->registered_resources[$type])) {
+            unset($this->smarty->registered_resources[$type]);
+        } 
+    } 
+
+
+    /**
+     * Registers object to be used in templates
+     * 
+     * @param string $object name of template object
+     * @param object $ &$object_impl the referenced PHP object to register
+     * @param mixed $ null | array $allowed list of allowed methods (empty = all)
+     * @param boolean $smarty_args smarty argument format, else traditional
+     * @param mixed $ null | array $block_functs list of methods that are block format
+     */
+    function registerObject($object_name, $object_impl, $allowed = array(), $smarty_args = true, $block_methods = array())
+    { 
+        // test if allowed methodes callable
+        if (!empty($allowed)) {
+            foreach ((array)$allowed as $method) {
+                if (!is_callable(array($object_impl, $method))) {
+                    throw new SmartyException("Undefined method '$method' in registered object");
+                } 
+            } 
+        } 
+        // test if block methodes callable
+        if (!empty($block_methods)) {
+            foreach ((array)$block_methods as $method) {
+                if (!is_callable(array($object_impl, $method))) {
+                    throw new SmartyException("Undefined method '$method' in registered object");
+                } 
+            } 
+        } 
+        // register the object
+        $this->smarty->registered_objects[$object_name] =
+        array($object_impl, (array)$allowed, (boolean)$smarty_args, (array)$block_methods);
+    } 
+
+    /**
+     * Registers static classes to be used in templates
+     * 
+     * @param string $class name of template class
+     * @param string $class_impl the referenced PHP class to register
+     */
+    function registerClass($class_name, $class_impl)
+    { 
+        // test if exists
+        if (!class_exists($class_impl)) {
+            throw new SmartyException("Undefined class '$class_impl' in register template class");
+        } 
+        // register the class
+        $this->smarty->registered_classes[$class_name] = $class_impl;
+    } 
+
+    /**
+     * Registers a default plugin handler
+     * 
+     * @param  $callback mixed string | array $plugin class/methode name
+     */
+    function registerDefaultPluginHandler($callback)
+    {
+        if (is_callable($callback)) {
+            $this->smarty->default_plugin_handler_func = $callback;
+        } else {
+            throw new SmartyException("Default plugin handler '$callback' not callable");
+        } 
+    } 
+
+    /**
+     * Registers a default template handler
+     * 
+     * @param  $callback mixed string | array class/method name
+     */
+    function registerDefaultTemplateHandler($callback)
+    {
+        if (is_callable($callback)) {
+            $this->smarty->default_template_handler_func = $callback;
+        } else {
+            throw new SmartyException("Default template handler '$callback' not callable");
+        } 
+    } 
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_eval.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_eval.php
new file mode 100644 (file)
index 0000000..a3acf71
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource Eval
+ * 
+ * Implements the strings as resource for Smarty template
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Resource Eval
+ */
+class Smarty_Internal_Resource_Eval {
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser';
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = true;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($template)
+    {
+        return true;
+    } 
+
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string return 'string' as template source is not a file
+     */
+    public function getTemplateFilepath($_template)
+    { 
+        // no filepath for evaluated strings
+        // return "string" for compiler error messages
+        return 'eval:';
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return boolean false as string resources have no timestamp
+     */
+    public function getTemplateTimestamp($_template)
+    { 
+        // evaluated strings must always be compiled and have no timestamp
+        return false;
+    } 
+
+    /**
+     * Retuen template source from resource name
+     * 
+     * @param object $_template template object
+     * @return string content of template source
+     */
+    public function getTemplateSource($_template)
+    { 
+        // return template string
+        $_template->template_source = $_template->resource_name;
+        return true;
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return boolean return false as compiled template is not stored
+     */
+    public function getCompiledFilepath($_template)
+    { 
+        // no filepath for strings
+        return false;
+    } 
+} 
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_extends.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_extends.php
new file mode 100644 (file)
index 0000000..74dc0f9
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource Extends
+ * 
+ * Implements the file system as resource for Smarty which does extend a chain of template files templates
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Resource Extends
+ */
+class Smarty_Internal_Resource_Extends {
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+        $this->_rdl = preg_quote($smarty->right_delimiter);
+        $this->_ldl = preg_quote($smarty->left_delimiter);
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser'; 
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = false;
+    public $allFilepaths = array();
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @param object $_template template object
+     * @return boolean result
+     */
+    public function isExisting($_template)
+    {
+         $_template->getTemplateFilepath();
+         foreach ($this->allFilepaths as $_filepath) {
+               if ($_filepath === false) {
+            return false;
+          }
+        }
+        return true;
+    } 
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string filepath to template source file
+     */
+    public function getTemplateFilepath($_template)
+    {
+        $sha1String = '';
+        $_files = explode('|', $_template->resource_name);
+        foreach ($_files as $_file) {
+            $_filepath = $_template->buildTemplateFilepath ($_file);
+            if ($_filepath !== false) {
+               if (is_object($_template->smarty->security_policy)) {
+                       $_template->smarty->security_policy->isTrustedResourceDir($_filepath);
+               } 
+            } 
+            $sha1String .= $_filepath;
+            $this->allFilepaths[$_file] = $_filepath;
+        } 
+        $_template->templateUid = sha1($sha1String);
+        return $_filepath;
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return integer timestamp of template source file
+     */
+    public function getTemplateTimestamp($_template)
+    {
+        return filemtime($_template->getTemplateFilepath());
+    } 
+
+    /**
+     * Read template source from file
+     * 
+     * @param object $_template template object
+     * @return string content of template source file
+     */
+    public function getTemplateSource($_template)
+    {
+        $this->template = $_template;
+        $_files = array_reverse($this->allFilepaths);
+               $_first = reset($_files);
+               $_last = end($_files);
+        foreach ($_files as $_file => $_filepath) {
+                       if ($_filepath === false) {
+                       throw new SmartyException("Unable to load template 'file : {$_file}'");
+                       }
+            // read template file
+            if ($_filepath != $_first) {
+                $_template->properties['file_dependency'][sha1($_filepath)] = array($_filepath, filemtime($_filepath),'file');
+            } 
+            $_template->template_filepath = $_filepath;
+            $_content = file_get_contents($_filepath);
+            if ($_filepath != $_last) {
+                if (preg_match_all("!({$this->_ldl}block\s(.+?){$this->_rdl})!", $_content, $_open) !=
+                        preg_match_all("!({$this->_ldl}/block{$this->_rdl})!", $_content, $_close)) {
+                    $this->smarty->triggerError("unmatched {block} {/block} pairs in file '$_filepath'");
+                } 
+                preg_match_all("!{$this->_ldl}block\s(.+?){$this->_rdl}|{$this->_ldl}/block{$this->_rdl}!", $_content, $_result, PREG_OFFSET_CAPTURE);
+                $_result_count = count($_result[0]);
+                $_start = 0;
+                while ($_start < $_result_count) {
+                    $_end = 0;
+                    $_level = 1;
+                    while ($_level != 0) {
+                        $_end++;
+                        if (!strpos($_result[0][$_start + $_end][0], '/')) {
+                            $_level++;
+                        } else {
+                            $_level--;
+                        } 
+                    } 
+                    $_block_content = str_replace($this->smarty->left_delimiter . '$smarty.block.parent' . $this->smarty->right_delimiter, '%%%%SMARTY_PARENT%%%%',
+                        substr($_content, $_result[0][$_start][1] + strlen($_result[0][$_start][0]), $_result[0][$_start + $_end][1] - $_result[0][$_start][1] - + strlen($_result[0][$_start][0])));
+                    Smarty_Internal_Compile_Block::saveBlockData($_block_content, $_result[0][$_start][0], $_template, $_filepath);
+                    $_start = $_start + $_end + 1;
+                } 
+            } else {
+                $_template->template_source = $_content;
+                return true;
+            } 
+        } 
+    }
+    
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return string return path to compiled template
+     */
+    public function getCompiledFilepath($_template)
+    {
+        $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
+        $_files = explode('|', $_template->resource_name); 
+        // calculate Uid if not already done
+        if ($_template->templateUid == '') {
+            $_template->getTemplateFilepath();
+        } 
+        $_filepath = $_template->templateUid; 
+        // if use_sub_dirs, break file into directories
+        if ($_template->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_compile_id)) {
+            $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
+        } 
+        if ($_template->caching) {
+            $_cache = '.cache';
+        } else {
+            $_cache = '';
+        } 
+        $_compile_dir = $_template->smarty->compile_dir;
+        if (substr($_compile_dir, -1) != DS) {
+            $_compile_dir .= DS;
+        } 
+        return $_compile_dir . $_filepath . '.' . $_template->resource_type . '.' . basename($_files[count($_files)-1]) . $_cache . '.php';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_file.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_file.php
new file mode 100644 (file)
index 0000000..79decc5
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource File
+ * 
+ * Implements the file system as resource for Smarty templates
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+
+/** 
+ * Smarty Internal Plugin Resource File
+ */
+class Smarty_Internal_Resource_File {
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser'; 
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = false;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($template)
+    {
+        if ($template->getTemplateFilepath() === false) {
+            return false;
+        } else {
+            return true;
+        } 
+    } 
+
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string filepath to template source file
+     */
+    public function getTemplateFilepath($_template)
+    {
+        $_filepath = $_template->buildTemplateFilepath ();
+
+        if ($_filepath !== false) {
+            if (is_object($_template->smarty->security_policy)) {
+                $_template->smarty->security_policy->isTrustedResourceDir($_filepath);
+            } 
+        } 
+        $_template->templateUid = sha1($_filepath);
+        return $_filepath;
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return integer timestamp of template source file
+     */
+    public function getTemplateTimestamp($_template)
+    {
+        return filemtime($_template->getTemplateFilepath());
+    } 
+
+    /**
+     * Read template source from file
+     * 
+     * @param object $_template template object
+     * @return string content of template source file
+     */
+    public function getTemplateSource($_template)
+    { 
+        // read template file
+        if (file_exists($_tfp = $_template->getTemplateFilepath())) {
+            $_template->template_source = file_get_contents($_tfp);
+            return true;
+        } else {
+            return false;
+        } 
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return string return path to compiled template
+     */
+    public function getCompiledFilepath($_template)
+    {
+        $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
+        // calculate Uid if not already done
+        if ($_template->templateUid == '') {
+            $_template->getTemplateFilepath();
+        } 
+        $_filepath = $_template->templateUid; 
+        // if use_sub_dirs, break file into directories
+        if ($_template->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_compile_id)) {
+            $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
+        } 
+        if ($_template->caching) {
+            $_cache = '.cache';
+        } else {
+            $_cache = '';
+        } 
+        $_compile_dir = $_template->smarty->compile_dir;
+        if (strpos('/\\', substr($_compile_dir, -1)) === false) {
+            $_compile_dir .= DS;
+        } 
+        return $_compile_dir . $_filepath . '.' . $_template->resource_type . '.' . basename($_template->resource_name) . $_cache . '.php';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_php.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_php.php
new file mode 100644 (file)
index 0000000..16c7744
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource PHP
+ * 
+ * Implements the file system as resource for PHP templates
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+
+/**
+ * Smarty Internal Plugin Resource PHP
+ */
+class Smarty_Internal_Resource_PHP {
+    /**
+     * Class constructor, enable short open tags
+     */
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+        ini_set('short_open_tag', '1');
+    } 
+    // properties
+    public $usesCompiler = false;
+    public $isEvaluated = false;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($template)
+    {
+        if ($template->getTemplateFilepath() === false) {
+            return false;
+        } else {
+            return true;
+        } 
+    } 
+
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string filepath to template source file
+     */
+    public function getTemplateFilepath($_template)
+    {
+        $_filepath = $_template->buildTemplateFilepath ();
+
+        if (is_object($_template->smarty->security_policy)) {
+            $_template->smarty->security_policy->isTrustedResourceDir($_filepath);
+        } 
+        $_template->templateUid = sha1($_filepath);
+        return $_filepath;
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return integer timestamp of template source file
+     */
+    public function getTemplateTimestamp($_template)
+    {
+        return filemtime($_template->getTemplateFilepath());
+    } 
+
+    /**
+     * Read template source from file
+     * 
+     * @param object $_template template object
+     * @return string content of template source file
+     */
+    public function getTemplateSource($_template)
+    {
+        if (file_exists($_tfp = $_template->getTemplateFilepath())) {
+            $_template->template_source = file_get_contents($_tfp);
+            return true;
+        } else {
+            return false;
+        } 
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return boolean return false as compiled template is not stored
+     */
+    public function getCompiledFilepath($_template)
+    { 
+        // no filepath for PHP templates
+        return false;
+    } 
+
+    /**
+     * renders the PHP template
+     */
+    public function renderUncompiled($_smarty_template)
+    {
+        if (!$this->smarty->allow_php_templates) {
+            throw new SmartyException("PHP templates are disabled");
+        } 
+        if ($this->getTemplateFilepath($_smarty_template) === false) {
+            throw new SmartyException("Unable to load template \"{$_smarty_template->resource_type} : {$_smarty_template->resource_name}\"");
+        } 
+        // prepare variables
+        $_smarty_ptr = $_smarty_template;
+        do {
+            foreach ($_smarty_ptr->tpl_vars as $_smarty_var => $_smarty_var_object) {
+                if (isset($_smarty_var_object->value)) {
+                    $$_smarty_var = $_smarty_var_object->value;
+                } 
+            } 
+            $_smarty_ptr = $_smarty_ptr->parent;
+        } while ($_smarty_ptr != null);
+        unset ($_smarty_var, $_smarty_var_object, $_smarty_ptr); 
+        // include PHP template
+        include($this->getTemplateFilepath($_smarty_template));
+        return;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_registered.php
new file mode 100644 (file)
index 0000000..467da11
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource Registered
+ * 
+ * Implements the registered resource for Smarty template
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Resource Registered
+ */
+class Smarty_Internal_Resource_Registered {
+    public function __construct($template, $resource_type = null)
+    {
+        $this->smarty = $template->smarty;
+        if (isset($resource_type)) {
+               $template->smarty->registerResource($resource_type,
+                       array("smarty_resource_{$resource_type}_source",
+                       "smarty_resource_{$resource_type}_timestamp",
+                       "smarty_resource_{$resource_type}_secure",
+                       "smarty_resource_{$resource_type}_trusted"));
+        }
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser';
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = false;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($_template)
+    {
+        if (is_integer($_template->getTemplateTimestamp())) {
+            return true;
+        } else {
+            return false;
+        } 
+    } 
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string return 'string' as template source is not a file
+     */
+    public function getTemplateFilepath($_template)
+    { 
+        $_filepath = $_template->resource_type .':'.$_template->resource_name;
+        $_template->templateUid = sha1($_filepath);
+        return $_filepath;
+    } 
+
+    /**
+     * Get timestamp of template source
+     * 
+     * @param object $_template template object
+     * @return int  timestamp
+     */
+    public function getTemplateTimestamp($_template)
+    { 
+        // return timestamp
+        $time_stamp = false;
+        call_user_func_array($this->smarty->registered_resources[$_template->resource_type][0][1],
+            array($_template->resource_name, &$time_stamp, $this->smarty));
+        return is_numeric($time_stamp) ? (int)$time_stamp : $time_stamp;
+    }
+     
+    /**
+     * Get timestamp of template source by type and name
+     * 
+     * @param object $_template template object
+     * @return int  timestamp
+     */
+    public function getTemplateTimestampTypeName($_resource_type, $_resource_name)
+    { 
+        // return timestamp
+        $time_stamp = false;
+        call_user_func_array($this->smarty->registered_resources[$_resource_type][0][1],
+            array($_resource_name, &$time_stamp, $this->smarty));
+        return is_numeric($time_stamp) ? (int)$time_stamp : $time_stamp;
+    } 
+
+    /**
+     * Retuen template source from resource name
+     * 
+     * @param object $_template template object
+     * @return string content of template source
+     */
+    public function getTemplateSource($_template)
+    { 
+        // return template string
+        return call_user_func_array($this->smarty->registered_resources[$_template->resource_type][0][0],
+            array($_template->resource_name, &$_template->template_source, $this->smarty));
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return boolean return false as compiled template is not stored
+     */
+    public function getCompiledFilepath($_template)
+    { 
+        $_compile_id =  isset($_template->compile_id) ? preg_replace('![^\w\|]+!','_',$_template->compile_id) : null;
+        // calculate Uid if not already done
+        if ($_template->templateUid == '') {
+            $_template->getTemplateFilepath();
+        } 
+        $_filepath = $_template->templateUid; 
+        // if use_sub_dirs, break file into directories
+        if ($_template->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_compile_id)) {
+            $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
+        } 
+        if ($_template->caching) {
+            $_cache = '.cache';
+        } else {
+            $_cache = '';
+        } 
+        $_compile_dir = $_template->smarty->compile_dir;
+        if (strpos('/\\', substr($_compile_dir, -1)) === false) {
+            $_compile_dir .= DS;
+        } 
+        return $_compile_dir . $_filepath . '.' . $_template->resource_type . '.' . basename($_template->resource_name) . $_cache . '.php';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_stream.php
new file mode 100644 (file)
index 0000000..de2996e
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource Stream
+ * 
+ * Implements the streams as resource for Smarty template
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Resource Stream
+ */
+class Smarty_Internal_Resource_Stream {
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser';
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = true;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($template)
+    {
+        if ($template->getTemplateSource() == '') {
+            return false;
+        } else {
+            return true;
+        } 
+    } 
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string return 'string' as template source is not a file
+     */
+    public function getTemplateFilepath($_template)
+    { 
+        // no filepath for strings
+        // return resource name for compiler error messages
+        return str_replace(':', '://', $_template->template_resource);
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return boolean false as string resources have no timestamp
+     */
+    public function getTemplateTimestamp($_template)
+    { 
+        // strings must always be compiled and have no timestamp
+        return false;
+    } 
+
+    /**
+     * Retuen template source from resource name
+     * 
+     * @param object $_template template object
+     * @return string content of template source
+     */
+    public function getTemplateSource($_template)
+    { 
+        // return template string
+        $_template->template_source = '';
+        $fp = fopen(str_replace(':', '://', $_template->template_resource),'r+');
+        while (!feof($fp)) {
+            $_template->template_source .= fgets($fp);
+        } 
+        fclose($fp);
+
+        return true;
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return boolean return false as compiled template is not stored
+     */
+    public function getCompiledFilepath($_template)
+    { 
+        // no filepath for strings
+        return false;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_string.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_resource_string.php
new file mode 100644 (file)
index 0000000..9368f04
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Resource String
+ * 
+ * Implements the strings as resource for Smarty template
+ * 
+ * @package Smarty
+ * @subpackage TemplateResources
+ * @author Uwe Tews 
+ */
+/**
+ * Smarty Internal Plugin Resource String
+ */
+class Smarty_Internal_Resource_String {
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+    // classes used for compiling Smarty templates from file resource
+    public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
+    public $template_lexer_class = 'Smarty_Internal_Templatelexer';
+    public $template_parser_class = 'Smarty_Internal_Templateparser';
+    // properties
+    public $usesCompiler = true;
+    public $isEvaluated = false;
+
+    /**
+     * Return flag if template source is existing
+     * 
+     * @return boolean true
+     */
+    public function isExisting($template)
+    {
+        return true;
+    } 
+
+    /**
+     * Get filepath to template source
+     * 
+     * @param object $_template template object
+     * @return string return 'string' as template source is not a file
+     */
+    public function getTemplateFilepath($_template)
+    { 
+        $_template->templateUid = sha1($_template->resource_name);
+        // no filepath for strings
+        // return "string" for compiler error messages
+        return 'string:';
+    } 
+
+    /**
+     * Get timestamp to template source
+     * 
+     * @param object $_template template object
+     * @return boolean false as string resources have no timestamp
+     */
+    public function getTemplateTimestamp($_template)
+    { 
+        if ($this->isEvaluated) {
+               //must always be compiled and have no timestamp
+               return false;
+        } else {
+               return 0;
+        }
+    } 
+
+    /**
+     * Get timestamp of template source by type and name
+     * 
+     * @param object $_template template object
+     * @return int  timestamp (always 0)
+     */
+    public function getTemplateTimestampTypeName($_resource_type, $_resource_name)
+    { 
+        // return timestamp 0
+        return 0;
+    } 
+
+
+    /**
+     * Retuen template source from resource name
+     * 
+     * @param object $_template template object
+     * @return string content of template source
+     */
+    public function getTemplateSource($_template)
+    { 
+        // return template string
+        $_template->template_source = $_template->resource_name;
+        return true;
+    } 
+
+    /**
+     * Get filepath to compiled template
+     * 
+     * @param object $_template template object
+     * @return boolean return false as compiled template is not stored
+     */
+    public function getCompiledFilepath($_template)
+    {
+        $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
+        // calculate Uid if not already done
+        if ($_template->templateUid == '') {
+            $_template->getTemplateFilepath();
+        } 
+        $_filepath = $_template->templateUid; 
+        // if use_sub_dirs, break file into directories
+        if ($_template->smarty->use_sub_dirs) {
+            $_filepath = substr($_filepath, 0, 2) . DS
+             . substr($_filepath, 2, 2) . DS
+             . substr($_filepath, 4, 2) . DS
+             . $_filepath;
+        } 
+        $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
+        if (isset($_compile_id)) {
+            $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
+        } 
+        if ($_template->caching) {
+            $_cache = '.cache';
+        } else {
+            $_cache = '';
+        } 
+        $_compile_dir = $_template->smarty->compile_dir;
+        if (strpos('/\\', substr($_compile_dir, -1)) === false) {
+            $_compile_dir .= DS;
+        } 
+        return $_compile_dir . $_filepath . '.' . $_template->resource_type . $_cache . '.php';
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php
new file mode 100644 (file)
index 0000000..dcc89c6
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Smarty Template Compiler Base
+ * 
+ * This file contains the basic classes and methodes for compiling Smarty templates with lexer/parser
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+require_once("smarty_internal_parsetree.php");
+
+/**
+ * Class SmartyTemplateCompiler
+ */
+class Smarty_Internal_SmartyTemplateCompiler extends Smarty_Internal_TemplateCompilerBase {
+    // array of vars which can be compiled in local scope
+    public $local_var = array();
+    /**
+     * Initialize compiler
+     */
+    public function __construct($lexer_class, $parser_class, $smarty)
+    {
+        $this->smarty = $smarty;
+        parent::__construct(); 
+        // get required plugins
+        $this->lexer_class = $lexer_class;
+        $this->parser_class = $parser_class;
+    } 
+
+    /**
+     * Methode to compile a Smarty template
+     * 
+     * @param  $_content template source
+     * @return bool true if compiling succeeded, false if it failed
+     */
+    protected function doCompile($_content)
+    {
+        /* here is where the compiling takes place. Smarty
+       tags in the templates are replaces with PHP code,
+       then written to compiled files. */ 
+        // init the lexer/parser to compile the template
+        $this->lex = new $this->lexer_class($_content, $this);
+        $this->parser = new $this->parser_class($this->lex, $this);
+        if (isset($this->smarty->_parserdebug)) $this->parser->PrintTrace(); 
+        // get tokens from lexer and parse them
+        while ($this->lex->yylex() && !$this->abort_and_recompile) {
+            if (isset($this->smarty->_parserdebug)) echo "<pre>Line {$this->lex->line} Parsing  {$this->parser->yyTokenName[$this->lex->token]} Token " . htmlentities($this->lex->value) . "</pre>";
+            $this->parser->doParse($this->lex->token, $this->lex->value);
+        } 
+
+        if ($this->abort_and_recompile) {
+            // exit here on abort
+            return false;
+        } 
+        // finish parsing process
+        $this->parser->doParse(0, 0); 
+        // check for unclosed tags
+        if (count($this->_tag_stack) > 0) {
+            // get stacked info
+            list($_open_tag, $_data) = array_pop($this->_tag_stack);
+            $this->trigger_template_error("unclosed {" . $_open_tag . "} tag");
+        } 
+        // return compiled code
+        // return str_replace(array("? >\n<?php","? ><?php"), array('',''), $this->parser->retvalue);
+        return $this->parser->retvalue;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_template.php
new file mode 100644 (file)
index 0000000..070fb84
--- /dev/null
@@ -0,0 +1,998 @@
+<?php
+
+/**
+ * Smarty Internal Plugin Template
+ * 
+ * This file contains the Smarty template engine
+ * 
+ * @package Smarty
+ * @subpackage Templates
+ * @author Uwe Tews 
+ */
+
+/**
+ * Main class with template data structures and methods
+ */
+class Smarty_Internal_Template extends Smarty_Internal_Data {
+    // object cache
+    public $compiler_object = null;
+    public $cacher_object = null; 
+    // Smarty parameter
+    public $cache_id = null;
+    public $compile_id = null;
+    public $caching = null;
+    public $cache_lifetime = null;
+    public $cacher_class = null;
+    public $caching_type = null;
+    public $forceNocache = false; 
+    // Template resource
+    public $template_resource = null;
+    public $resource_type = null;
+    public $resource_name = null;
+//    public $resource_object = null;
+    private $isExisting = null;
+    public $templateUid = ''; 
+    // Template source
+    public $template_filepath = null;
+    public $template_source = null;
+    private $template_timestamp = null; 
+    // Compiled template
+    private $compiled_filepath = null;
+    public $compiled_template = null;
+    private $compiled_timestamp = null;
+    public $mustCompile = null;
+    public $suppressHeader = false;
+    public $suppressFileDependency = false;
+    public $has_nocache_code = false; 
+    public $write_compiled_code = true; 
+    // Rendered content
+    public $rendered_content = null; 
+    // Cache file
+    private $cached_filepath = null;
+    public $cached_timestamp = null;
+    private $isCached = null;
+//    private $cache_resource_object = null;
+    private $cacheFileChecked = false; 
+    // template variables
+    public $tpl_vars = array();
+    public $parent = null;
+    public $config_vars = array(); 
+    // storage for plugin
+    public $plugin_data = array(); 
+    // special properties
+    public $properties = array ('file_dependency' => array(),
+        'nocache_hash' => '',
+        'function' => array()); 
+    // required plugins
+    public $required_plugins = array('compiled' => array(), 'nocache' => array());
+    public $saved_modifier = null;
+    public $smarty = null;
+    // blocks for template inheritance
+    public $block_data = array();
+    public $wrapper = null;
+    /**
+     * Create template data object
+     * 
+     * Some of the global Smarty settings copied to template scope
+     * It load the required template resources and cacher plugins
+     * 
+     * @param string $template_resource template resource string
+     * @param object $_parent back pointer to parent object with variables or null
+     * @param mixed $_cache_id cache id or null
+     * @param mixed $_compile_id compile id or null
+     */
+    public function __construct($template_resource, $smarty, $_parent = null, $_cache_id = null, $_compile_id = null, $_caching = null, $_cache_lifetime = null)
+    {
+        $this->smarty = &$smarty; 
+        // Smarty parameter
+        $this->cache_id = $_cache_id === null ? $this->smarty->cache_id : $_cache_id;
+        $this->compile_id = $_compile_id === null ? $this->smarty->compile_id : $_compile_id;
+        $this->caching = $_caching === null ? $this->smarty->caching : $_caching;
+        if ($this->caching === true) $this->caching =  Smarty::CACHING_LIFETIME_CURRENT;
+        $this->cache_lifetime = $_cache_lifetime === null ?$this->smarty->cache_lifetime : $_cache_lifetime;
+        $this->parent = $_parent; 
+        // dummy local smarty variable
+        $this->tpl_vars['smarty'] = new Smarty_Variable; 
+        // Template resource
+        $this->template_resource = $template_resource; 
+        // copy block data of template inheritance
+        if ($this->parent instanceof Smarty_Internal_Template) {
+               $this->block_data = $this->parent->block_data;
+        }
+    } 
+
+    /**
+     * Returns the template filepath
+     * 
+     * The template filepath is determined by the actual resource handler
+     * 
+     * @return string the template filepath
+     */
+    public function getTemplateFilepath ()
+    {
+        return $this->template_filepath === null ?
+        $this->template_filepath = $this->resource_object->getTemplateFilepath($this) :
+        $this->template_filepath;
+    } 
+
+    /**
+     * Returns the timpestamp of the template source
+     * 
+     * The template timestamp is determined by the actual resource handler
+     * 
+     * @return integer the template timestamp
+     */
+    public function getTemplateTimestamp ()
+    {
+        return $this->template_timestamp === null ?
+        $this->template_timestamp = $this->resource_object->getTemplateTimestamp($this) :
+        $this->template_timestamp;
+    } 
+
+    /**
+     * Returns the template source code
+     * 
+     * The template source is being read by the actual resource handler
+     * 
+     * @return string the template source
+     */
+    public function getTemplateSource ()
+    {
+        if ($this->template_source === null) {
+            if (!$this->resource_object->getTemplateSource($this)) {
+                throw new SmartyException("Unable to read template {$this->resource_type} '{$this->resource_name}'");
+            } 
+        } 
+        return $this->template_source;
+    } 
+
+    /**
+     * Returns if the  template is existing
+     * 
+     * The status is determined by the actual resource handler
+     * 
+     * @return boolean true if the template exists
+     */
+    public function isExisting ($error = false)
+    {
+        if ($this->isExisting === null) {
+            $this->isExisting = $this->resource_object->isExisting($this);
+        } 
+        if (!$this->isExisting && $error) {
+            throw new SmartyException("Unable to load template {$this->resource_type} '{$this->resource_name}'");
+        } 
+        return $this->isExisting;
+    } 
+
+    /**
+     * Returns if the current template must be compiled by the Smarty compiler
+     * 
+     * It does compare the timestamps of template source and the compiled templates and checks the force compile configuration
+     * 
+     * @return boolean true if the template must be compiled
+     */
+    public function mustCompile ()
+    {
+        $this->isExisting(true);
+        if ($this->mustCompile === null) {
+            $this->mustCompile = ($this->resource_object->usesCompiler && ($this->smarty->force_compile || $this->resource_object->isEvaluated || $this->getCompiledTimestamp () === false || 
+                    // ($this->smarty->compile_check && $this->getCompiledTimestamp () !== $this->getTemplateTimestamp ())));
+                    ($this->smarty->compile_check && $this->getCompiledTimestamp () < $this->getTemplateTimestamp ())));
+        } 
+        return $this->mustCompile;
+    } 
+
+    /**
+     * Returns the compiled template filepath
+     * 
+     * @return string the template filepath
+     */
+    public function getCompiledFilepath ()
+    {
+        return $this->compiled_filepath === null ?
+        ($this->compiled_filepath = !$this->resource_object->isEvaluated ? $this->resource_object->getCompiledFilepath($this) : false) :
+        $this->compiled_filepath;
+    } 
+
+    /**
+     * Returns the timpestamp of the compiled template
+     * 
+     * @return integer the template timestamp
+     */
+    public function getCompiledTimestamp ()
+    {
+        return $this->compiled_timestamp === null ?
+        ($this->compiled_timestamp = (!$this->resource_object->isEvaluated && file_exists($this->getCompiledFilepath())) ? filemtime($this->getCompiledFilepath()) : false) :
+        $this->compiled_timestamp;
+    } 
+
+    /**
+     * Returns the compiled template 
+     * 
+     * It checks if the template must be compiled or just read from the template resource
+     * 
+     * @return string the compiled template
+     */
+    public function getCompiledTemplate ()
+    {
+        if ($this->compiled_template === null) {
+            // see if template needs compiling.
+            if ($this->mustCompile()) {
+                $this->compileTemplateSource();
+            } else {
+                if ($this->compiled_template === null) {
+                    $this->compiled_template = !$this->resource_object->isEvaluated && $this->resource_object->usesCompiler ? file_get_contents($this->getCompiledFilepath()) : false;
+                } 
+            } 
+        } 
+        return $this->compiled_template;
+    } 
+
+    /**
+     * Compiles the template
+     * 
+     * If the template is not evaluated the compiled template is saved on disk
+     */
+    public function compileTemplateSource ()
+    {
+        if (!$this->resource_object->isEvaluated) {
+            $this->properties['file_dependency'] = array();
+            $this->properties['file_dependency'][$this->templateUid] = array($this->getTemplateFilepath(), $this->getTemplateTimestamp(),$this->resource_type);
+        } 
+        if ($this->smarty->debugging) {
+            Smarty_Internal_Debug::start_compile($this);
+        } 
+        // compile template
+        if (!is_object($this->compiler_object)) {
+            // load compiler
+            $this->smarty->loadPlugin($this->resource_object->compiler_class);
+            $this->compiler_object = new $this->resource_object->compiler_class($this->resource_object->template_lexer_class, $this->resource_object->template_parser_class, $this->smarty);
+        } 
+        // compile locking
+        if ($this->smarty->compile_locking && !$this->resource_object->isEvaluated) {
+            if ($saved_timestamp = $this->getCompiledTimestamp()) {
+                touch($this->getCompiledFilepath());
+            } 
+        } 
+        // call compiler
+        try {
+            $this->compiler_object->compileTemplate($this);
+        } 
+        catch (Exception $e) {
+            // restore old timestamp in case of error
+            if ($this->smarty->compile_locking && !$this->resource_object->isEvaluated && $saved_timestamp) {
+                touch($this->getCompiledFilepath(), $saved_timestamp);
+            } 
+            throw $e;
+        } 
+        // compiling succeded
+        if (!$this->resource_object->isEvaluated && $this->write_compiled_code) {
+            // write compiled template
+            Smarty_Internal_Write_File::writeFile($this->getCompiledFilepath(), $this->compiled_template, $this->smarty);
+        } 
+        if ($this->smarty->debugging) {
+            Smarty_Internal_Debug::end_compile($this);
+        }
+        // release objects to free memory
+               Smarty_Internal_TemplateCompilerBase::$_tag_objects = array();  
+        unset($this->compiler_object->parser->root_buffer,
+               $this->compiler_object->parser->current_buffer,
+               $this->compiler_object->parser,
+               $this->compiler_object->lex,
+               $this->compiler_object->template,
+               $this->compiler_object
+               ); 
+    } 
+
+    /**
+     * Returns the filepath of the cached template output
+     * 
+     * The filepath is determined by the actual cache resource
+     * 
+     * @return string the cache filepath
+     */
+    public function getCachedFilepath ()
+    {
+        return $this->cached_filepath === null ?
+        $this->cached_filepath = ($this->resource_object->isEvaluated || !($this->caching == Smarty::CACHING_LIFETIME_CURRENT || $this->caching == Smarty::CACHING_LIFETIME_SAVED)) ? false : $this->cache_resource_object->getCachedFilepath($this) :
+        $this->cached_filepath;
+    } 
+
+    /**
+     * Returns the timpestamp of the cached template output
+     * 
+     * The timestamp is determined by the actual cache resource
+     * 
+     * @return integer the template timestamp
+     */
+    public function getCachedTimestamp ()
+    {
+        return $this->cached_timestamp === null ?
+        $this->cached_timestamp = ($this->resource_object->isEvaluated || !($this->caching == Smarty::CACHING_LIFETIME_CURRENT || $this->caching == Smarty::CACHING_LIFETIME_SAVED)) ? false : $this->cache_resource_object->getCachedTimestamp($this) :
+        $this->cached_timestamp;
+    } 
+
+    /**
+     * Returns the cached template output
+     * 
+     * @return string |booelan the template content or false if the file does not exist
+     */
+    public function getCachedContent ()
+    {
+        return $this->rendered_content === null ?
+        $this->rendered_content = ($this->resource_object->isEvaluated || !($this->caching == Smarty::CACHING_LIFETIME_CURRENT || $this->caching == Smarty::CACHING_LIFETIME_SAVED)) ? false : $this->cache_resource_object->getCachedContents($this) :
+        $this->rendered_content;
+    } 
+
+    /**
+     * Writes the cached template output
+     */
+    public function writeCachedContent ($content)
+    {
+        if ($this->resource_object->isEvaluated || !($this->caching == Smarty::CACHING_LIFETIME_CURRENT || $this->caching == Smarty::CACHING_LIFETIME_SAVED)) {
+            // don't write cache file
+            return false;
+        } 
+        $this->properties['cache_lifetime'] = $this->cache_lifetime;
+        return $this->cache_resource_object->writeCachedContent($this, $this->createPropertyHeader(true) .$content);
+    } 
+
+    /**
+     * Checks of a valid version redered HTML output is in the cache
+     * 
+     * If the cache is valid the contents is stored in the template object
+     * 
+     * @return boolean true if cache is valid
+     */
+    public function isCached ($template = null, $cache_id = null, $compile_id = null, $parent = null)
+    {
+       if ($template === null) {               
+                       $no_render = true;
+               } elseif ($template === false) {
+                       $no_render = false;
+               } else {
+                       if ($parent === null) {
+                               $parent = $this;
+                       }
+                       $this->smarty->isCached ($template, $cache_id, $compile_id, $parent);
+               }
+        if ($this->isCached === null) {
+            $this->isCached = false;
+            if (($this->caching == Smarty::CACHING_LIFETIME_CURRENT || $this->caching == Smarty::CACHING_LIFETIME_SAVED) && !$this->resource_object->isEvaluated) {
+                $cachedTimestamp = $this->getCachedTimestamp();
+                if ($cachedTimestamp === false || $this->smarty->force_compile || $this->smarty->force_cache) {
+                    return $this->isCached;
+                } 
+                if ($this->caching === Smarty::CACHING_LIFETIME_SAVED || ($this->caching == Smarty::CACHING_LIFETIME_CURRENT && (time() <= ($cachedTimestamp + $this->cache_lifetime) || $this->cache_lifetime < 0))) {
+                    if ($this->smarty->debugging) {
+                        Smarty_Internal_Debug::start_cache($this);
+                    } 
+                    $this->rendered_content = $this->cache_resource_object->getCachedContents($this, $no_render);
+                    if ($this->smarty->debugging) {
+                        Smarty_Internal_Debug::end_cache($this);
+                    } 
+                    if ($this->cacheFileChecked) {
+                        $this->isCached = true;
+                        return $this->isCached;
+                    } 
+                    $this->cacheFileChecked = true;
+                    if ($this->caching === Smarty::CACHING_LIFETIME_SAVED && $this->properties['cache_lifetime'] >= 0 && (time() > ($this->getCachedTimestamp() + $this->properties['cache_lifetime']))) {
+                        $this->tpl_vars = array();
+                        $this->rendered_content = null;
+                        return $this->isCached;
+                    } 
+                    if (!empty($this->properties['file_dependency']) && $this->smarty->compile_check) {
+                        $resource_type = null;
+                        $resource_name = null;
+                        foreach ($this->properties['file_dependency'] as $_file_to_check) {
+                            If ($_file_to_check[2] == 'file' || $_file_to_check[2] == 'extends' || $_file_to_check[2] == 'php') {
+                                $mtime = filemtime($_file_to_check[0]);
+                            } else {
+                                $this->getResourceTypeName($_file_to_check[0], $resource_type, $resource_name);
+                                $resource_handler = $this->loadTemplateResourceHandler($resource_type);
+                                $mtime = $resource_handler->getTemplateTimestampTypeName($resource_type, $resource_name);
+                            } 
+                            // If ($mtime > $this->getCachedTimestamp()) {
+                            If ($mtime > $_file_to_check[1]) {
+                                $this->tpl_vars = array();
+                                $this->rendered_content = null;
+                                return $this->isCached;
+                            } 
+                        } 
+                    } 
+                    $this->isCached = true;
+                } 
+            } 
+        } 
+        return $this->isCached;
+    } 
+
+    /**
+     * Render the output using the compiled template or the PHP template source
+     * 
+     * The rendering process is accomplished by just including the PHP files.
+     * The only exceptions are evaluated templates (string template). Their code has 
+     * to be evaluated
+     */
+    public function renderTemplate ()
+    {
+        if ($this->resource_object->usesCompiler) {
+            if ($this->mustCompile() && $this->compiled_template === null) {
+                $this->compileTemplateSource();
+            } 
+            if ($this->smarty->debugging) {
+                Smarty_Internal_Debug::start_render($this);
+            } 
+            $_smarty_tpl = $this;
+            ob_start();
+            if ($this->resource_object->isEvaluated) {
+                eval("?>" . $this->compiled_template);
+            } else {
+                include($this->getCompiledFilepath ()); 
+                // check file dependencies at compiled code
+                if ($this->smarty->compile_check) {
+                    if (!empty($this->properties['file_dependency'])) {
+                        $this->mustCompile = false;
+                        $resource_type = null;
+                        $resource_name = null;
+                        foreach ($this->properties['file_dependency'] as $_file_to_check) {
+                            If ($_file_to_check[2] == 'file' || $_file_to_check[2] == 'extends' || $_file_to_check[2] == 'php') {
+                                $mtime = filemtime($_file_to_check[0]);
+                            } else {
+                               $this->getResourceTypeName($_file_to_check[0], $resource_type, $resource_name);
+                                $resource_handler = $this->loadTemplateResourceHandler($resource_type);
+                                $mtime = $resource_handler->getTemplateTimestampTypeName($resource_type, $resource_name);
+                            } 
+                            // If ($mtime != $_file_to_check[1]) {
+                            If ($mtime > $_file_to_check[1]) {
+                                $this->mustCompile = true;
+                                break;
+                            } 
+                        } 
+                        if ($this->mustCompile) {
+                            // recompile and render again
+                            ob_get_clean();
+                            $this->compileTemplateSource();
+                            ob_start();
+                            include($this->getCompiledFilepath ());
+                        } 
+                    } 
+                } 
+            } 
+        } else {
+            if (is_callable(array($this->resource_object, 'renderUncompiled'))) {
+                if ($this->smarty->debugging) {
+                    Smarty_Internal_Debug::start_render($this);
+                } 
+                ob_start();
+                $this->resource_object->renderUncompiled($this);
+            } else {
+                throw new SmartyException("Resource '$this->resource_type' must have 'renderUncompiled' methode");
+            } 
+        } 
+        $this->rendered_content = ob_get_clean();
+        if (!$this->resource_object->isEvaluated && empty($this->properties['file_dependency'][$this->templateUid])) {
+            $this->properties['file_dependency'][$this->templateUid] = array($this->getTemplateFilepath(), $this->getTemplateTimestamp(),$this->resource_type);
+        } 
+        if ($this->parent instanceof Smarty_Internal_Template) {
+            $this->parent->properties['file_dependency'] = array_merge($this->parent->properties['file_dependency'], $this->properties['file_dependency']);
+            foreach($this->required_plugins as $code => $tmp1) {
+                foreach($tmp1 as $name => $tmp) {
+                    foreach($tmp as $type => $data) {
+                        $this->parent->required_plugins[$code][$name][$type] = $data;
+                    } 
+                } 
+            } 
+        } 
+        if ($this->smarty->debugging) {
+            Smarty_Internal_Debug::end_render($this);
+        } 
+        // write to cache when nessecary
+        if (!$this->resource_object->isEvaluated && ($this->caching == Smarty::CACHING_LIFETIME_SAVED || $this->caching == Smarty::CACHING_LIFETIME_CURRENT)) {
+            if ($this->smarty->debugging) {
+                Smarty_Internal_Debug::start_cache($this);
+            } 
+            $this->properties['has_nocache_code'] = false; 
+            // get text between non-cached items
+            $cache_split = preg_split("!/\*%%SmartyNocache:{$this->properties['nocache_hash']}%%\*\/(.+?)/\*/%%SmartyNocache:{$this->properties['nocache_hash']}%%\*/!s", $this->rendered_content); 
+            // get non-cached items
+            preg_match_all("!/\*%%SmartyNocache:{$this->properties['nocache_hash']}%%\*\/(.+?)/\*/%%SmartyNocache:{$this->properties['nocache_hash']}%%\*/!s", $this->rendered_content, $cache_parts);
+            $output = ''; 
+            // loop over items, stitch back together
+            foreach($cache_split as $curr_idx => $curr_split) {
+                // escape PHP tags in template content
+                $output .= preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php echo \'$1\'; ?>', $curr_split);
+                if (isset($cache_parts[0][$curr_idx])) {
+                    $this->properties['has_nocache_code'] = true; 
+                    // remove nocache tags from cache output
+                    $output .= preg_replace("!/\*/?%%SmartyNocache:{$this->properties['nocache_hash']}%%\*/!", '', $cache_parts[0][$curr_idx]);
+                } 
+            } 
+            if (isset($this->smarty->autoload_filters['output']) || isset($this->smarty->registered_filters['output'])) {
+               $output = Smarty_Internal_Filter_Handler::runFilter('output', $output, $this);
+               }
+            // rendering (must be done before writing cache file because of {function} nocache handling)
+            $_smarty_tpl = $this;
+            ob_start();
+            eval("?>" . $output);
+            $this->rendered_content = ob_get_clean(); 
+            // write cache file content
+            $this->writeCachedContent('<?php if (!$no_render) {?>'. $output. '<?php } ?>');
+            if ($this->smarty->debugging) {
+                Smarty_Internal_Debug::end_cache($this);
+            } 
+        } else {
+            // var_dump('renderTemplate', $this->has_nocache_code, $this->template_resource, $this->properties['nocache_hash'], $this->parent->properties['nocache_hash'], $this->rendered_content);
+            if ($this->has_nocache_code && !empty($this->properties['nocache_hash']) && !empty($this->parent->properties['nocache_hash'])) {
+                // replace nocache_hash
+                $this->rendered_content = preg_replace("/{$this->properties['nocache_hash']}/", $this->parent->properties['nocache_hash'], $this->rendered_content);
+                $this->parent->has_nocache_code = $this->has_nocache_code;
+            } 
+        } 
+    } 
+
+    /**
+     * Returns the rendered HTML output 
+     * 
+     * If the cache is valid the cached content is used, otherwise
+     * the output is rendered from the compiled template or PHP template source
+     * 
+     * @return string rendered HTML output
+     */
+    public function getRenderedTemplate ()
+    { 
+        // disable caching for evaluated code
+        if ($this->resource_object->isEvaluated) {
+            $this->caching = false;
+        } 
+        // checks if template exists
+        $this->isExisting(true); 
+        // read from cache or render
+        if ($this->rendered_content === null) {
+               if ($this->isCached) {
+                       if ($this->smarty->debugging) {
+               Smarty_Internal_Debug::start_cache($this);
+            } 
+            $this->rendered_content = $this->cache_resource_object->getCachedContents($this, false);
+            if ($this->smarty->debugging) {
+               Smarty_Internal_Debug::end_cache($this);
+            }
+          } 
+          if ($this->isCached === null) { 
+            $this->isCached(false); 
+          }
+          if (!$this->isCached) {          
+            // render template (not loaded and not in cache)
+            $this->renderTemplate();
+          }
+        } 
+        $this->updateParentVariables();
+        $this->isCached = null;
+        return $this->rendered_content;
+    } 
+
+    /**
+     * Parse a template resource in its name and type
+     * Load required resource handler
+     * 
+     * @param string $template_resource template resource specification
+     * @param string $resource_type return resource type
+     * @param string $resource_name return resource name
+     * @param object $resource_handler return resource handler object
+     */
+    public function parseResourceName($template_resource, &$resource_type, &$resource_name, &$resource_handler)
+    {
+        if (empty($template_resource))
+            return false;
+        $this->getResourceTypeName($template_resource, $resource_type, $resource_name);
+        $resource_handler = $this->loadTemplateResourceHandler($resource_type); 
+        // cache template object under a unique ID
+        // do not cache eval resources
+        if ($resource_type != 'eval') {
+            $this->smarty->template_objects[sha1($this->template_resource . $this->cache_id . $this->compile_id)] = $this;
+        } 
+        return true;
+    } 
+
+    /**
+     * get system filepath to template
+     */
+    public function buildTemplateFilepath ($file = null)
+    {
+        if ($file == null) {
+            $file = $this->resource_name;
+        }
+        // relative file name? 
+        if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
+               foreach((array)$this->smarty->template_dir as $_template_dir) {
+                       if (strpos('/\\', substr($_template_dir, -1)) === false) {
+                       $_template_dir .= DS;
+               } 
+               $_filepath = $_template_dir . $file;
+               if (file_exists($_filepath)) {
+                       return $_filepath;
+               }
+                       if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_template_dir)) {
+                               // try PHP include_path
+                               if (($_filepath = Smarty_Internal_Get_Include_Path::getIncludePath($_filepath)) !== false) {
+                                       return $_filepath;
+                               }
+                       }
+                       }
+               }
+        // try absolute filepath
+        if (file_exists($file)) return $file;
+        // no tpl file found
+        if (!empty($this->smarty->default_template_handler_func)) {
+            if (!is_callable($this->smarty->default_template_handler_func)) {
+                throw new SmartyException("Default template handler not callable");
+            } else {
+                $_return = call_user_func_array($this->smarty->default_template_handler_func,
+                    array($this->resource_type, $this->resource_name, &$this->template_source, &$this->template_timestamp, $this));
+                if (is_string($_return)) {
+                    return $_return;
+                } elseif ($_return === true) {
+                    return $file;
+                } 
+            } 
+        } 
+        return false;
+    } 
+
+    /**
+     * Update Smarty variables in other scopes
+     */
+    public function updateParentVariables ($scope = Smarty::SCOPE_LOCAL)
+    {
+        $has_root = false;
+        foreach ($this->tpl_vars as $_key => $_variable) {
+            $_variable_scope = $this->tpl_vars[$_key]->scope;
+            if ($scope == Smarty::SCOPE_LOCAL && $_variable_scope == Smarty::SCOPE_LOCAL) {
+                continue;
+            } 
+            if (isset($this->parent) && ($scope == Smarty::SCOPE_PARENT || $_variable_scope == Smarty::SCOPE_PARENT)) {
+                if (isset($this->parent->tpl_vars[$_key])) {
+                    // variable is already defined in parent, copy value
+                    $this->parent->tpl_vars[$_key]->value = $this->tpl_vars[$_key]->value;
+                } else {
+                    // create variable in parent
+                    $this->parent->tpl_vars[$_key] = clone $_variable;
+                    $this->parent->tpl_vars[$_key]->scope = Smarty::SCOPE_LOCAL;
+                } 
+            } 
+            if ($scope == Smarty::SCOPE_ROOT || $_variable_scope == Smarty::SCOPE_ROOT) {
+                if ($this->parent == null) {
+                    continue;
+                } 
+                if (!$has_root) {
+                    // find  root
+                    $root_ptr = $this;
+                    while ($root_ptr->parent != null) {
+                        $root_ptr = $root_ptr->parent;
+                        $has_root = true;
+                    } 
+                } 
+                if (isset($root_ptr->tpl_vars[$_key])) {
+                    // variable is already defined in root, copy value
+                    $root_ptr->tpl_vars[$_key]->value = $this->tpl_vars[$_key]->value;
+                } else {
+                    // create variable in root
+                    $root_ptr->tpl_vars[$_key] = clone $_variable;
+                    $root_ptr->tpl_vars[$_key]->scope = Smarty::SCOPE_LOCAL;
+                } 
+            } 
+            if ($scope == Smarty::SCOPE_GLOBAL || $_variable_scope == Smarty::SCOPE_GLOBAL) {
+                if (isset(Smarty::$global_tpl_vars[$_key])) {
+                    // variable is already defined in root, copy value
+                    Smarty::$global_tpl_vars[$_key]->value = $this->tpl_vars[$_key]->value;
+                } else {
+                    // create global variable
+                   Smarty::$global_tpl_vars[$_key] = clone $_variable;
+                } 
+               Smarty::$global_tpl_vars[$_key]->scope = Smarty::SCOPE_LOCAL;
+            } 
+        } 
+    } 
+
+    /**
+     * Split a template resource in its name and type
+     * 
+     * @param string $template_resource template resource specification
+     * @param string $resource_type return resource type
+     * @param string $resource_name return resource name
+     */
+    protected function getResourceTypeName ($template_resource, &$resource_type, &$resource_name)
+    {
+        if (strpos($template_resource, ':') === false) {
+            // no resource given, use default
+            $resource_type = $this->smarty->default_resource_type;
+            $resource_name = $template_resource;
+        } else {
+            // get type and name from path
+            list($resource_type, $resource_name) = explode(':', $template_resource, 2);
+            if (strlen($resource_type) == 1) {
+                // 1 char is not resource type, but part of filepath
+                $resource_type = 'file';
+                $resource_name = $template_resource;
+            }
+        } 
+    } 
+
+    /**
+     * Load template resource handler by type
+     * 
+     * @param string $resource_type template resource type
+     * @return object resource handler object
+     */
+    protected function loadTemplateResourceHandler ($resource_type)
+    { 
+        // try registered resource
+        if (isset($this->smarty->registered_resources[$resource_type])) {
+            return new Smarty_Internal_Resource_Registered($this);
+        } else {
+            // try sysplugins dir
+            if (in_array($resource_type, array('file', 'string', 'extends', 'php', 'stream', 'eval'))) {
+                $_resource_class = 'Smarty_Internal_Resource_' . ucfirst($resource_type);
+                return new $_resource_class($this->smarty);
+            } else {
+                // try plugins dir
+                $_resource_class = 'Smarty_Resource_' . ucfirst($resource_type);
+                if ($this->smarty->loadPlugin($_resource_class)) {
+                    if (class_exists($_resource_class, false)) {
+                        return new $_resource_class($this->smarty);
+                    } else {
+                        return new Smarty_Internal_Resource_Registered($this, $resource_type);
+                    } 
+                } else {
+                    // try streams
+                    $_known_stream = stream_get_wrappers();
+                    if (in_array($resource_type, $_known_stream)) {
+                        // is known stream
+                        if (is_object($this->smarty->security_policy)) {
+                            $this->smarty->security_policy->isTrustedStream($resource_type);
+                        } 
+                        return new Smarty_Internal_Resource_Stream($this->smarty);
+                    } else {
+                        throw new SmartyException('Unkown resource type \'' . $resource_type . '\'');
+                    } 
+                } 
+            } 
+        } 
+    } 
+
+    /**
+     * Create property header
+     */
+    public function createPropertyHeader ($cache = false)
+    {
+        $plugins_string = ''; 
+        // include code for plugins
+        if (!$cache) {
+            if (!empty($this->required_plugins['compiled'])) {
+                $plugins_string = '<?php ';
+                foreach($this->required_plugins['compiled'] as $tmp) {
+                    foreach($tmp as $data) {
+                        $plugins_string .= "if (!is_callable('{$data['function']}')) include '{$data['file']}';\n";
+                    } 
+                } 
+                $plugins_string .= '?>';
+            } 
+            if (!empty($this->required_plugins['nocache'])) {
+                $this->has_nocache_code = true;
+                $plugins_string .= "<?php echo '/*%%SmartyNocache:{$this->properties['nocache_hash']}%%*/<?php ";
+                foreach($this->required_plugins['nocache'] as $tmp) {
+                    foreach($tmp as $data) {
+                        $plugins_string .= "if (!is_callable(\'{$data['function']}\')) include \'{$data['file']}\';\n";
+                    } 
+                } 
+                $plugins_string .= "?>/*/%%SmartyNocache:{$this->properties['nocache_hash']}%%*/';?>\n";
+            } 
+        } 
+        // build property code
+        $this->properties['has_nocache_code'] = $this->has_nocache_code;
+        $properties_string = "<?php /*%%SmartyHeaderCode:{$this->properties['nocache_hash']}%%*/" ;
+        if ($this->smarty->direct_access_security) {
+            $properties_string .= "if(!defined('SMARTY_DIR')) exit('no direct access allowed');\n";
+        } 
+        if ($cache) {
+            // remove compiled code of{function} definition
+            unset($this->properties['function']);
+            if (!empty($this->smarty->template_functions)) {
+                // copy code of {function} tags called in nocache mode
+                foreach ($this->smarty->template_functions as $name => $function_data) {
+                    if (isset($function_data['called_nocache'])) {
+                        unset($function_data['called_nocache'], $this->smarty->template_functions[$name]['called_nocache']);
+                        $this->properties['function'][$name] = $function_data;
+                    } 
+                } 
+            } 
+        } 
+        $properties_string .= "\$_smarty_tpl->decodeProperties(" . var_export($this->properties, true) . "); /*/%%SmartyHeaderCode%%*/?>\n";
+        return $properties_string . $plugins_string;
+    } 
+
+    /**
+     * Decode saved properties from compiled template and cache files
+     */
+    public function decodeProperties ($properties)
+    {
+        $this->has_nocache_code = $properties['has_nocache_code'];
+        $this->properties['nocache_hash'] = $properties['nocache_hash'];
+        if (isset($properties['cache_lifetime'])) {
+            $this->properties['cache_lifetime'] = $properties['cache_lifetime'];
+        } 
+        if (isset($properties['file_dependency'])) {
+            $this->properties['file_dependency'] = array_merge($this->properties['file_dependency'], $properties['file_dependency']);
+        } 
+        if (!empty($properties['function'])) {
+            $this->properties['function'] = array_merge($this->properties['function'], $properties['function']);
+            $this->smarty->template_functions = array_merge($this->smarty->template_functions, $properties['function']);
+        } 
+    } 
+
+    /**
+     * creates a local Smarty variable for array assignments
+     */
+    public function createLocalArrayVariable($tpl_var, $nocache = false, $scope = Smarty::SCOPE_LOCAL)
+    {
+        if (!isset($this->tpl_vars[$tpl_var])) {
+            $tpl_var_inst = $this->getVariable($tpl_var, null, true, false);
+            if ($tpl_var_inst instanceof Undefined_Smarty_Variable) {
+                $this->tpl_vars[$tpl_var] = new Smarty_variable(array(), $nocache, $scope);
+            } else {
+                $this->tpl_vars[$tpl_var] = clone $tpl_var_inst;
+                if ($scope != Smarty::SCOPE_LOCAL) {
+                    $this->tpl_vars[$tpl_var]->scope = $scope;
+                } 
+            } 
+        } 
+        if (!(is_array($this->tpl_vars[$tpl_var]->value) || $this->tpl_vars[$tpl_var]->value instanceof ArrayAccess)) {
+            settype($this->tpl_vars[$tpl_var]->value, 'array');
+        } 
+    } 
+
+    /**
+     * [util function] counts an array, arrayaccess/traversable or PDOStatement object
+     * @param mixed $value
+     * @return int the count for arrays and objects that implement countable, 1 for other objects that don't, and 0 for empty elements
+     */
+    public function _count($value)
+    {
+        if (is_array($value) === true || $value instanceof Countable) {
+            return count($value);
+        } elseif ($value instanceof Iterator) {
+            $value->rewind();
+            if ($value->valid()) {
+                return iterator_count($value);
+            }
+        } elseif ($value instanceof PDOStatement) {
+            return $value->rowCount();
+        } elseif ($value instanceof Traversable) {
+            return iterator_count($value);
+        } elseif ($value instanceof ArrayAccess) {
+            if ($value->offsetExists(0)) {
+                return 1;
+            }
+       } elseif (is_object($value)) {
+            return count($value);
+       }
+       return 0;
+    }
+
+    /**
+     * wrapper for fetch
+     */
+    public function fetch ($template = null, $cache_id = null, $compile_id = null, $parent = null, $display = false)
+    {
+               if ($template == null) {
+               return $this->smarty->fetch($this);
+        } else {
+               if (!isset($parent)) {
+                       $parent = $this;
+               }
+               return $this->smarty->fetch($template, $cache_id, $compile_id, $parent, $display);
+        }
+        
+    } 
+     /**
+     * wrapper for display
+     */
+    public function display ($template = null, $cache_id = null, $compile_id = null, $parent = null)
+    {
+               if ($template == null) {
+               return $this->smarty->display($this);
+        } else {
+               if (!isset($parent)) {
+                       $parent = $this;
+               }
+                       return $this->smarty->display($template, $cache_id, $compile_id, $parent);
+        }
+       
+    } 
+
+    /**
+     * set Smarty property in template context      
+     * @param string $property_name property name
+     * @param mixed $value value
+     */
+    public function __set($property_name, $value)
+    {
+       if ($property_name == 'resource_object' || $property_name == 'cache_resource_object') {
+               $this->$property_name = $value;
+       } elseif (property_exists($this->smarty, $property_name)) {
+               $this->smarty->$property_name = $value;
+       } else {
+               throw new SmartyException("invalid template property '$property_name'.");
+        }
+    }
+
+    /**
+     * get Smarty property in template context      
+     * @param string $property_name property name
+     */
+    public function __get($property_name)
+    {
+       if ($property_name == 'resource_object') {
+               // load template resource
+               $this->resource_object = null;
+               if (!$this->parseResourceName ($this->template_resource, $this->resource_type, $this->resource_name, $this->resource_object)) {
+               throw new SmartyException ("Unable to parse resource name \"{$template_resource}\"");
+               }
+               return $this->resource_object;
+        }
+        if ($property_name == 'cache_resource_object') { 
+               // load cache resource
+            $this->cache_resource_object = $this->loadCacheResource();
+            return $this->cache_resource_object;
+       }
+       if (property_exists($this->smarty, $property_name)) {
+               return $this->smarty->$property_name;
+       } else {
+               throw new SmartyException("template property '$property_name' does not exist.");
+        }
+    }
+
+
+    /**
+     * Takes unknown class methods and lazy loads sysplugin files for them
+     * class name format: Smarty_Method_MethodName
+     * plugin filename format: method.methodname.php
+     * 
+     * @param string $name unknown methode name
+     * @param array $args aurgument array
+     */
+    public function __call($name, $args)
+    {
+        static $camel_func;
+        if (!isset($camel_func))
+            $camel_func = create_function('$c', 'return "_" . strtolower($c[1]);'); 
+        // see if this is a set/get for a property
+        $first3 = strtolower(substr($name, 0, 3));
+        if (in_array($first3, array('set', 'get')) && substr($name, 3, 1) !== '_') {
+            // try to keep case correct for future PHP 6.0 case-sensitive class methods
+            // lcfirst() not available < PHP 5.3.0, so improvise
+            $property_name = strtolower(substr($name, 3, 1)) . substr($name, 4); 
+            // convert camel case to underscored name
+            $property_name = preg_replace_callback('/([A-Z])/', $camel_func, $property_name);
+               if (property_exists($this, $property_name)) {
+               if ($first3 == 'get')
+                       return $this->$property_name;
+               else
+                       return $this->$property_name = $args[0];
+               }
+        }
+        // Smarty Backward Compatible wrapper
+               if (strpos($name,'_') !== false) {
+               if (!isset($this->wrapper)) {
+                $this->wrapper = new Smarty_Internal_Wrapper($this);
+               } 
+               return $this->wrapper->convert($name, $args);
+        }
+        // pass call to Smarty object  
+        return call_user_func_array(array($this->smarty,$name),$args);
+    } 
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php
new file mode 100644 (file)
index 0000000..2a95eca
--- /dev/null
@@ -0,0 +1,435 @@
+<?php
+/**
+ * Smarty Internal Plugin Smarty Template Compiler Base
+ * 
+ * This file contains the basic classes and methodes for compiling Smarty templates with lexer/parser
+ * 
+ * @package Smarty
+ * @subpackage Compiler
+ * @author Uwe Tews 
+ */
+
+/**
+ * Main compiler class
+ */
+class Smarty_Internal_TemplateCompilerBase {
+    // hash for nocache sections
+    private $nocache_hash = null; 
+    // suppress generation of nocache code
+    public $suppressNocacheProcessing = false; 
+    // compile tag objects
+    static $_tag_objects = array(); 
+    // tag stack
+    public $_tag_stack = array(); 
+    // current template
+    public $template = null;
+    // optional log of tag/attributes
+    public $used_tags = array();
+
+    /**
+     * Initialize compiler
+     */
+    public function __construct()
+    {
+        $this->nocache_hash = str_replace('.', '-', uniqid(rand(), true));
+    } 
+
+    /**
+     * Methode to compile a Smarty template
+     * 
+     * @param  $template template object to compile
+     * @return bool true if compiling succeeded, false if it failed
+     */
+    public function compileTemplate($template)
+    {
+        if (empty($template->properties['nocache_hash'])) {
+            $template->properties['nocache_hash'] = $this->nocache_hash;
+        } else {
+            $this->nocache_hash = $template->properties['nocache_hash'];
+        } 
+        // flag for nochache sections
+        $this->nocache = false;
+        $this->tag_nocache = false; 
+        // save template object in compiler class
+        $this->template = $template;
+        $this->smarty->_current_file = $this->template->getTemplateFilepath(); 
+        // template header code
+        $template_header = '';
+        if (!$template->suppressHeader) {
+            $template_header .= "<?php /* Smarty version " . Smarty::SMARTY_VERSION . ", created on " . strftime("%Y-%m-%d %H:%M:%S") . "\n";
+            $template_header .= "         compiled from \"" . $this->template->getTemplateFilepath() . "\" */ ?>\n";
+        } 
+
+        do {
+            // flag for aborting current and start recompile
+            $this->abort_and_recompile = false; 
+            // get template source
+            $_content = $template->getTemplateSource(); 
+            // run prefilter if required
+            if (isset($this->smarty->autoload_filters['pre']) || isset($this->smarty->registered_filters['pre'])) {
+                $template->template_source = $_content = Smarty_Internal_Filter_Handler::runFilter('pre', $_content, $template);
+            } 
+            // on empty template just return header
+            if ($_content == '') {
+                if ($template->suppressFileDependency) {
+                    $template->compiled_template = '';
+                } else {
+                    $template->compiled_template = $template_header . $template->createPropertyHeader();
+                } 
+                return true;
+            } 
+            // call compiler
+            $_compiled_code = $this->doCompile($_content);
+        } while ($this->abort_and_recompile); 
+        // return compiled code to template object
+        if ($template->suppressFileDependency) {
+            $template->compiled_template = $_compiled_code;
+        } else {
+            $template->compiled_template = $template_header . $template->createPropertyHeader() . $_compiled_code;
+        } 
+        // run postfilter if required
+        if (isset($this->smarty->autoload_filters['post']) || isset($this->smarty->registered_filters['post'])) {
+            $template->compiled_template = Smarty_Internal_Filter_Handler::runFilter('post', $template->compiled_template, $template);
+        } 
+    } 
+
+    /**
+     * Compile Tag
+     * 
+     * This is a call back from the lexer/parser
+     * It executes the required compile plugin for the Smarty tag
+     * 
+     * @param string $tag tag name
+     * @param array $args array with tag attributes
+     * @param array $parameter array with compilation parameter
+     * @return string compiled code
+     */
+    public function compileTag($tag, $args, $parameter = array())
+    { 
+        // $args contains the attributes parsed and compiled by the lexer/parser
+        // assume that tag does compile into code, but creates no HTML output
+        $this->has_code = true;
+        $this->has_output = false;
+        // log tag/attributes
+        if (isset($this->smarty->get_used_tags) && $this->smarty->get_used_tags) {
+               $this->used_tags[] = array($tag,$args);
+        } 
+               // check nocache option flag
+        if (in_array("'nocache'",$args) || in_array(array('nocache'=>'true'),$args)
+                       || in_array(array('nocache'=>'"true"'),$args) || in_array(array('nocache'=>"'true'"),$args)) {
+               $this->tag_nocache = true;
+        }
+        // compile the smarty tag (required compile classes to compile the tag are autoloaded)
+        if (($_output = $this->callTagCompiler($tag, $args, $parameter)) === false) {
+            if (isset($this->smarty->template_functions[$tag])) {
+                // template defined by {template} tag
+                $args['_attr']['name'] = "'" . $tag . "'";
+                $_output = $this->callTagCompiler('call', $args, $parameter);
+            } 
+        } 
+        if ($_output !== false) {
+            if ($_output !== true) {
+                // did we get compiled code
+                if ($this->has_code) {
+                    // Does it create output?
+                    if ($this->has_output) {
+                        $_output .= "\n";
+                    } 
+                    // return compiled code
+                    return $_output;
+                } 
+            } 
+            // tag did not produce compiled code
+            return '';
+        } else {
+            // map_named attributes
+            if (isset($args['_attr'])) {
+                foreach ($args['_attr'] as $key => $attribute) {
+                    if (is_array($attribute)) {
+                        $args = array_merge($args, $attribute);
+                    } 
+                } 
+            } 
+            // not an internal compiler tag
+            if (strlen($tag) < 6 || substr($tag, -5) != 'close') {
+                // check if tag is a registered object
+                if (isset($this->smarty->registered_objects[$tag]) && isset($parameter['object_methode'])) {
+                    $methode = $parameter['object_methode'];
+                    if (!in_array($methode, $this->smarty->registered_objects[$tag][3]) &&
+                            (empty($this->smarty->registered_objects[$tag][1]) || in_array($methode, $this->smarty->registered_objects[$tag][1]))) {
+                        return $this->callTagCompiler('private_object_function', $args, $parameter, $tag, $methode);
+                    } elseif (in_array($methode, $this->smarty->registered_objects[$tag][3])) {
+                        return $this->callTagCompiler('private_object_block_function', $args, $parameter, $tag, $methode);
+                    } else {
+                        return $this->trigger_template_error ('unallowed methode "' . $methode . '" in registered object "' . $tag . '"', $this->lex->taglineno);
+                    } 
+                } 
+                // check if tag is registered
+                foreach (array(Smarty::PLUGIN_COMPILER, Smarty::PLUGIN_FUNCTION, Smarty::PLUGIN_BLOCK) as $type) {
+                    if (isset($this->smarty->registered_plugins[$type][$tag])) {
+                        // if compiler function plugin call it now
+                        if ($type == Smarty::PLUGIN_COMPILER) {
+                            $new_args = array();
+                            foreach ($args as $mixed) {
+                                $new_args = array_merge($new_args, $mixed);
+                            } 
+                            if (!$this->smarty->registered_plugins[$type][$tag][1]) {
+                                $this->tag_nocache = true;
+                            } 
+                            $function = $this->smarty->registered_plugins[$type][$tag][0];
+                            if (!is_array($function)) {
+                                return $function($new_args, $this);
+                            } else if (is_object($function[0])) {
+                                return $this->smarty->registered_plugins[$type][$tag][0][0]->$function[1]($new_args, $this);
+                            } else {
+                                return call_user_func_array($this->smarty->registered_plugins[$type][$tag][0], array($new_args, $this));
+                            } 
+                        } 
+                        // compile registered function or block function
+                        if ($type == Smarty::PLUGIN_FUNCTION || $type == Smarty::PLUGIN_BLOCK) {
+                            return $this->callTagCompiler('private_registered_' . $type, $args, $parameter, $tag);
+                        } 
+                    } 
+                } 
+                // check plugins from plugins folder
+                foreach ($this->smarty->plugin_search_order as $plugin_type) {
+                    if ($plugin_type == Smarty::PLUGIN_BLOCK && $this->smarty->loadPlugin('smarty_compiler_' . $tag)) {
+                        $plugin = 'smarty_compiler_' . $tag;
+                        if (is_callable($plugin)) {
+                               // convert arguments format for old compiler plugins
+                            $new_args = array();
+                            foreach ($args as $mixed) {
+                                $new_args = array_merge($new_args, $mixed);
+                            } 
+                            return $plugin($new_args, $this->smarty);
+                        } 
+                        if (class_exists($plugin, false)) {
+                            $plugin_object = new $plugin;
+                            if (method_exists($plugin_object, 'compile')) {
+                                return $plugin_object->compile($args, $this);
+                            } 
+                        } 
+                        throw new SmartyException("Plugin \"{$tag}\" not callable");
+                    } else {
+                        if ($function = $this->getPlugin($tag, $plugin_type)) {
+                            return $this->callTagCompiler('private_' . $plugin_type . '_plugin', $args, $parameter, $tag, $function);
+                        } 
+                    } 
+                } 
+            } else {
+                // compile closing tag of block function
+                $base_tag = substr($tag, 0, -5); 
+                // check if closing tag is a registered object
+                if (isset($this->smarty->registered_objects[$base_tag]) && isset($parameter['object_methode'])) {
+                    $methode = $parameter['object_methode'];
+                    if (in_array($methode, $this->smarty->registered_objects[$base_tag][3])) {
+                        return $this->callTagCompiler('private_object_block_function', $args, $parameter, $tag, $methode);
+                    } else {
+                        return $this->trigger_template_error ('unallowed closing tag methode "' . $methode . '" in registered object "' . $base_tag . '"', $this->lex->taglineno);
+                    } 
+                } 
+                // registered block tag ?
+                if (isset($this->smarty->registered_plugins[Smarty::PLUGIN_BLOCK][$base_tag])) {
+                    return $this->callTagCompiler('private_registered_block', $args, $parameter, $tag);
+                } 
+                // block plugin?
+                if ($function = $this->getPlugin($base_tag, Smarty::PLUGIN_BLOCK)) {
+                    return $this->callTagCompiler('private_block_plugin', $args, $parameter, $tag, $function);
+                } 
+                if ($this->smarty->loadPlugin('smarty_compiler_' . $tag)) {
+                    $plugin = 'smarty_compiler_' . $tag;
+                    if (is_callable($plugin)) {
+                        return $plugin($args, $this->smarty);
+                    } 
+                    if (class_exists($plugin, false)) {
+                        $plugin_object = new $plugin;
+                        if (method_exists($plugin_object, 'compile')) {
+                            return $plugin_object->compile($args, $this);
+                        } 
+                    } 
+                    throw new SmartyException("Plugin \"{$tag}\" not callable");
+                } 
+            } 
+            $this->trigger_template_error ("unknown tag \"" . $tag . "\"", $this->lex->taglineno);
+        } 
+    } 
+
+    /**
+     * lazy loads internal compile plugin for tag and calls the compile methode
+     * 
+     * compile objects cached for reuse.
+     * class name format:  Smarty_Internal_Compile_TagName
+     * plugin filename format: Smarty_Internal_Tagname.php
+     * 
+     * @param  $tag string tag name
+     * @param  $args array with tag attributes
+     * @param  $param1 optional parameter
+     * @param  $param2 optional parameter
+     * @param  $param3 optional parameter
+     * @return string compiled code
+     */
+    public function callTagCompiler($tag, $args, $param1 = null, $param2 = null, $param3 = null)
+    { 
+        // re-use object if already exists
+        if (isset(self::$_tag_objects[$tag])) {
+            // compile this tag
+            return self::$_tag_objects[$tag]->compile($args, $this, $param1, $param2, $param3);
+        } 
+        // lazy load internal compiler plugin
+        $class_name = 'Smarty_Internal_Compile_' . $tag;
+        if ($this->smarty->loadPlugin($class_name)) {
+            // use plugin if found
+            self::$_tag_objects[$tag] = new $class_name; 
+            // compile this tag
+            return self::$_tag_objects[$tag]->compile($args, $this, $param1, $param2, $param3);
+        } 
+        // no internal compile plugin for this tag
+        return false;
+    } 
+
+    /**
+     * Check for plugins and return function name
+     * 
+     * @param  $pugin_name string name of plugin or function
+     * @param  $type string type of plugin
+     * @return string call name of function
+     */
+    public function getPlugin($plugin_name, $type)
+    {
+        $function = null;
+        if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {
+            if (isset($this->template->required_plugins['nocache'][$plugin_name][$type])) {
+                $function = $this->template->required_plugins['nocache'][$plugin_name][$type]['function'];
+            } else if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {
+                $this->template->required_plugins['nocache'][$plugin_name][$type] = $this->template->required_plugins['compiled'][$plugin_name][$type];
+                $function = $this->template->required_plugins['nocache'][$plugin_name][$type]['function'];
+            } 
+        } else {
+            if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {
+                $function = $this->template->required_plugins['compiled'][$plugin_name][$type]['function'];
+            } else if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {
+                $this->template->required_plugins['compiled'][$plugin_name][$type] = $this->template->required_plugins['nocache'][$plugin_name][$type];
+                $function = $this->template->required_plugins['compiled'][$plugin_name][$type]['function'];
+            } 
+        } 
+        if (isset($function)) {
+            if ($type == 'modifier') {
+                $this->template->saved_modifier[$plugin_name] = true;
+            } 
+            return $function;
+        } 
+        // loop through plugin dirs and find the plugin
+        $function = 'smarty_' . $type . '_' . $plugin_name;
+        $found = false;
+        foreach((array)$this->smarty->plugins_dir as $_plugin_dir) {
+            $file = rtrim($_plugin_dir, '/\\') . DS . $type . '.' . $plugin_name . '.php';
+            if (file_exists($file)) {
+                // require_once($file);
+                $found = true;
+                break;
+            } 
+        } 
+        if ($found) {
+            if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {
+                $this->template->required_plugins['nocache'][$plugin_name][$type]['file'] = $file;
+                $this->template->required_plugins['nocache'][$plugin_name][$type]['function'] = $function;
+            } else {
+                $this->template->required_plugins['compiled'][$plugin_name][$type]['file'] = $file;
+                $this->template->required_plugins['compiled'][$plugin_name][$type]['function'] = $function;
+            } 
+            if ($type == 'modifier') {
+                $this->template->saved_modifier[$plugin_name] = true;
+            } 
+            return $function;
+        } 
+        if (is_callable($function)) {
+            // plugin function is defined in the script
+            return $function;
+        } 
+        return false;
+    } 
+    /**
+     * Inject inline code for nocache template sections
+     * 
+     * This method gets the content of each template element from the parser.
+     * If the content is compiled code and it should be not cached the code is injected
+     * into the rendered output.
+     * 
+     * @param string $content content of template element
+     * @param boolean $tag_nocache true if the parser detected a nocache situation
+     * @param boolean $is_code true if content is compiled code
+     * @return string content
+     */
+    public function processNocacheCode ($content, $is_code)
+    { 
+        // If the template is not evaluated and we have a nocache section and or a nocache tag
+        if ($is_code && !empty($content)) {
+            // generate replacement code
+            if ((!$this->template->resource_object->isEvaluated || $this->template->forceNocache) && $this->template->caching && !$this->suppressNocacheProcessing &&
+                    ($this->nocache || $this->tag_nocache || $this->template->forceNocache == 2)) {
+                $this->template->has_nocache_code = true;
+                $_output = str_replace("'", "\'", $content);
+                $_output = str_replace("^#^", "'", $_output);
+                $_output = "<?php echo '/*%%SmartyNocache:{$this->nocache_hash}%%*/" . $_output . "/*/%%SmartyNocache:{$this->nocache_hash}%%*/';?>"; 
+                // make sure we include modifer plugins for nocache code
+                if (isset($this->template->saved_modifier)) {
+                    foreach ($this->template->saved_modifier as $plugin_name => $dummy) {
+                        if (isset($this->template->required_plugins['compiled'][$plugin_name]['modifier'])) {
+                            $this->template->required_plugins['nocache'][$plugin_name]['modifier'] = $this->template->required_plugins['compiled'][$plugin_name]['modifier'];
+                        } 
+                    } 
+                    $this->template->saved_modifier = null;
+                } 
+            } else {
+                $_output = $content;
+            } 
+        } else {
+            $_output = $content;
+        } 
+        $this->suppressNocacheProcessing = false;
+        $this->tag_nocache = false;
+        return $_output;
+    } 
+    /**
+     * display compiler error messages without dying
+     * 
+     * If parameter $args is empty it is a parser detected syntax error.
+     * In this case the parser is called to obtain information about expected tokens.
+     * 
+     * If parameter $args contains a string this is used as error message
+     * 
+     * @param  $args string individual error message or null
+     */
+    public function trigger_template_error($args = null, $line = null)
+    { 
+        // get template source line which has error
+        if (!isset($line)) {
+            $line = $this->lex->line;
+        } 
+        $match = preg_split("/\n/", $this->lex->data);
+        $error_text = 'Syntax Error in template "' . $this->template->getTemplateFilepath() . '"  on line ' . $line . ' "' . htmlspecialchars(trim(preg_replace('![\t\r\n]+!',' ',$match[$line-1]))) . '" ';
+        if (isset($args)) {
+            // individual error message
+            $error_text .= $args;
+        } else {
+            // expected token from parser
+            $error_text .= ' - Unexpected "' . $this->lex->value.'"';
+            if (count($this->parser->yy_get_expected_tokens($this->parser->yymajor)) <= 4 ) {
+               foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
+                   $exp_token = $this->parser->yyTokenName[$token];
+                   if (isset($this->lex->smarty_token_names[$exp_token])) {
+                       // token type from lexer
+                       $expect[] = '"' . $this->lex->smarty_token_names[$exp_token] . '"';
+                   } else {
+                       // otherwise internal token name
+                       $expect[] = $this->parser->yyTokenName[$token];
+                   } 
+               } 
+               $error_text .= ', expected one of: ' . implode(' , ', $expect);
+               }
+        } 
+        throw new SmartyCompilerException($error_text);
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templatelexer.php
new file mode 100644 (file)
index 0000000..5e0e496
--- /dev/null
@@ -0,0 +1,1182 @@
+<?php
+/**
+* Smarty Internal Plugin Templatelexer
+*
+* This is the lexer to break the template source into tokens 
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews 
+*/
+/**
+* Smarty Internal Plugin Templatelexer
+*/
+class Smarty_Internal_Templatelexer
+{
+    public $data;
+    public $counter;
+    public $token;
+    public $value;
+    public $node;
+    public $line;
+    public $taglineno;
+    public $state = 1;
+    public $strip = false;
+    private $heredoc_id_stack = Array();
+    public $smarty_token_names = array (               // Text for parser error messages
+                               'IDENTITY'      => '===',
+                               'NONEIDENTITY'  => '!==',
+                               'EQUALS'        => '==',
+                               'NOTEQUALS'     => '!=',
+                               'GREATEREQUAL' => '(>=,ge)',
+                               'LESSEQUAL' => '(<=,le)',
+                               'GREATERTHAN' => '(>,gt)',
+                               'LESSTHAN' => '(<,lt)',
+                               'MOD' => '(%,mod)',
+                               'NOT'                   => '(!,not)',
+                               'LAND'          => '(&&,and)',
+                               'LOR'                   => '(||,or)',
+                               'LXOR'                  => 'xor',
+                               'OPENP'         => '(',
+                               'CLOSEP'        => ')',
+                               'OPENB'         => '[',
+                               'CLOSEB'        => ']',
+                               'PTR'                   => '->',
+                               'APTR'          => '=>',
+                               'EQUAL'         => '=',
+                               'NUMBER'        => 'number',
+                               'UNIMATH'       => '+" , "-',
+                               'MATH'          => '*" , "/" , "%',
+                               'INCDEC'        => '++" , "--',
+                               'SPACE'         => ' ',
+                               'DOLLAR'        => '$',
+                               'SEMICOLON' => ';',
+                               'COLON'         => ':',
+                               'DOUBLECOLON'           => '::',
+                               'AT'            => '@',
+                               'HATCH'         => '#',
+                               'QUOTE'         => '"',
+                               'BACKTICK'              => '`',
+                               'VERT'          => '|',
+                               'DOT'                   => '.',
+                               'COMMA'         => '","',
+                               'ANDSYM'                => '"&"',
+                               'QMARK'         => '"?"',
+                               'ID'                    => 'identifier',
+                               'OTHER'         => 'text',
+                               'LINEBREAK'             => 'newline',
+                               'FAKEPHPSTARTTAG'       => 'Fake PHP start tag',
+                               'PHPSTARTTAG'   => 'PHP start tag',
+                               'PHPENDTAG'     => 'PHP end tag',
+                                               'LITERALSTART'  => 'Literal start',
+                                               'LITERALEND'    => 'Literal end',
+                               'LDELSLASH' => 'closing tag',
+                               'COMMENT' => 'comment',
+                               'LITERALEND' => 'literal close',
+                               'AS' => 'as',
+                               'TO' => 'to',
+                               );
+                               
+                               
+    function __construct($data,$compiler)
+    {
+//        $this->data = preg_replace("/(\r\n|\r|\n)/", "\n", $data);
+        $this->data = $data;
+        $this->counter = 0;
+        $this->line = 1;
+        $this->smarty = $compiler->smarty;
+        $this->compiler = $compiler;
+        $this->ldel = preg_quote($this->smarty->left_delimiter,'/'); 
+        $this->ldel_length = strlen($this->smarty->left_delimiter); 
+        $this->rdel = preg_quote($this->smarty->right_delimiter,'/');
+        $this->smarty_token_names['LDEL'] =    $this->smarty->left_delimiter;
+        $this->smarty_token_names['RDEL'] =    $this->smarty->right_delimiter;
+     }
+
+
+    private $_yy_state = 1;
+    private $_yy_stack = array();
+
+    function yylex()
+    {
+        return $this->{'yylex' . $this->_yy_state}();
+    }
+
+    function yypushstate($state)
+    {
+        array_push($this->_yy_stack, $this->_yy_state);
+        $this->_yy_state = $state;
+    }
+
+    function yypopstate()
+    {
+        $this->_yy_state = array_pop($this->_yy_stack);
+    }
+
+    function yybegin($state)
+    {
+        $this->_yy_state = $state;
+    }
+
+
+
+    function yylex1()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 1,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+              8 => 0,
+              9 => 0,
+              10 => 0,
+              11 => 0,
+              12 => 1,
+              14 => 0,
+              15 => 0,
+              16 => 0,
+              17 => 0,
+              18 => 0,
+              19 => 0,
+              20 => 0,
+              21 => 0,
+              22 => 0,
+              23 => 2,
+              26 => 0,
+              27 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(".$this->ldel."[$]smarty\\.block\\.child".$this->rdel.")|^(\\{\\})|^(".$this->ldel."\\*([\S\s]*?)\\*".$this->rdel.")|^([\t ]*[\r\n]+[\t ]*)|^(".$this->ldel."strip".$this->rdel.")|^(".$this->ldel."\\s{1,}strip\\s{1,}".$this->rdel.")|^(".$this->ldel."\/strip".$this->rdel.")|^(".$this->ldel."\\s{1,}\/strip\\s{1,}".$this->rdel.")|^(".$this->ldel."\\s*literal\\s*".$this->rdel.")|^(".$this->ldel."\\s{1,}\/)|^(".$this->ldel."\\s*(if|elseif|else if|while)(?![^\s]))|^(".$this->ldel."\\s*for(?![^\s]))|^(".$this->ldel."\\s*foreach(?![^\s]))|^(".$this->ldel."\\s{1,})|^(".$this->ldel."\/)|^(".$this->ldel.")|^(<\\?(?:php\\w+|=|[a-zA-Z]+)?)|^(\\?>)|^(<%)|^(%>)|^(([\S\s]*?)(?=([\t ]*[\r\n]+[\t ]*|".$this->ldel."|<\\?|\\?>|<%|%>)))|^([\S\s]+)|^(.)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state TEXT');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r1_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const TEXT = 1;
+    function yy_r1_1($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_SMARTYBLOCKCHILD;
+    }
+    function yy_r1_2($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r1_3($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_COMMENT;
+    }
+    function yy_r1_5($yy_subpatterns)
+    {
+
+  if ($this->strip) {
+     return false;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LINEBREAK;
+  }
+    }
+    function yy_r1_6($yy_subpatterns)
+    {
+
+  $this->strip = true;
+  return false;
+    }
+    function yy_r1_7($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+    $this->strip = true;
+    return false;
+  }
+    }
+    function yy_r1_8($yy_subpatterns)
+    {
+
+  $this->strip = false;
+  return false;
+    }
+    function yy_r1_9($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+    $this->strip = false;
+    return false;
+  }
+    }
+    function yy_r1_10($yy_subpatterns)
+    {
+
+   $this->token = Smarty_Internal_Templateparser::TP_LITERALSTART;
+   $this->yypushstate(self::LITERAL);
+    }
+    function yy_r1_11($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r1_12($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELIF;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r1_14($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOR;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r1_15($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOREACH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r1_16($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r1_17($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r1_18($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r1_19($yy_subpatterns)
+    {
+
+  if (in_array($this->value, Array('<?', '<?=', '<?php'))) {
+    $this->token = Smarty_Internal_Templateparser::TP_PHPSTARTTAG;
+  } elseif ($this->value == '<?xml') {
+      $this->token = Smarty_Internal_Templateparser::TP_XMLTAG;
+  } else {
+    $this->token = Smarty_Internal_Templateparser::TP_FAKEPHPSTARTTAG;
+    $this->value = substr($this->value, 0, 2);
+  }
+     }
+    function yy_r1_20($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_PHPENDTAG;
+    }
+    function yy_r1_21($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ASPSTARTTAG;
+    }
+    function yy_r1_22($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ASPENDTAG;
+    }
+    function yy_r1_23($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r1_26($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r1_27($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+
+
+    function yylex2()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 1,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+              8 => 0,
+              9 => 0,
+              10 => 0,
+              11 => 0,
+              12 => 0,
+              13 => 0,
+              14 => 0,
+              15 => 0,
+              16 => 0,
+              17 => 0,
+              18 => 0,
+              19 => 0,
+              20 => 1,
+              22 => 1,
+              24 => 1,
+              26 => 0,
+              27 => 0,
+              28 => 0,
+              29 => 0,
+              30 => 0,
+              31 => 0,
+              32 => 0,
+              33 => 0,
+              34 => 0,
+              35 => 0,
+              36 => 0,
+              37 => 0,
+              38 => 0,
+              39 => 0,
+              40 => 0,
+              41 => 0,
+              42 => 0,
+              43 => 3,
+              47 => 0,
+              48 => 0,
+              49 => 0,
+              50 => 0,
+              51 => 0,
+              52 => 0,
+              53 => 0,
+              54 => 0,
+              55 => 1,
+              57 => 1,
+              59 => 0,
+              60 => 0,
+              61 => 0,
+              62 => 0,
+              63 => 0,
+              64 => 0,
+              65 => 0,
+              66 => 0,
+              67 => 0,
+              68 => 0,
+              69 => 0,
+              70 => 0,
+              71 => 0,
+              72 => 0,
+              73 => 0,
+              74 => 0,
+              75 => 0,
+              76 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|^(".$this->ldel."\\s{1,}\/)|^(".$this->ldel."\\s*(if|elseif|else if|while)(?![^\s]))|^(".$this->ldel."\\s*for(?![^\s]))|^(".$this->ldel."\\s*foreach(?![^\s]))|^(".$this->ldel."\\s{1,})|^(\\s{1,}".$this->rdel.")|^(".$this->ldel."\/)|^(".$this->ldel.")|^(".$this->rdel.")|^(\\s+is\\s+in\\s+)|^(\\s+as\\s+)|^(\\s+to\\s+)|^(\\s+step\\s+)|^(\\s+instanceof\\s+)|^(\\s*===\\s*)|^(\\s*!==\\s*)|^(\\s*==\\s*|\\s+eq\\s+)|^(\\s*!=\\s*|\\s*<>\\s*|\\s+(ne|neq)\\s+)|^(\\s*>=\\s*|\\s+(ge|gte)\\s+)|^(\\s*<=\\s*|\\s+(le|lte)\\s+)|^(\\s*>\\s*|\\s+gt\\s+)|^(\\s*<\\s*|\\s+lt\\s+)|^(\\s+mod\\s+)|^(!\\s*|not\\s+)|^(\\s*&&\\s*|\\s*and\\s+)|^(\\s*\\|\\|\\s*|\\s*or\\s+)|^(\\s*xor\\s+)|^(\\s+is\\s+odd\\s+by\\s+)|^(\\s+is\\s+not\\s+odd\\s+by\\s+)|^(\\s+is\\s+odd)|^(\\s+is\\s+not\\s+odd)|^(\\s+is\\s+even\\s+by\\s+)|^(\\s+is\\s+not\\s+even\\s+by\\s+)|^(\\s+is\\s+even)|^(\\s+is\\s+not\\s+even)|^(\\s+is\\s+div\\s+by\\s+)|^(\\s+is\\s+not\\s+div\\s+by\\s+)|^(\\((int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)\\)\\s*)|^(\\(\\s*)|^(\\s*\\))|^(\\[\\s*)|^(\\s*\\])|^(\\s*->\\s*)|^(\\s*=>\\s*)|^(\\s*=\\s*)|^(\\+\\+|--)|^(\\s*(\\+|-)\\s*)|^(\\s*(\\*|\/|%)\\s*)|^(\\$)|^(\\s*;)|^(::)|^(\\s*:\\s*)|^(@)|^(#)|^(\")|^(`)|^(\\|)|^(\\.)|^(\\s*,\\s*)|^(\\s*&\\s*)|^(\\s*\\?\\s*)|^(0[xX][0-9a-fA-F]+)|^([0-9]*[a-zA-Z_]\\w*)|^(\\d+)|^(\\s+)|^(.)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state SMARTY');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r2_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const SMARTY = 2;
+    function yy_r2_1($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_SINGLEQUOTESTRING;
+    }
+    function yy_r2_2($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r2_3($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELIF;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r2_5($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOR;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r2_6($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOREACH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r2_7($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r2_8($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_RDEL;
+     $this->yypopstate();
+  }
+    }
+    function yy_r2_9($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r2_10($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r2_11($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_RDEL;
+     $this->yypopstate();
+    }
+    function yy_r2_12($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISIN;
+    }
+    function yy_r2_13($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_AS;
+    }
+    function yy_r2_14($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_TO;
+    }
+    function yy_r2_15($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_STEP;
+    }
+    function yy_r2_16($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_INSTANCEOF;
+    }
+    function yy_r2_17($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_IDENTITY;
+    }
+    function yy_r2_18($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_NONEIDENTITY;
+    }
+    function yy_r2_19($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_EQUALS;
+    }
+    function yy_r2_20($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_NOTEQUALS;
+    }
+    function yy_r2_22($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_GREATEREQUAL;
+    }
+    function yy_r2_24($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LESSEQUAL;
+    }
+    function yy_r2_26($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_GREATERTHAN;
+    }
+    function yy_r2_27($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LESSTHAN;
+    }
+    function yy_r2_28($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_MOD;
+    }
+    function yy_r2_29($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_NOT;
+    }
+    function yy_r2_30($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LAND;
+    }
+    function yy_r2_31($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LOR;
+    }
+    function yy_r2_32($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LXOR;
+    }
+    function yy_r2_33($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISODDBY;
+    }
+    function yy_r2_34($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISNOTODDBY;
+    }
+    function yy_r2_35($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISODD;
+    }
+    function yy_r2_36($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISNOTODD;
+    }
+    function yy_r2_37($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISEVENBY;
+    }
+    function yy_r2_38($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISNOTEVENBY;
+    }
+    function yy_r2_39($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISEVEN;
+    }
+    function yy_r2_40($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISNOTEVEN;
+    }
+    function yy_r2_41($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISDIVBY;
+    }
+    function yy_r2_42($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ISNOTDIVBY;
+    }
+    function yy_r2_43($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_TYPECAST;
+    }
+    function yy_r2_47($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OPENP;
+    }
+    function yy_r2_48($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_CLOSEP;
+    }
+    function yy_r2_49($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OPENB;
+    }
+    function yy_r2_50($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_CLOSEB;
+    }
+    function yy_r2_51($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_PTR; 
+    }
+    function yy_r2_52($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_APTR;
+    }
+    function yy_r2_53($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_EQUAL;
+    }
+    function yy_r2_54($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_INCDEC;
+    }
+    function yy_r2_55($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_UNIMATH;
+    }
+    function yy_r2_57($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_MATH;
+    }
+    function yy_r2_59($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_DOLLAR;
+    }
+    function yy_r2_60($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_SEMICOLON;
+    }
+    function yy_r2_61($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_DOUBLECOLON;
+    }
+    function yy_r2_62($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_COLON;
+    }
+    function yy_r2_63($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_AT;
+    }
+    function yy_r2_64($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_HATCH;
+    }
+    function yy_r2_65($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_QUOTE;
+  $this->yypushstate(self::DOUBLEQUOTEDSTRING);
+    }
+    function yy_r2_66($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_BACKTICK;
+  $this->yypopstate();
+    }
+    function yy_r2_67($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_VERT;
+    }
+    function yy_r2_68($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_DOT;
+    }
+    function yy_r2_69($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_COMMA;
+    }
+    function yy_r2_70($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ANDSYM;
+    }
+    function yy_r2_71($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_QMARK;
+    }
+    function yy_r2_72($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_HEX;
+    }
+    function yy_r2_73($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ID;
+    }
+    function yy_r2_74($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_INTEGER;
+    }
+    function yy_r2_75($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_SPACE;
+    }
+    function yy_r2_76($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+
+
+
+    function yylex3()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 0,
+              3 => 0,
+              4 => 0,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+              8 => 2,
+              11 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(".$this->ldel."\\s*literal\\s*".$this->rdel.")|^(".$this->ldel."\\s*\/literal\\s*".$this->rdel.")|^([\t ]*[\r\n]+[\t ]*)|^(<\\?(?:php\\w+|=|[a-zA-Z]+)?)|^(\\?>)|^(<%)|^(%>)|^(([\S\s]*?)(?=([\t ]*[\r\n]+[\t ]*|".$this->ldel."\/?literal".$this->rdel."|<\\?|<%)))|^(.)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state LITERAL');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r3_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const LITERAL = 3;
+    function yy_r3_1($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LITERALSTART;
+  $this->yypushstate(self::LITERAL);
+    }
+    function yy_r3_2($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LITERALEND;
+  $this->yypopstate();
+    }
+    function yy_r3_3($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LITERAL;
+    }
+    function yy_r3_4($yy_subpatterns)
+    {
+
+  if (in_array($this->value, Array('<?', '<?=', '<?php'))) {
+    $this->token = Smarty_Internal_Templateparser::TP_PHPSTARTTAG;
+   } else {
+    $this->token = Smarty_Internal_Templateparser::TP_FAKEPHPSTARTTAG;
+    $this->value = substr($this->value, 0, 2);
+   }
+    }
+    function yy_r3_5($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_PHPENDTAG;
+    }
+    function yy_r3_6($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ASPSTARTTAG;
+    }
+    function yy_r3_7($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_ASPENDTAG;
+    }
+    function yy_r3_8($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LITERAL;
+    }
+    function yy_r3_11($yy_subpatterns)
+    {
+
+  $this->compiler->trigger_template_error ("missing or misspelled literal closing tag");
+    }
+
+
+    function yylex4()
+    {
+        $tokenMap = array (
+              1 => 0,
+              2 => 1,
+              4 => 0,
+              5 => 0,
+              6 => 0,
+              7 => 0,
+              8 => 0,
+              9 => 0,
+              10 => 0,
+              11 => 0,
+              12 => 0,
+              13 => 3,
+              17 => 0,
+              18 => 0,
+            );
+        if ($this->counter >= strlen($this->data)) {
+            return false; // end of input
+        }
+        $yy_global_pattern = "/^(".$this->ldel."\\s{1,}\/)|^(".$this->ldel."\\s*(if|elseif|else if|while)(?![^\s]))|^(".$this->ldel."\\s*for(?![^\s]))|^(".$this->ldel."\\s*foreach(?![^\s]))|^(".$this->ldel."\\s{1,})|^(".$this->ldel."\/)|^(".$this->ldel.")|^(\")|^(`\\$)|^(\\$[0-9]*[a-zA-Z_]\\w*)|^(\\$)|^(([^\"\\\\]*?)((?:\\\\.[^\"\\\\]*?)*?)(?=(".$this->ldel."|\\$|`\\$|\")))|^([\S\s]+)|^(.)/iS";
+
+        do {
+            if (preg_match($yy_global_pattern, substr($this->data, $this->counter), $yymatches)) {
+                $yysubmatches = $yymatches;
+                $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+                if (!count($yymatches)) {
+                    throw new Exception('Error: lexing failed because a rule matched' .
+                        'an empty string.  Input "' . substr($this->data,
+                        $this->counter, 5) . '... state DOUBLEQUOTEDSTRING');
+                }
+                next($yymatches); // skip global match
+                $this->token = key($yymatches); // token number
+                if ($tokenMap[$this->token]) {
+                    // extract sub-patterns for passing to lex function
+                    $yysubmatches = array_slice($yysubmatches, $this->token + 1,
+                        $tokenMap[$this->token]);
+                } else {
+                    $yysubmatches = array();
+                }
+                $this->value = current($yymatches); // token value
+                $r = $this->{'yy_r4_' . $this->token}($yysubmatches);
+                if ($r === null) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    // accept this token
+                    return true;
+                } elseif ($r === true) {
+                    // we have changed state
+                    // process this token in the new state
+                    return $this->yylex();
+                } elseif ($r === false) {
+                    $this->counter += strlen($this->value);
+                    $this->line += substr_count($this->value, "\n");
+                    if ($this->counter >= strlen($this->data)) {
+                        return false; // end of input
+                    }
+                    // skip this token
+                    continue;
+                }            } else {
+                throw new Exception('Unexpected input at line' . $this->line .
+                    ': ' . $this->data[$this->counter]);
+            }
+            break;
+        } while (true);
+
+    } // end function
+
+
+    const DOUBLEQUOTEDSTRING = 4;
+    function yy_r4_1($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r4_2($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELIF;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r4_4($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOR;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r4_5($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal && trim(substr($this->value,$this->ldel_length,1)) == '') {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDELFOREACH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r4_6($yy_subpatterns)
+    {
+
+  if ($this->smarty->auto_literal) {
+     $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+  } else {
+     $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+  }
+    }
+    function yy_r4_7($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDELSLASH;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r4_8($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_LDEL;
+     $this->yypushstate(self::SMARTY);
+     $this->taglineno = $this->line;
+    }
+    function yy_r4_9($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_QUOTE;
+  $this->yypopstate();
+    }
+    function yy_r4_10($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_BACKTICK;
+  $this->value = substr($this->value,0,-1);
+  $this->yypushstate(self::SMARTY);
+  $this->taglineno = $this->line;
+    }
+    function yy_r4_11($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_DOLLARID;
+    }
+    function yy_r4_12($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r4_13($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r4_17($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+    function yy_r4_18($yy_subpatterns)
+    {
+
+  $this->token = Smarty_Internal_Templateparser::TP_OTHER;
+    }
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_templateparser.php
new file mode 100644 (file)
index 0000000..90e1c97
--- /dev/null
@@ -0,0 +1,2966 @@
+<?php
+/**
+* Smarty Internal Plugin Templateparser
+*
+* This is the template parser.
+* It is generated from the internal.templateparser.y file
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+
+class TP_yyToken implements ArrayAccess
+{
+    public $string = '';
+    public $metadata = array();
+
+    function __construct($s, $m = array())
+    {
+        if ($s instanceof TP_yyToken) {
+            $this->string = $s->string;
+            $this->metadata = $s->metadata;
+        } else {
+            $this->string = (string) $s;
+            if ($m instanceof TP_yyToken) {
+                $this->metadata = $m->metadata;
+            } elseif (is_array($m)) {
+                $this->metadata = $m;
+            }
+        }
+    }
+
+    function __toString()
+    {
+        return $this->_string;
+    }
+
+    function offsetExists($offset)
+    {
+        return isset($this->metadata[$offset]);
+    }
+
+    function offsetGet($offset)
+    {
+        return $this->metadata[$offset];
+    }
+
+    function offsetSet($offset, $value)
+    {
+        if ($offset === null) {
+            if (isset($value[0])) {
+                $x = ($value instanceof TP_yyToken) ?
+                    $value->metadata : $value;
+                $this->metadata = array_merge($this->metadata, $x);
+                return;
+            }
+            $offset = count($this->metadata);
+        }
+        if ($value === null) {
+            return;
+        }
+        if ($value instanceof TP_yyToken) {
+            if ($value->metadata) {
+                $this->metadata[$offset] = $value->metadata;
+            }
+        } elseif ($value) {
+            $this->metadata[$offset] = $value;
+        }
+    }
+
+    function offsetUnset($offset)
+    {
+        unset($this->metadata[$offset]);
+    }
+}
+
+class TP_yyStackEntry
+{
+    public $stateno;       /* The state-number */
+    public $major;         /* The major token value.  This is the code
+                     ** number for the token at this stack level */
+    public $minor; /* The user-supplied minor token value.  This
+                     ** is the value of the token  */
+};
+
+
+#line 12 "smarty_internal_templateparser.y"
+class Smarty_Internal_Templateparser#line 79 "smarty_internal_templateparser.php"
+{
+#line 14 "smarty_internal_templateparser.y"
+
+         const Err1 = "Security error: Call to private object member not allowed";
+         const Err2 = "Security error: Call to dynamic object member not allowed";
+    // states whether the parse was successful or not
+    public $successful = true;
+    public $retvalue = 0;
+    private $lex;
+    private $internalError = false;
+
+    function __construct($lex, $compiler) {
+        $this->lex = $lex;
+        $this->compiler = $compiler;
+        $this->smarty = $this->compiler->smarty;
+        $this->template = $this->compiler->template;
+        $this->compiler->has_variable_string = false;
+                               $this->compiler->prefix_code = array();
+                               $this->prefix_number = 0;
+                               $this->block_nesting_level = 0;
+                               if ($this->security = isset($this->smarty->security_policy)) {
+              $this->php_handling = $this->smarty->security_policy->php_handling;
+        } else {
+              $this->php_handling = $this->smarty->php_handling;
+        }
+                               $this->is_xml = false;
+                               $this->asp_tags = (ini_get('asp_tags') != '0');
+                               $this->current_buffer = $this->root_buffer = new _smarty_template_buffer($this);
+    }
+
+    public static function escape_start_tag($tag_text) {
+       $tag = preg_replace('/\A<\?(.*)\z/', '<<?php ?>?\1', $tag_text, -1 , $count); //Escape tag
+       return $tag;
+    }
+
+    public static function escape_end_tag($tag_text) {
+       return '?<?php ?>>';
+    }
+
+    
+#line 121 "smarty_internal_templateparser.php"
+
+    const TP_VERT                           =  1;
+    const TP_COLON                          =  2;
+    const TP_COMMENT                        =  3;
+    const TP_PHPSTARTTAG                    =  4;
+    const TP_PHPENDTAG                      =  5;
+    const TP_ASPSTARTTAG                    =  6;
+    const TP_ASPENDTAG                      =  7;
+    const TP_FAKEPHPSTARTTAG                =  8;
+    const TP_XMLTAG                         =  9;
+    const TP_OTHER                          = 10;
+    const TP_LINEBREAK                      = 11;
+    const TP_LITERALSTART                   = 12;
+    const TP_LITERALEND                     = 13;
+    const TP_LITERAL                        = 14;
+    const TP_LDEL                           = 15;
+    const TP_RDEL                           = 16;
+    const TP_DOLLAR                         = 17;
+    const TP_ID                             = 18;
+    const TP_EQUAL                          = 19;
+    const TP_PTR                            = 20;
+    const TP_LDELIF                         = 21;
+    const TP_SPACE                          = 22;
+    const TP_LDELFOR                        = 23;
+    const TP_SEMICOLON                      = 24;
+    const TP_INCDEC                         = 25;
+    const TP_TO                             = 26;
+    const TP_STEP                           = 27;
+    const TP_LDELFOREACH                    = 28;
+    const TP_AS                             = 29;
+    const TP_APTR                           = 30;
+    const TP_SMARTYBLOCKCHILD               = 31;
+    const TP_LDELSLASH                      = 32;
+    const TP_INTEGER                        = 33;
+    const TP_COMMA                          = 34;
+    const TP_OPENP                          = 35;
+    const TP_CLOSEP                         = 36;
+    const TP_MATH                           = 37;
+    const TP_UNIMATH                        = 38;
+    const TP_ANDSYM                         = 39;
+    const TP_ISIN                           = 40;
+    const TP_ISDIVBY                        = 41;
+    const TP_ISNOTDIVBY                     = 42;
+    const TP_ISEVEN                         = 43;
+    const TP_ISNOTEVEN                      = 44;
+    const TP_ISEVENBY                       = 45;
+    const TP_ISNOTEVENBY                    = 46;
+    const TP_ISODD                          = 47;
+    const TP_ISNOTODD                       = 48;
+    const TP_ISODDBY                        = 49;
+    const TP_ISNOTODDBY                     = 50;
+    const TP_INSTANCEOF                     = 51;
+    const TP_QMARK                          = 52;
+    const TP_NOT                            = 53;
+    const TP_TYPECAST                       = 54;
+    const TP_HEX                            = 55;
+    const TP_DOT                            = 56;
+    const TP_SINGLEQUOTESTRING              = 57;
+    const TP_DOUBLECOLON                    = 58;
+    const TP_AT                             = 59;
+    const TP_HATCH                          = 60;
+    const TP_OPENB                          = 61;
+    const TP_CLOSEB                         = 62;
+    const TP_EQUALS                         = 63;
+    const TP_NOTEQUALS                      = 64;
+    const TP_GREATERTHAN                    = 65;
+    const TP_LESSTHAN                       = 66;
+    const TP_GREATEREQUAL                   = 67;
+    const TP_LESSEQUAL                      = 68;
+    const TP_IDENTITY                       = 69;
+    const TP_NONEIDENTITY                   = 70;
+    const TP_MOD                            = 71;
+    const TP_LAND                           = 72;
+    const TP_LOR                            = 73;
+    const TP_LXOR                           = 74;
+    const TP_QUOTE                          = 75;
+    const TP_BACKTICK                       = 76;
+    const TP_DOLLARID                       = 77;
+    const YY_NO_ACTION = 584;
+    const YY_ACCEPT_ACTION = 583;
+    const YY_ERROR_ACTION = 582;
+
+    const YY_SZ_ACTTAB = 2566;
+static public $yy_action = array(
+ /*     0 */   218,  272,  271,  275,  274,  278,  277,  276,  270,  262,
+ /*    10 */   260,  264,  268,  196,  298,  285,   42,   22,  159,  265,
+ /*    20 */    19,   29,  222,  374,  237,   29,  294,   29,  280,  149,
+ /*    30 */   243,   19,  378,  225,  374,  244,   52,   47,   50,   45,
+ /*    40 */    38,   37,  331,  332,   40,   39,  340,  337,   30,   25,
+ /*    50 */   292,  299,  291,  290,  295,  190,  123,  342,  196,  279,
+ /*    60 */   293,  135,  335,  322,  321,  308,  309,  310,  307,  306,
+ /*    70 */   302,  303,  304,  305,  218,  242,  319,  175,  199,  133,
+ /*    80 */   138,   19,  248,   72,  374,  124,   19,  288,  448,  374,
+ /*    90 */    41,   14,  339,  311,  448,   29,  348,  329,  376,  320,
+ /*   100 */    34,  583,   95,  273,  271,  275,  219,    3,  301,    3,
+ /*   110 */    52,   47,   50,   45,   38,   37,  331,  332,   40,   39,
+ /*   120 */   340,  337,   30,   25,    7,  231,   17,  108,  134,  167,
+ /*   130 */   140,   35,  140,  143,  336,  192,  335,  322,  321,  308,
+ /*   140 */   309,  310,  307,  306,  302,  303,  304,  305,  218,  334,
+ /*   150 */   319,  193,  353,   10,  138,    3,  248,   55,    3,  119,
+ /*   160 */   136,   36,   31,  371,  218,   19,  339,  311,  374,   29,
+ /*   170 */   348,  329,   29,  320,  199,   27,  223,  258,  140,  372,
+ /*   180 */   224,  140,  254,  220,   52,   47,   50,   45,   38,   37,
+ /*   190 */   331,  332,   40,   39,  340,  337,   30,   25,  341,  179,
+ /*   200 */    32,  159,  106,  323,   29,  194,  379,  342,  218,  288,
+ /*   210 */   335,  322,  321,  308,  309,  310,  307,  306,  302,  303,
+ /*   220 */   304,  305,  218,  366,  319,  199,  186,  218,  138,  190,
+ /*   230 */   248,   72,  445,  124,  218,  266,  288,  364,  445,  123,
+ /*   240 */   339,  311,  447,   29,  348,  329,   19,  320,  447,  374,
+ /*   250 */    23,    3,  199,   16,  211,   29,  297,  170,   52,   47,
+ /*   260 */    50,   45,   38,   37,  331,  332,   40,   39,  340,  337,
+ /*   270 */    30,   25,  218,  172,  140,  183,  104,   46,   19,  189,
+ /*   280 */   379,  374,   41,  288,  335,  322,  321,  308,  309,  310,
+ /*   290 */   307,  306,  302,  303,  304,  305,  344,  188,  444,  199,
+ /*   300 */   218,  235,  249,  216,   29,  191,  379,  342,   52,   47,
+ /*   310 */    50,   45,   38,   37,  331,  332,   40,   39,  340,  337,
+ /*   320 */    30,   25,  242,   19,  142,   43,  374,  130,  245,   28,
+ /*   330 */    29,  159,  107,  346,  335,  322,  321,  308,  309,  310,
+ /*   340 */   307,  306,  302,  303,  304,  305,  218,  347,  319,   27,
+ /*   350 */    46,  257,  138,  198,  248,   62,  164,  119,  240,  218,
+ /*   360 */   267,  252,  228,  126,  339,  311,  288,  205,  348,  329,
+ /*   370 */   103,  320,    8,  261,  444,  357,  180,  376,  376,   29,
+ /*   380 */    29,   29,   52,   47,   50,   45,   38,   37,  331,  332,
+ /*   390 */    40,   39,  340,  337,   30,   25,  184,  349,  361,  365,
+ /*   400 */    27,  284,  358,   29,   29,   29,  288,   29,  335,  322,
+ /*   410 */   321,  308,  309,  310,  307,  306,  302,  303,  304,  305,
+ /*   420 */   218,  319,  202,  221,  181,  138,  154,  248,   72,  171,
+ /*   430 */   124,  313,    9,  162,  288,  289,  163,  339,  311,  288,
+ /*   440 */   320,  348,  329,  288,  320,  376,  288,  281,  269,  370,
+ /*   450 */   376,  214,    6,   29,   29,   29,   52,   47,   50,   45,
+ /*   460 */    38,   37,  331,  332,   40,   39,  340,  337,   30,   25,
+ /*   470 */   218,  178,  239,  283,  373,   19,  226,  238,  374,   29,
+ /*   480 */    29,  288,  335,  322,  321,  308,  309,  310,  307,  306,
+ /*   490 */   302,  303,  304,  305,  177,  205,  286,  202,  227,  377,
+ /*   500 */     8,  166,   29,  376,  288,   29,   52,   47,   50,   45,
+ /*   510 */    38,   37,  331,  332,   40,   39,  340,  337,   30,   25,
+ /*   520 */   202,  218,  363,  375,  380,  315,  235,  296,   29,   29,
+ /*   530 */    29,   29,  335,  322,  321,  308,  309,  310,  307,  306,
+ /*   540 */   302,  303,  304,  305,  197,  369,  352,   19,  327,  218,
+ /*   550 */   236,   29,   29,  165,  234,  156,  174,   52,   47,   50,
+ /*   560 */    45,   38,   37,  331,  332,   40,   39,  340,  337,   30,
+ /*   570 */    25,   26,  344,    5,   19,  314,  199,  212,   19,  199,
+ /*   580 */   159,  241,  218,  335,  322,  321,  308,  309,  310,  307,
+ /*   590 */   306,  302,  303,  304,  305,  218,  319,  300,  100,   46,
+ /*   600 */   138,   19,  248,   76,  233,  124,    6,  218,  110,  351,
+ /*   610 */   201,  338,  339,  311,  115,  168,  348,  329,  123,  320,
+ /*   620 */   182,  338,  287,  234,  105,  288,  324,  338,  235,  240,
+ /*   630 */   288,   52,   47,   50,   45,   38,   37,  331,  332,   40,
+ /*   640 */    39,  340,  337,   30,   25,  218,  333,  144,  263,   33,
+ /*   650 */    13,  342,  312,  156,   29,  355,   97,  335,  322,  321,
+ /*   660 */   308,  309,  310,  307,  306,  302,  303,  304,  305,  338,
+ /*   670 */   141,   32,  325,  121,  195,  131,  356,  229,  127,    2,
+ /*   680 */   250,   52,   47,   50,   45,   38,   37,  331,  332,   40,
+ /*   690 */    39,  340,  337,   30,   25,  318,  228,   11,  330,   94,
+ /*   700 */   129,  282,  218,  253,  159,   29,  323,  335,  322,  321,
+ /*   710 */   308,  309,  310,  307,  306,  302,  303,  304,  305,  218,
+ /*   720 */   218,  319,   18,  101,  148,  122,  114,  248,   54,   44,
+ /*   730 */   124,  202,   99,  158,  316,  367,  376,  339,  311,  338,
+ /*   740 */    29,  348,  329,  376,  320,  338,  338,  354,  169,  368,
+ /*   750 */   321,  321,  321,  321,  321,   52,   47,   50,   45,   38,
+ /*   760 */    37,  331,  332,   40,   39,  340,  337,   30,   25,  218,
+ /*   770 */    46,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*   780 */   113,  335,  322,  321,  308,  309,  310,  307,  306,  302,
+ /*   790 */   303,  304,  305,  338,  321,  321,  321,  321,  321,  321,
+ /*   800 */   321,  321,  321,  321,  256,   52,   47,   50,   45,   38,
+ /*   810 */    37,  331,  332,   40,   39,  340,  337,   30,   25,  218,
+ /*   820 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*   830 */   321,  335,  322,  321,  308,  309,  310,  307,  306,  302,
+ /*   840 */   303,  304,  305,  321,  321,  321,  321,  321,  321,  321,
+ /*   850 */   321,  321,  321,  321,  321,   52,   47,   50,   45,   38,
+ /*   860 */    37,  331,  332,   40,   39,  340,  337,   30,   25,  218,
+ /*   870 */    12,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*   880 */   382,  335,  322,  321,  308,  309,  310,  307,  306,  302,
+ /*   890 */   303,  304,  305,  321,  321,  321,  321,  321,  321,  321,
+ /*   900 */   321,  321,  321,  321,  321,   52,   47,   50,   45,   38,
+ /*   910 */    37,  331,  332,   40,   39,  340,  337,   30,   25,  321,
+ /*   920 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*   930 */   321,  335,  322,  321,  308,  309,  310,  307,  306,  302,
+ /*   940 */   303,  304,  305,  218,  319,  321,  321,  321,  138,  321,
+ /*   950 */   248,   61,  321,  124,  321,   98,  132,  321,  200,  321,
+ /*   960 */   339,  311,  321,  321,  348,  329,  321,  320,  338,  338,
+ /*   970 */   321,  321,  321,  321,  321,  321,  321,  321,  321,   52,
+ /*   980 */    47,   50,   45,   38,   37,  331,  332,   40,   39,  340,
+ /*   990 */   337,   30,   25,  218,  321,  321,  321,  321,  321,  321,
+ /*  1000 */   321,  321,  321,  321,  321,  335,  322,  321,  308,  309,
+ /*  1010 */   310,  307,  306,  302,  303,  304,  305,  321,  321,  321,
+ /*  1020 */   321,  321,  321,  321,  321,  321,  321,  321,  321,   52,
+ /*  1030 */    47,   50,   45,   38,   37,  331,  332,   40,   39,  340,
+ /*  1040 */   337,   30,   25,  321,  321,  321,  321,  321,  321,  321,
+ /*  1050 */   321,  321,  321,  321,  321,  335,  322,  321,  308,  309,
+ /*  1060 */   310,  307,  306,  302,  303,  304,  305,   52,   47,   50,
+ /*  1070 */    45,   38,   37,  331,  332,   40,   39,  340,  337,   30,
+ /*  1080 */    25,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*  1090 */   321,  321,  321,  335,  322,  321,  308,  309,  310,  307,
+ /*  1100 */   306,  302,  303,  304,  305,  321,  321,  321,  321,   42,
+ /*  1110 */   321,  139,  207,  321,  319,  222,  321,  237,  138,  321,
+ /*  1120 */   248,   78,  149,  124,  321,  378,  225,  232,  321,   15,
+ /*  1130 */   339,  311,   49,  321,  348,  329,  321,  320,  321,  321,
+ /*  1140 */   321,  321,  321,  321,  321,  321,  321,   51,   48,  317,
+ /*  1150 */   247,  328,  321,  319,  103,    1,  255,  145,  321,  248,
+ /*  1160 */   321,  321,  124,  321,   42,  321,  139,  209,  321,   96,
+ /*  1170 */   222,  321,  237,  348,  329,  321,  320,  149,  345,  321,
+ /*  1180 */   378,  225,  232,   24,   15,  321,  321,   49,  321,  222,
+ /*  1190 */   321,  237,  321,  321,  321,  321,  149,  321,  321,  378,
+ /*  1200 */   225,  321,   51,   48,  317,  247,  328,  321,  319,  103,
+ /*  1210 */     1,  321,  146,  321,  248,  321,  321,  124,  321,   42,
+ /*  1220 */   161,  130,  209,  193,   96,  222,  321,  237,  348,  329,
+ /*  1230 */   288,  320,  149,   36,   31,  378,  225,  232,  321,   21,
+ /*  1240 */   321,  321,   49,  350,   20,  343,  199,  319,  218,  321,
+ /*  1250 */   321,  155,  321,  248,  321,  321,  124,   51,   48,  317,
+ /*  1260 */   247,  328,  321,  450,  103,    1,  321,  348,  329,  450,
+ /*  1270 */   320,  321,  321,  321,   42,  321,  125,  209,  321,   96,
+ /*  1280 */   222,  321,  237,  321,  321,  321,  321,  149,  345,  321,
+ /*  1290 */   378,  225,  232,   24,    4,  321,  321,   49,   46,  222,
+ /*  1300 */   321,  237,  321,  321,  321,  321,  149,  321,  321,  378,
+ /*  1310 */   225,  321,   51,   48,  317,  247,  328,  321,  319,  103,
+ /*  1320 */     1,  321,  151,  321,  248,  321,  321,  124,  321,   42,
+ /*  1330 */   176,  139,  204,  193,   96,  222,  321,  237,  348,  329,
+ /*  1340 */   288,  320,  149,   36,   31,  378,  225,  215,  321,   15,
+ /*  1350 */   321,  321,   49,  362,   20,  343,  199,  319,  218,  321,
+ /*  1360 */   321,  150,  321,  248,  321,  321,  124,   51,   48,  317,
+ /*  1370 */   247,  328,  321,  259,  103,    1,  321,  348,  329,   29,
+ /*  1380 */   320,  321,  321,  321,   42,  173,  128,   92,  193,   96,
+ /*  1390 */   222,  321,  237,  321,  218,  288,  321,  149,   36,   31,
+ /*  1400 */   378,  225,  232,  321,   15,  321,  321,   49,   46,  381,
+ /*  1410 */   321,  199,  319,  230,  321,   29,  152,  321,  248,  321,
+ /*  1420 */   321,  124,   51,   48,  317,  247,  328,  321,    3,  103,
+ /*  1430 */     1,  321,  348,  329,  321,  320,  321,  321,  321,   42,
+ /*  1440 */   185,  139,  208,  102,   96,  222,  321,  237,  321,  321,
+ /*  1450 */   288,  140,  149,   36,   31,  378,  225,  232,  321,   15,
+ /*  1460 */   321,  321,   49,  321,  321,  321,  199,  319,  321,  321,
+ /*  1470 */   321,  147,  321,  248,  321,  321,  124,   51,   48,  317,
+ /*  1480 */   247,  328,  321,  321,  103,    1,  321,  348,  329,  321,
+ /*  1490 */   320,  321,  321,  321,   42,  187,  139,  203,  193,   96,
+ /*  1500 */   222,  321,  237,  321,  321,  288,  321,  149,   36,   31,
+ /*  1510 */   378,  225,  232,  321,   15,  321,  160,   49,  321,  193,
+ /*  1520 */   321,  199,  321,  321,  321,  321,  288,  321,  321,   36,
+ /*  1530 */    31,  321,   51,   48,  317,  247,  328,  321,  321,  103,
+ /*  1540 */     1,  321,  199,  321,  321,  321,  321,  321,  321,   42,
+ /*  1550 */   321,  139,  206,  218,   96,  222,  321,  237,  321,  321,
+ /*  1560 */   321,  321,  149,  321,  321,  378,  225,  232,  450,   15,
+ /*  1570 */   321,  321,   49,  321,  450,  321,  321,  321,  321,  321,
+ /*  1580 */   321,  246,  321,  321,  321,  321,  321,   51,   48,  317,
+ /*  1590 */   247,  328,  321,  321,  103,    1,  321,  321,  321,  321,
+ /*  1600 */   321,  321,  321,   46,   42,  321,  137,  209,  321,   96,
+ /*  1610 */   222,  321,  237,  321,  321,  321,  321,  149,  321,  321,
+ /*  1620 */   378,  225,  232,  321,   15,  321,  321,   49,  321,  321,
+ /*  1630 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*  1640 */   321,  321,   51,   48,  317,  247,  328,  321,  321,  103,
+ /*  1650 */     1,  321,  321,  321,  321,  321,  321,  321,  321,   42,
+ /*  1660 */   321,  130,  210,  321,   96,  222,  321,  237,  321,  321,
+ /*  1670 */   321,  321,  149,  321,  321,  378,  225,  232,  321,   21,
+ /*  1680 */   321,  321,   49,  321,  321,  321,  321,  321,  321,  321,
+ /*  1690 */   321,  321,  321,  321,  321,  321,  321,   51,   48,  317,
+ /*  1700 */   247,  328,  321,  321,  103,  321,  321,  321,  321,  321,
+ /*  1710 */   321,  321,  321,  321,   42,  321,  130,  209,  321,   96,
+ /*  1720 */   222,  321,  237,  321,  321,  321,  321,  149,  321,  321,
+ /*  1730 */   378,  225,  232,  321,   21,  321,  321,   49,  321,  321,
+ /*  1740 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*  1750 */   321,  321,   51,   48,  317,  247,  328,  321,  321,  103,
+ /*  1760 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  493,
+ /*  1770 */   321,  321,  321,  321,   96,  493,  321,  493,  321,  493,
+ /*  1780 */   493,  321,  493,  321,  321,  321,  321,  493,    3,  493,
+ /*  1790 */   321,  321,  321,  321,  321,  321,  321,  321,  321,  321,
+ /*  1800 */   321,  321,  321,  319,  493,  321,  321,  117,  321,  248,
+ /*  1810 */    82,  140,  124,  321,  321,  493,  321,  321,  321,  339,
+ /*  1820 */   311,  321,  321,  348,  329,  321,  320,  321,  321,  493,
+ /*  1830 */   321,  321,  321,  321,  321,  321,  319,  217,  360,  321,
+ /*  1840 */   117,  321,  248,   82,  321,  124,  321,  321,  321,  321,
+ /*  1850 */   321,  321,  339,  311,  321,  321,  348,  329,  319,  320,
+ /*  1860 */   321,  321,  138,  321,  248,   90,  321,  124,  321,  321,
+ /*  1870 */   321,  359,  321,  321,  339,  311,  321,  321,  348,  329,
+ /*  1880 */   321,  320,  321,  321,  321,  321,  319,  321,  321,  321,
+ /*  1890 */   138,  321,  248,   69,  321,  124,  321,  321,  321,  321,
+ /*  1900 */   321,  321,  339,  311,  321,  321,  348,  329,  321,  320,
+ /*  1910 */   321,  321,  319,  321,  321,  321,  138,  321,  248,   67,
+ /*  1920 */   321,  124,  321,  321,  321,  321,  321,  321,  339,  311,
+ /*  1930 */   321,  321,  348,  329,  321,  320,  319,  321,  321,  321,
+ /*  1940 */   138,  321,  248,   58,  321,  124,  321,  321,  321,  321,
+ /*  1950 */   321,  321,  339,  311,  319,  321,  348,  329,  138,  320,
+ /*  1960 */   248,   62,  321,  124,  321,  321,  321,  321,  321,  321,
+ /*  1970 */   339,  311,  321,  321,  348,  329,  319,  320,  321,  321,
+ /*  1980 */   138,  321,  248,   56,  321,  124,  321,  321,  321,  321,
+ /*  1990 */   321,  321,  339,  311,  321,  321,  348,  329,  321,  320,
+ /*  2000 */   321,  319,  321,  321,  321,  112,  321,  248,   71,  321,
+ /*  2010 */   124,  321,  321,  321,  321,  321,  321,  339,  311,  319,
+ /*  2020 */   321,  348,  329,  111,  320,  248,   81,  321,  124,  321,
+ /*  2030 */   321,  321,  321,  321,  321,  339,  311,  319,  321,  348,
+ /*  2040 */   329,  138,  320,  248,   74,  321,  124,  321,  321,  321,
+ /*  2050 */   321,  321,  321,  339,  311,  321,  321,  348,  329,  319,
+ /*  2060 */   320,  321,  321,  138,  321,  248,   91,  321,  124,  321,
+ /*  2070 */   321,  321,  321,  321,  321,  339,  311,  321,  321,  348,
+ /*  2080 */   329,  321,  320,  321,  319,  321,  321,  321,  138,  321,
+ /*  2090 */   248,   64,  321,  124,  321,  321,  321,  321,  321,  321,
+ /*  2100 */   339,  311,  319,  321,  348,  329,  138,  320,  248,   63,
+ /*  2110 */   321,  124,  321,  321,  321,  321,  321,  321,  339,  311,
+ /*  2120 */   319,  321,  348,  329,  138,  320,  248,   83,  321,  124,
+ /*  2130 */   321,  321,  321,  321,  321,  321,  339,  311,  321,  321,
+ /*  2140 */   348,  329,  319,  320,  321,  321,  138,  321,  248,   79,
+ /*  2150 */   321,  124,  321,  321,  321,  321,  321,  321,  339,  311,
+ /*  2160 */   321,  321,  348,  329,  321,  320,  321,  319,  321,  321,
+ /*  2170 */   321,  138,  321,  248,   75,  321,  124,  321,  321,  321,
+ /*  2180 */   321,  321,  321,  339,  311,  319,  321,  348,  329,  138,
+ /*  2190 */   320,  248,   70,  321,  124,  321,  321,  321,  321,  321,
+ /*  2200 */   321,  339,  311,  319,  321,  348,  329,  109,  320,  248,
+ /*  2210 */    68,  321,  124,  321,  321,  321,  321,  321,  321,  339,
+ /*  2220 */   311,  321,  321,  348,  329,  319,  320,  321,  321,  138,
+ /*  2230 */   321,  248,   77,  321,  124,  321,  321,  321,  321,  321,
+ /*  2240 */   321,  339,  311,  321,  321,  348,  329,  321,  320,  321,
+ /*  2250 */   319,  321,  321,  321,  138,  321,  248,   73,  321,  124,
+ /*  2260 */   321,  321,  321,  321,  321,  321,  339,  311,  319,  321,
+ /*  2270 */   348,  329,  138,  320,  213,   65,  321,  124,  321,  321,
+ /*  2280 */   321,  321,  321,  321,  339,  311,  319,  321,  348,  329,
+ /*  2290 */   138,  320,  248,   86,  321,  124,  321,  321,  321,  321,
+ /*  2300 */   321,  321,  339,  311,  321,  321,  348,  329,  319,  320,
+ /*  2310 */   321,  321,  138,  321,  248,   88,  321,  124,  321,  321,
+ /*  2320 */   321,  321,  321,  321,  339,  311,  321,  321,  348,  329,
+ /*  2330 */   321,  320,  321,  319,  321,  321,  321,   93,  321,  120,
+ /*  2340 */    59,  321,  116,  321,  321,  321,  321,  321,  321,  339,
+ /*  2350 */   311,  319,  321,  348,  329,  138,  320,  248,   57,  321,
+ /*  2360 */   124,  321,  321,  321,  321,  321,  321,  339,  311,  319,
+ /*  2370 */   321,  348,  329,  138,  320,  248,   60,  321,  124,  321,
+ /*  2380 */   321,  321,  321,  321,  321,  339,  311,  321,  321,  348,
+ /*  2390 */   329,  319,  320,  321,  321,  138,  321,  248,   89,  321,
+ /*  2400 */   124,  321,  321,  321,  321,  321,  321,  339,  311,  321,
+ /*  2410 */   321,  348,  329,  321,  320,  321,  319,  321,  321,  321,
+ /*  2420 */   138,  321,  248,   85,  321,  124,  321,  321,  321,  321,
+ /*  2430 */   321,  321,  339,  311,  319,  321,  348,  329,  138,  320,
+ /*  2440 */   248,   80,  321,  124,  321,  321,  321,  321,  321,  321,
+ /*  2450 */   339,  311,  319,  321,  348,  329,  138,  320,  248,   84,
+ /*  2460 */   321,  124,  321,  321,  321,  321,  321,  321,  339,  311,
+ /*  2470 */   321,  321,  348,  329,  319,  320,  321,  321,  138,  321,
+ /*  2480 */   248,   66,  321,  124,  321,  321,  321,  321,  321,  321,
+ /*  2490 */   339,  311,  321,  321,  348,  329,  321,  320,  321,  319,
+ /*  2500 */   321,  321,  321,  138,  321,  248,   87,  321,  124,  321,
+ /*  2510 */   321,  321,  321,  321,  321,  339,  311,  319,  321,  348,
+ /*  2520 */   329,   93,  320,  118,   53,  321,  116,  321,  321,  321,
+ /*  2530 */   321,  321,  321,  339,  311,  319,  321,  348,  329,  153,
+ /*  2540 */   320,  248,  319,  321,  124,  321,  157,  321,  248,  321,
+ /*  2550 */   321,  124,  326,  321,  321,  348,  329,  321,  320,  251,
+ /*  2560 */   321,  321,  348,  329,  321,  320,
+    );
+    static public $yy_lookahead = array(
+ /*     0 */     1,   81,   82,   83,    3,    4,    5,    6,    7,    8,
+ /*    10 */     9,   10,   11,   12,   22,   16,   15,   19,   20,   16,
+ /*    20 */    15,   22,   21,   18,   23,   22,   83,   22,   85,   28,
+ /*    30 */    94,   15,   31,   32,   18,   30,   37,   38,   39,   40,
+ /*    40 */    41,   42,   43,   44,   45,   46,   47,   48,   49,   50,
+ /*    50 */     4,    5,    6,    7,    8,   90,   58,   25,   12,   13,
+ /*    60 */    14,   17,   63,   64,   65,   66,   67,   68,   69,   70,
+ /*    70 */    71,   72,   73,   74,    1,   59,   82,   87,  113,   35,
+ /*    80 */    86,   15,   88,   89,   18,   91,   15,   97,   16,   18,
+ /*    90 */    19,   19,   98,   99,   22,   22,  102,  103,  108,  105,
+ /*   100 */    27,   79,   80,   81,   82,   83,  112,   35,   76,   35,
+ /*   110 */    37,   38,   39,   40,   41,   42,   43,   44,   45,   46,
+ /*   120 */    47,   48,   49,   50,   34,   59,   15,   84,   17,   18,
+ /*   130 */    58,   15,   58,   17,   18,  114,   63,   64,   65,   66,
+ /*   140 */    67,   68,   69,   70,   71,   72,   73,   74,    1,   33,
+ /*   150 */    82,   90,   62,   30,   86,   35,   88,   89,   35,   91,
+ /*   160 */    92,  100,  101,   16,    1,   15,   98,   99,   18,   22,
+ /*   170 */   102,  103,   22,  105,  113,   34,   56,   36,   58,   16,
+ /*   180 */    30,   58,   62,   20,   37,   38,   39,   40,   41,   42,
+ /*   190 */    43,   44,   45,   46,   47,   48,   49,   50,   16,   87,
+ /*   200 */    19,   20,   90,  107,   22,  109,  110,   25,    1,   97,
+ /*   210 */    63,   64,   65,   66,   67,   68,   69,   70,   71,   72,
+ /*   220 */    73,   74,    1,   16,   82,  113,   87,    1,   86,   90,
+ /*   230 */    88,   89,   16,   91,    1,   13,   97,   16,   22,   58,
+ /*   240 */    98,   99,   16,   22,  102,  103,   15,  105,   22,   18,
+ /*   250 */    19,   35,  113,   94,  112,   22,   25,  106,   37,   38,
+ /*   260 */    39,   40,   41,   42,   43,   44,   45,   46,   47,   48,
+ /*   270 */    49,   50,    1,   87,   58,  106,   90,   51,   15,  109,
+ /*   280 */   110,   18,   19,   97,   63,   64,   65,   66,   67,   68,
+ /*   290 */    69,   70,   71,   72,   73,   74,   82,  114,   16,  113,
+ /*   300 */     1,   91,   92,   93,   22,  109,  110,   25,   37,   38,
+ /*   310 */    39,   40,   41,   42,   43,   44,   45,   46,   47,   48,
+ /*   320 */    49,   50,   59,   15,   17,   19,   18,   17,   18,   30,
+ /*   330 */    22,   20,  118,  119,   63,   64,   65,   66,   67,   68,
+ /*   340 */    69,   70,   71,   72,   73,   74,    1,   76,   82,   34,
+ /*   350 */    51,   36,   86,   24,   88,   89,   87,   91,   92,    1,
+ /*   360 */    36,   16,   56,   34,   98,   99,   97,   56,  102,  103,
+ /*   370 */    60,  105,   61,   16,   16,   16,  106,  108,  108,   22,
+ /*   380 */    22,   22,   37,   38,   39,   40,   41,   42,   43,   44,
+ /*   390 */    45,   46,   47,   48,   49,   50,   87,   16,   16,   16,
+ /*   400 */    34,   16,   36,   22,   22,   22,   97,   22,   63,   64,
+ /*   410 */    65,   66,   67,   68,   69,   70,   71,   72,   73,   74,
+ /*   420 */     1,   82,  113,   88,   87,   86,   91,   88,   89,   87,
+ /*   430 */    91,   18,   15,   87,   97,   16,   87,   98,   99,   97,
+ /*   440 */   105,  102,  103,   97,  105,  108,   97,   16,   16,   16,
+ /*   450 */   108,  112,   35,   22,   22,   22,   37,   38,   39,   40,
+ /*   460 */    41,   42,   43,   44,   45,   46,   47,   48,   49,   50,
+ /*   470 */     1,   87,   59,   16,   16,   15,   17,   18,   18,   22,
+ /*   480 */    22,   97,   63,   64,   65,   66,   67,   68,   69,   70,
+ /*   490 */    71,   72,   73,   74,   87,   56,   16,  113,   29,   16,
+ /*   500 */    61,  106,   22,  108,   97,   22,   37,   38,   39,   40,
+ /*   510 */    41,   42,   43,   44,   45,   46,   47,   48,   49,   50,
+ /*   520 */   113,    1,   16,   16,   16,   16,   91,   92,   22,   22,
+ /*   530 */    22,   22,   63,   64,   65,   66,   67,   68,   69,   70,
+ /*   540 */    71,   72,   73,   74,   24,   16,   16,   15,  104,    1,
+ /*   550 */    18,   22,   22,   90,    2,  111,   90,   37,   38,   39,
+ /*   560 */    40,   41,   42,   43,   44,   45,   46,   47,   48,   49,
+ /*   570 */    50,   19,   82,   35,   15,   18,  113,   18,   15,  113,
+ /*   580 */    20,   18,    1,   63,   64,   65,   66,   67,   68,   69,
+ /*   590 */    70,   71,   72,   73,   74,    1,   82,   16,   95,   51,
+ /*   600 */    86,   15,   88,   89,   18,   91,   35,    1,   95,  119,
+ /*   610 */    16,  108,   98,   99,   95,   87,  102,  103,   58,  105,
+ /*   620 */    87,  108,   16,    2,   22,   97,   18,  108,   91,   92,
+ /*   630 */    97,   37,   38,   39,   40,   41,   42,   43,   44,   45,
+ /*   640 */    46,   47,   48,   49,   50,    1,  104,   17,   16,   26,
+ /*   650 */    52,   25,   33,  111,   22,   60,   95,   63,   64,   65,
+ /*   660 */    66,   67,   68,   69,   70,   71,   72,   73,   74,  108,
+ /*   670 */    17,   19,   18,   18,   18,   17,   60,   18,   17,   22,
+ /*   680 */    36,   37,   38,   39,   40,   41,   42,   43,   44,   45,
+ /*   690 */    46,   47,   48,   49,   50,   33,   56,    2,   18,   18,
+ /*   700 */    18,   97,    1,   62,   20,   22,  107,   63,   64,   65,
+ /*   710 */    66,   67,   68,   69,   70,   71,   72,   73,   74,    1,
+ /*   720 */     1,   82,   22,  106,   96,   86,   95,   88,   89,    2,
+ /*   730 */    91,  113,   95,   95,  110,   16,  108,   98,   99,  108,
+ /*   740 */    22,  102,  103,  108,  105,  108,  108,  111,  106,  115,
+ /*   750 */   120,  120,  120,  120,  120,   37,   38,   39,   40,   41,
+ /*   760 */    42,   43,   44,   45,   46,   47,   48,   49,   50,    1,
+ /*   770 */    51,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*   780 */    95,   63,   64,   65,   66,   67,   68,   69,   70,   71,
+ /*   790 */    72,   73,   74,  108,  120,  120,  120,  120,  120,  120,
+ /*   800 */   120,  120,  120,  120,   36,   37,   38,   39,   40,   41,
+ /*   810 */    42,   43,   44,   45,   46,   47,   48,   49,   50,    1,
+ /*   820 */   120,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*   830 */   120,   63,   64,   65,   66,   67,   68,   69,   70,   71,
+ /*   840 */    72,   73,   74,  120,  120,  120,  120,  120,  120,  120,
+ /*   850 */   120,  120,  120,  120,  120,   37,   38,   39,   40,   41,
+ /*   860 */    42,   43,   44,   45,   46,   47,   48,   49,   50,    1,
+ /*   870 */     2,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*   880 */    62,   63,   64,   65,   66,   67,   68,   69,   70,   71,
+ /*   890 */    72,   73,   74,  120,  120,  120,  120,  120,  120,  120,
+ /*   900 */   120,  120,  120,  120,  120,   37,   38,   39,   40,   41,
+ /*   910 */    42,   43,   44,   45,   46,   47,   48,   49,   50,  120,
+ /*   920 */   120,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*   930 */   120,   63,   64,   65,   66,   67,   68,   69,   70,   71,
+ /*   940 */    72,   73,   74,    1,   82,  120,  120,  120,   86,  120,
+ /*   950 */    88,   89,  120,   91,  120,   95,   95,  120,   16,  120,
+ /*   960 */    98,   99,  120,  120,  102,  103,  120,  105,  108,  108,
+ /*   970 */   120,  120,  120,  120,  120,  120,  120,  120,  120,   37,
+ /*   980 */    38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
+ /*   990 */    48,   49,   50,    1,  120,  120,  120,  120,  120,  120,
+ /*  1000 */   120,  120,  120,  120,  120,   63,   64,   65,   66,   67,
+ /*  1010 */    68,   69,   70,   71,   72,   73,   74,  120,  120,  120,
+ /*  1020 */   120,  120,  120,  120,  120,  120,  120,  120,  120,   37,
+ /*  1030 */    38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
+ /*  1040 */    48,   49,   50,  120,  120,  120,  120,  120,  120,  120,
+ /*  1050 */   120,  120,  120,  120,  120,   63,   64,   65,   66,   67,
+ /*  1060 */    68,   69,   70,   71,   72,   73,   74,   37,   38,   39,
+ /*  1070 */    40,   41,   42,   43,   44,   45,   46,   47,   48,   49,
+ /*  1080 */    50,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*  1090 */   120,  120,  120,   63,   64,   65,   66,   67,   68,   69,
+ /*  1100 */    70,   71,   72,   73,   74,  120,  120,  120,  120,   15,
+ /*  1110 */   120,   17,   18,  120,   82,   21,  120,   23,   86,  120,
+ /*  1120 */    88,   89,   28,   91,  120,   31,   32,   33,  120,   35,
+ /*  1130 */    98,   99,   38,  120,  102,  103,  120,  105,  120,  120,
+ /*  1140 */   120,  120,  120,  120,  120,  120,  120,   53,   54,   55,
+ /*  1150 */    56,   57,  120,   82,   60,   61,   62,   86,  120,   88,
+ /*  1160 */   120,  120,   91,  120,   15,  120,   17,   18,  120,   75,
+ /*  1170 */    21,  120,   23,  102,  103,  120,  105,   28,   10,  120,
+ /*  1180 */    31,   32,   33,   15,   35,  120,  120,   38,  120,   21,
+ /*  1190 */   120,   23,  120,  120,  120,  120,   28,  120,  120,   31,
+ /*  1200 */    32,  120,   53,   54,   55,   56,   57,  120,   82,   60,
+ /*  1210 */    61,  120,   86,  120,   88,  120,  120,   91,  120,   15,
+ /*  1220 */    87,   17,   18,   90,   75,   21,  120,   23,  102,  103,
+ /*  1230 */    97,  105,   28,  100,  101,   31,   32,   33,  120,   35,
+ /*  1240 */   120,  120,   38,   75,   76,   77,  113,   82,    1,  120,
+ /*  1250 */   120,   86,  120,   88,  120,  120,   91,   53,   54,   55,
+ /*  1260 */    56,   57,  120,   16,   60,   61,  120,  102,  103,   22,
+ /*  1270 */   105,  120,  120,  120,   15,  120,   17,   18,  120,   75,
+ /*  1280 */    21,  120,   23,  120,  120,  120,  120,   28,   10,  120,
+ /*  1290 */    31,   32,   33,   15,   35,  120,  120,   38,   51,   21,
+ /*  1300 */   120,   23,  120,  120,  120,  120,   28,  120,  120,   31,
+ /*  1310 */    32,  120,   53,   54,   55,   56,   57,  120,   82,   60,
+ /*  1320 */    61,  120,   86,  120,   88,  120,  120,   91,  120,   15,
+ /*  1330 */    87,   17,   18,   90,   75,   21,  120,   23,  102,  103,
+ /*  1340 */    97,  105,   28,  100,  101,   31,   32,   33,  120,   35,
+ /*  1350 */   120,  120,   38,   75,   76,   77,  113,   82,    1,  120,
+ /*  1360 */   120,   86,  120,   88,  120,  120,   91,   53,   54,   55,
+ /*  1370 */    56,   57,  120,   16,   60,   61,  120,  102,  103,   22,
+ /*  1380 */   105,  120,  120,  120,   15,   87,   17,   18,   90,   75,
+ /*  1390 */    21,  120,   23,  120,    1,   97,  120,   28,  100,  101,
+ /*  1400 */    31,   32,   33,  120,   35,  120,  120,   38,   51,   16,
+ /*  1410 */   120,  113,   82,   20,  120,   22,   86,  120,   88,  120,
+ /*  1420 */   120,   91,   53,   54,   55,   56,   57,  120,   35,   60,
+ /*  1430 */    61,  120,  102,  103,  120,  105,  120,  120,  120,   15,
+ /*  1440 */    87,   17,   18,   90,   75,   21,  120,   23,  120,  120,
+ /*  1450 */    97,   58,   28,  100,  101,   31,   32,   33,  120,   35,
+ /*  1460 */   120,  120,   38,  120,  120,  120,  113,   82,  120,  120,
+ /*  1470 */   120,   86,  120,   88,  120,  120,   91,   53,   54,   55,
+ /*  1480 */    56,   57,  120,  120,   60,   61,  120,  102,  103,  120,
+ /*  1490 */   105,  120,  120,  120,   15,   87,   17,   18,   90,   75,
+ /*  1500 */    21,  120,   23,  120,  120,   97,  120,   28,  100,  101,
+ /*  1510 */    31,   32,   33,  120,   35,  120,   87,   38,  120,   90,
+ /*  1520 */   120,  113,  120,  120,  120,  120,   97,  120,  120,  100,
+ /*  1530 */   101,  120,   53,   54,   55,   56,   57,  120,  120,   60,
+ /*  1540 */    61,  120,  113,  120,  120,  120,  120,  120,  120,   15,
+ /*  1550 */   120,   17,   18,    1,   75,   21,  120,   23,  120,  120,
+ /*  1560 */   120,  120,   28,  120,  120,   31,   32,   33,   16,   35,
+ /*  1570 */   120,  120,   38,  120,   22,  120,  120,  120,  120,  120,
+ /*  1580 */   120,   29,  120,  120,  120,  120,  120,   53,   54,   55,
+ /*  1590 */    56,   57,  120,  120,   60,   61,  120,  120,  120,  120,
+ /*  1600 */   120,  120,  120,   51,   15,  120,   17,   18,  120,   75,
+ /*  1610 */    21,  120,   23,  120,  120,  120,  120,   28,  120,  120,
+ /*  1620 */    31,   32,   33,  120,   35,  120,  120,   38,  120,  120,
+ /*  1630 */   120,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*  1640 */   120,  120,   53,   54,   55,   56,   57,  120,  120,   60,
+ /*  1650 */    61,  120,  120,  120,  120,  120,  120,  120,  120,   15,
+ /*  1660 */   120,   17,   18,  120,   75,   21,  120,   23,  120,  120,
+ /*  1670 */   120,  120,   28,  120,  120,   31,   32,   33,  120,   35,
+ /*  1680 */   120,  120,   38,  120,  120,  120,  120,  120,  120,  120,
+ /*  1690 */   120,  120,  120,  120,  120,  120,  120,   53,   54,   55,
+ /*  1700 */    56,   57,  120,  120,   60,  120,  120,  120,  120,  120,
+ /*  1710 */   120,  120,  120,  120,   15,  120,   17,   18,  120,   75,
+ /*  1720 */    21,  120,   23,  120,  120,  120,  120,   28,  120,  120,
+ /*  1730 */    31,   32,   33,  120,   35,  120,  120,   38,  120,  120,
+ /*  1740 */   120,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*  1750 */   120,  120,   53,   54,   55,   56,   57,  120,  120,   60,
+ /*  1760 */   120,  120,  120,  120,  120,  120,  120,  120,  120,   16,
+ /*  1770 */   120,  120,  120,  120,   75,   22,  120,   24,  120,   26,
+ /*  1780 */    27,  120,   29,  120,  120,  120,  120,   34,   35,   36,
+ /*  1790 */   120,  120,  120,  120,  120,  120,  120,  120,  120,  120,
+ /*  1800 */   120,  120,  120,   82,   51,  120,  120,   86,  120,   88,
+ /*  1810 */    89,   58,   91,  120,  120,   62,  120,  120,  120,   98,
+ /*  1820 */    99,  120,  120,  102,  103,  120,  105,  120,  120,   76,
+ /*  1830 */   120,  120,  120,  120,  120,  120,   82,  116,  117,  120,
+ /*  1840 */    86,  120,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  1850 */   120,  120,   98,   99,  120,  120,  102,  103,   82,  105,
+ /*  1860 */   120,  120,   86,  120,   88,   89,  120,   91,  120,  120,
+ /*  1870 */   120,  117,  120,  120,   98,   99,  120,  120,  102,  103,
+ /*  1880 */   120,  105,  120,  120,  120,  120,   82,  120,  120,  120,
+ /*  1890 */    86,  120,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  1900 */   120,  120,   98,   99,  120,  120,  102,  103,  120,  105,
+ /*  1910 */   120,  120,   82,  120,  120,  120,   86,  120,   88,   89,
+ /*  1920 */   120,   91,  120,  120,  120,  120,  120,  120,   98,   99,
+ /*  1930 */   120,  120,  102,  103,  120,  105,   82,  120,  120,  120,
+ /*  1940 */    86,  120,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  1950 */   120,  120,   98,   99,   82,  120,  102,  103,   86,  105,
+ /*  1960 */    88,   89,  120,   91,  120,  120,  120,  120,  120,  120,
+ /*  1970 */    98,   99,  120,  120,  102,  103,   82,  105,  120,  120,
+ /*  1980 */    86,  120,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  1990 */   120,  120,   98,   99,  120,  120,  102,  103,  120,  105,
+ /*  2000 */   120,   82,  120,  120,  120,   86,  120,   88,   89,  120,
+ /*  2010 */    91,  120,  120,  120,  120,  120,  120,   98,   99,   82,
+ /*  2020 */   120,  102,  103,   86,  105,   88,   89,  120,   91,  120,
+ /*  2030 */   120,  120,  120,  120,  120,   98,   99,   82,  120,  102,
+ /*  2040 */   103,   86,  105,   88,   89,  120,   91,  120,  120,  120,
+ /*  2050 */   120,  120,  120,   98,   99,  120,  120,  102,  103,   82,
+ /*  2060 */   105,  120,  120,   86,  120,   88,   89,  120,   91,  120,
+ /*  2070 */   120,  120,  120,  120,  120,   98,   99,  120,  120,  102,
+ /*  2080 */   103,  120,  105,  120,   82,  120,  120,  120,   86,  120,
+ /*  2090 */    88,   89,  120,   91,  120,  120,  120,  120,  120,  120,
+ /*  2100 */    98,   99,   82,  120,  102,  103,   86,  105,   88,   89,
+ /*  2110 */   120,   91,  120,  120,  120,  120,  120,  120,   98,   99,
+ /*  2120 */    82,  120,  102,  103,   86,  105,   88,   89,  120,   91,
+ /*  2130 */   120,  120,  120,  120,  120,  120,   98,   99,  120,  120,
+ /*  2140 */   102,  103,   82,  105,  120,  120,   86,  120,   88,   89,
+ /*  2150 */   120,   91,  120,  120,  120,  120,  120,  120,   98,   99,
+ /*  2160 */   120,  120,  102,  103,  120,  105,  120,   82,  120,  120,
+ /*  2170 */   120,   86,  120,   88,   89,  120,   91,  120,  120,  120,
+ /*  2180 */   120,  120,  120,   98,   99,   82,  120,  102,  103,   86,
+ /*  2190 */   105,   88,   89,  120,   91,  120,  120,  120,  120,  120,
+ /*  2200 */   120,   98,   99,   82,  120,  102,  103,   86,  105,   88,
+ /*  2210 */    89,  120,   91,  120,  120,  120,  120,  120,  120,   98,
+ /*  2220 */    99,  120,  120,  102,  103,   82,  105,  120,  120,   86,
+ /*  2230 */   120,   88,   89,  120,   91,  120,  120,  120,  120,  120,
+ /*  2240 */   120,   98,   99,  120,  120,  102,  103,  120,  105,  120,
+ /*  2250 */    82,  120,  120,  120,   86,  120,   88,   89,  120,   91,
+ /*  2260 */   120,  120,  120,  120,  120,  120,   98,   99,   82,  120,
+ /*  2270 */   102,  103,   86,  105,   88,   89,  120,   91,  120,  120,
+ /*  2280 */   120,  120,  120,  120,   98,   99,   82,  120,  102,  103,
+ /*  2290 */    86,  105,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  2300 */   120,  120,   98,   99,  120,  120,  102,  103,   82,  105,
+ /*  2310 */   120,  120,   86,  120,   88,   89,  120,   91,  120,  120,
+ /*  2320 */   120,  120,  120,  120,   98,   99,  120,  120,  102,  103,
+ /*  2330 */   120,  105,  120,   82,  120,  120,  120,   86,  120,   88,
+ /*  2340 */    89,  120,   91,  120,  120,  120,  120,  120,  120,   98,
+ /*  2350 */    99,   82,  120,  102,  103,   86,  105,   88,   89,  120,
+ /*  2360 */    91,  120,  120,  120,  120,  120,  120,   98,   99,   82,
+ /*  2370 */   120,  102,  103,   86,  105,   88,   89,  120,   91,  120,
+ /*  2380 */   120,  120,  120,  120,  120,   98,   99,  120,  120,  102,
+ /*  2390 */   103,   82,  105,  120,  120,   86,  120,   88,   89,  120,
+ /*  2400 */    91,  120,  120,  120,  120,  120,  120,   98,   99,  120,
+ /*  2410 */   120,  102,  103,  120,  105,  120,   82,  120,  120,  120,
+ /*  2420 */    86,  120,   88,   89,  120,   91,  120,  120,  120,  120,
+ /*  2430 */   120,  120,   98,   99,   82,  120,  102,  103,   86,  105,
+ /*  2440 */    88,   89,  120,   91,  120,  120,  120,  120,  120,  120,
+ /*  2450 */    98,   99,   82,  120,  102,  103,   86,  105,   88,   89,
+ /*  2460 */   120,   91,  120,  120,  120,  120,  120,  120,   98,   99,
+ /*  2470 */   120,  120,  102,  103,   82,  105,  120,  120,   86,  120,
+ /*  2480 */    88,   89,  120,   91,  120,  120,  120,  120,  120,  120,
+ /*  2490 */    98,   99,  120,  120,  102,  103,  120,  105,  120,   82,
+ /*  2500 */   120,  120,  120,   86,  120,   88,   89,  120,   91,  120,
+ /*  2510 */   120,  120,  120,  120,  120,   98,   99,   82,  120,  102,
+ /*  2520 */   103,   86,  105,   88,   89,  120,   91,  120,  120,  120,
+ /*  2530 */   120,  120,  120,   98,   99,   82,  120,  102,  103,   86,
+ /*  2540 */   105,   88,   82,  120,   91,  120,   86,  120,   88,  120,
+ /*  2550 */   120,   91,   99,  120,  120,  102,  103,  120,  105,   99,
+ /*  2560 */   120,  120,  102,  103,  120,  105,
+);
+    const YY_SHIFT_USE_DFLT = -9;
+    const YY_SHIFT_MAX = 250;
+    static public $yy_shift_ofst = array(
+ /*     0 */     1, 1424, 1259, 1149, 1259, 1149, 1149, 1424, 1094, 1149,
+ /*    10 */  1149, 1479, 1149, 1589, 1534, 1149, 1149, 1149, 1314, 1149,
+ /*    20 */  1149, 1149, 1149, 1149, 1369, 1149, 1149, 1149, 1149, 1314,
+ /*    30 */  1149, 1149, 1149, 1149, 1149, 1149, 1149, 1149, 1149, 1149,
+ /*    40 */  1149, 1149, 1369, 1149, 1204, 1204, 1644, 1699, 1699, 1699,
+ /*    50 */  1699, 1699, 1699,  147,  221,   -1,   73,  718,  718,  718,
+ /*    60 */   768,  818,  644,  594,  345,  271,  419,  942,  469,  520,
+ /*    70 */   868,  992,  992,  992,  992,  992,  992,  992,  992,  992,
+ /*    80 */   992,  992,  992,  992,  992,  992,  992,  992,  992,  992,
+ /*    90 */  1030, 1030, 1393, 1357,  233,    1, 1278,  150,    5,  308,
+ /*   100 */   308,  311,  358,  310,  233,   44,  233, 1168,   46, 1552,
+ /*   110 */   263, 1247,  226,  231,   71,   16,   -2,  299,  182,  181,
+ /*   120 */   282,  163,  719,  459,  560,  532,   44,  460,  559,  581,
+ /*   130 */   460,  460,  460,   44,  563,  460,  632,  586,  548,  532,
+ /*   140 */   459,  460,  460,  460,  460,  701,  701,  701,  683,  700,
+ /*   150 */   701,  701,  701,  701,  684,  701,  684,   -9,   66,  111,
+ /*   160 */     3,  357,  359,  382,  381,  606,  439,  417,  509,  439,
+ /*   170 */   439,  530,  508,  458,  207,  431,  480,  507,  506,  483,
+ /*   180 */   439,  457,  432,  439,  529,  433,  385,  383,  727,  684,
+ /*   190 */   701,  684,  727,  701,  684,  538,  222,   -8,   -8,   -9,
+ /*   200 */    -9,   -9,   -9, 1753,   72,  116,  216,  120,  123,   74,
+ /*   210 */    74,  141,  552,   32,  315,  306,  329,   90,  413,  366,
+ /*   220 */   682,  616,  657,  659,  658,  655,  656,  661,  662,  641,
+ /*   230 */   681,  680,  640,  695,  654,  652,  621,  602,  571,  557,
+ /*   240 */   324,  538,  608,  307,  630,  595,  653,  619,  626,  623,
+ /*   250 */   598,
+);
+    const YY_REDUCE_USE_DFLT = -81;
+    const YY_REDUCE_MAX = 202;
+    static public $yy_reduce_ofst = array(
+ /*     0 */    22, 1721,   68,  339,  266,   -6,  142, 1754,  862, 2020,
+ /*    10 */   514, 1977, 1776, 2103, 1919, 1872, 1804, 1830, 2121, 2392,
+ /*    20 */  2186, 2287, 2269, 2168, 2435, 2143,  639, 1955, 2085, 1937,
+ /*    30 */  1032, 2060, 2038, 1894, 1854, 2002, 2352, 2417, 2370, 2334,
+ /*    40 */  2204, 2226, 2251, 2309, 2460, 2453, 1071, 1126, 1275, 1385,
+ /*    50 */  1330, 1236, 1165, 1353, 1408, 1243, 1429, 1298, 1133, 1353,
+ /*    60 */    61,   61,   61,   61,   61,   61,   61,   61,   61,   61,
+ /*    70 */    61,   61,   61,   61,   61,   61,   61,   61,   61,   61,
+ /*    80 */    61,   61,   61,   61,   61,   61,   61,   61,   61,   61,
+ /*    90 */    61,   61,  186,  139,  112,  -80,  214,  -10,  342,  337,
+ /*   100 */   269,   96,  309,  335,  384,  210,  407,  490,  -57,  -35,
+ /*   110 */   270,  -35,  -35,  628,  270,  270,  170,  -35,  528,  170,
+ /*   120 */   528,  466,  -35,  444,  170,  513,  435,  561,  519,  463,
+ /*   130 */   519,  637,  395,  537,  861,  631,  533,  519,  -35,  519,
+ /*   140 */   542,  860,  685,  638,  503,  -35,  -35,  -35,  346,  349,
+ /*   150 */   -35,  -35,  -35,  -35,  170,  -35,  196,  -35,  635,  636,
+ /*   160 */   604,  604,  604,  604,  604,  618,  599,  642,  604,  599,
+ /*   170 */   599,  604,  604,  604,  618,  604,  604,  604,  604,  604,
+ /*   180 */   599,  604,  604,  599,  604,  604,  604,  604,  634,  624,
+ /*   190 */   618,  624,  634,  618,  624,  617,   43,  -64,  159,   21,
+ /*   200 */   169,  151,  183,
+);
+    static public $yyExpectedTokens = array(
+        /* 0 */ array(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 21, 23, 28, 31, 32, ),
+        /* 1 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 2 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 3 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 4 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 5 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 6 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 7 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 8 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 62, 75, ),
+        /* 9 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 10 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 11 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 12 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 13 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 14 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 15 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 16 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 17 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 18 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 19 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 20 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 21 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 22 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 23 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 24 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 25 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 26 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 27 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 28 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 29 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 30 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 31 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 32 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 33 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 34 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 35 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 36 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 37 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 38 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 39 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 40 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 41 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 42 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 43 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 44 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 45 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 61, 75, ),
+        /* 46 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 47 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 48 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 49 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 50 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 51 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 52 */ array(15, 17, 18, 21, 23, 28, 31, 32, 33, 35, 38, 53, 54, 55, 56, 57, 60, 75, ),
+        /* 53 */ array(1, 16, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 54 */ array(1, 16, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 55 */ array(1, 16, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 56 */ array(1, 22, 27, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 57 */ array(1, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 58 */ array(1, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 59 */ array(1, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 60 */ array(1, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 61 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 62 */ array(1, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 63 */ array(1, 16, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 64 */ array(1, 16, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 65 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, ),
+        /* 66 */ array(1, 16, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 67 */ array(1, 16, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 68 */ array(1, 29, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 69 */ array(1, 24, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 70 */ array(1, 2, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 71 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 72 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 73 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 74 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 75 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 76 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 77 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 78 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 79 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 80 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 81 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 82 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 83 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 84 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 85 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 86 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 87 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 88 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 89 */ array(1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 90 */ array(37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 91 */ array(37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ),
+        /* 92 */ array(1, 16, 20, 22, 35, 58, ),
+        /* 93 */ array(1, 16, 22, 51, ),
+        /* 94 */ array(1, 22, ),
+        /* 95 */ array(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 21, 23, 28, 31, 32, ),
+        /* 96 */ array(10, 15, 21, 23, 28, 31, 32, 75, 76, 77, ),
+        /* 97 */ array(15, 18, 22, 30, ),
+        /* 98 */ array(15, 18, 22, 30, ),
+        /* 99 */ array(15, 18, 22, ),
+        /* 100 */ array(15, 18, 22, ),
+        /* 101 */ array(20, 56, 61, ),
+        /* 102 */ array(1, 16, 22, ),
+        /* 103 */ array(17, 18, 60, ),
+        /* 104 */ array(1, 22, ),
+        /* 105 */ array(17, 35, ),
+        /* 106 */ array(1, 22, ),
+        /* 107 */ array(10, 15, 21, 23, 28, 31, 32, 75, 76, 77, ),
+        /* 108 */ array(4, 5, 6, 7, 8, 12, 13, 14, ),
+        /* 109 */ array(1, 16, 22, 29, 51, ),
+        /* 110 */ array(15, 18, 19, 59, ),
+        /* 111 */ array(1, 16, 22, 51, ),
+        /* 112 */ array(1, 16, 22, 51, ),
+        /* 113 */ array(15, 18, 19, 25, ),
+        /* 114 */ array(15, 18, 19, ),
+        /* 115 */ array(15, 18, 59, ),
+        /* 116 */ array(19, 20, 58, ),
+        /* 117 */ array(1, 30, 51, ),
+        /* 118 */ array(16, 22, 25, ),
+        /* 119 */ array(19, 20, 58, ),
+        /* 120 */ array(16, 22, 25, ),
+        /* 121 */ array(1, 16, 20, ),
+        /* 122 */ array(1, 16, 51, ),
+        /* 123 */ array(17, 18, ),
+        /* 124 */ array(20, 58, ),
+        /* 125 */ array(15, 18, ),
+        /* 126 */ array(17, 35, ),
+        /* 127 */ array(15, 18, ),
+        /* 128 */ array(15, 18, ),
+        /* 129 */ array(1, 16, ),
+        /* 130 */ array(15, 18, ),
+        /* 131 */ array(15, 18, ),
+        /* 132 */ array(15, 18, ),
+        /* 133 */ array(17, 35, ),
+        /* 134 */ array(15, 18, ),
+        /* 135 */ array(15, 18, ),
+        /* 136 */ array(16, 22, ),
+        /* 137 */ array(15, 18, ),
+        /* 138 */ array(1, 51, ),
+        /* 139 */ array(15, 18, ),
+        /* 140 */ array(17, 18, ),
+        /* 141 */ array(15, 18, ),
+        /* 142 */ array(15, 18, ),
+        /* 143 */ array(15, 18, ),
+        /* 144 */ array(15, 18, ),
+        /* 145 */ array(1, ),
+        /* 146 */ array(1, ),
+        /* 147 */ array(1, ),
+        /* 148 */ array(22, ),
+        /* 149 */ array(22, ),
+        /* 150 */ array(1, ),
+        /* 151 */ array(1, ),
+        /* 152 */ array(1, ),
+        /* 153 */ array(1, ),
+        /* 154 */ array(20, ),
+        /* 155 */ array(1, ),
+        /* 156 */ array(20, ),
+        /* 157 */ array(),
+        /* 158 */ array(15, 18, 59, ),
+        /* 159 */ array(15, 17, 18, ),
+        /* 160 */ array(16, 22, ),
+        /* 161 */ array(16, 22, ),
+        /* 162 */ array(16, 22, ),
+        /* 163 */ array(16, 22, ),
+        /* 164 */ array(16, 22, ),
+        /* 165 */ array(1, 16, ),
+        /* 166 */ array(56, 61, ),
+        /* 167 */ array(15, 35, ),
+        /* 168 */ array(16, 22, ),
+        /* 169 */ array(56, 61, ),
+        /* 170 */ array(56, 61, ),
+        /* 171 */ array(16, 22, ),
+        /* 172 */ array(16, 22, ),
+        /* 173 */ array(16, 22, ),
+        /* 174 */ array(1, 16, ),
+        /* 175 */ array(16, 22, ),
+        /* 176 */ array(16, 22, ),
+        /* 177 */ array(16, 22, ),
+        /* 178 */ array(16, 22, ),
+        /* 179 */ array(16, 22, ),
+        /* 180 */ array(56, 61, ),
+        /* 181 */ array(16, 22, ),
+        /* 182 */ array(16, 22, ),
+        /* 183 */ array(56, 61, ),
+        /* 184 */ array(16, 22, ),
+        /* 185 */ array(16, 22, ),
+        /* 186 */ array(16, 22, ),
+        /* 187 */ array(16, 22, ),
+        /* 188 */ array(2, ),
+        /* 189 */ array(20, ),
+        /* 190 */ array(1, ),
+        /* 191 */ array(20, ),
+        /* 192 */ array(2, ),
+        /* 193 */ array(1, ),
+        /* 194 */ array(20, ),
+        /* 195 */ array(35, ),
+        /* 196 */ array(13, ),
+        /* 197 */ array(22, ),
+        /* 198 */ array(22, ),
+        /* 199 */ array(),
+        /* 200 */ array(),
+        /* 201 */ array(),
+        /* 202 */ array(),
+        /* 203 */ array(16, 22, 24, 26, 27, 29, 34, 35, 36, 51, 58, 62, 76, ),
+        /* 204 */ array(16, 19, 22, 35, 58, ),
+        /* 205 */ array(15, 17, 18, 33, ),
+        /* 206 */ array(16, 22, 35, 58, ),
+        /* 207 */ array(35, 56, 58, 62, ),
+        /* 208 */ array(30, 35, 58, ),
+        /* 209 */ array(35, 58, ),
+        /* 210 */ array(35, 58, ),
+        /* 211 */ array(34, 36, ),
+        /* 212 */ array(2, 19, ),
+        /* 213 */ array(25, 76, ),
+        /* 214 */ array(34, 36, ),
+        /* 215 */ array(19, 56, ),
+        /* 216 */ array(24, 34, ),
+        /* 217 */ array(34, 62, ),
+        /* 218 */ array(18, 59, ),
+        /* 219 */ array(34, 36, ),
+        /* 220 */ array(18, ),
+        /* 221 */ array(60, ),
+        /* 222 */ array(22, ),
+        /* 223 */ array(18, ),
+        /* 224 */ array(17, ),
+        /* 225 */ array(18, ),
+        /* 226 */ array(18, ),
+        /* 227 */ array(17, ),
+        /* 228 */ array(33, ),
+        /* 229 */ array(62, ),
+        /* 230 */ array(18, ),
+        /* 231 */ array(18, ),
+        /* 232 */ array(56, ),
+        /* 233 */ array(2, ),
+        /* 234 */ array(18, ),
+        /* 235 */ array(19, ),
+        /* 236 */ array(2, ),
+        /* 237 */ array(22, ),
+        /* 238 */ array(35, ),
+        /* 239 */ array(18, ),
+        /* 240 */ array(36, ),
+        /* 241 */ array(35, ),
+        /* 242 */ array(18, ),
+        /* 243 */ array(17, ),
+        /* 244 */ array(17, ),
+        /* 245 */ array(60, ),
+        /* 246 */ array(17, ),
+        /* 247 */ array(33, ),
+        /* 248 */ array(25, ),
+        /* 249 */ array(26, ),
+        /* 250 */ array(52, ),
+        /* 251 */ array(),
+        /* 252 */ array(),
+        /* 253 */ array(),
+        /* 254 */ array(),
+        /* 255 */ array(),
+        /* 256 */ array(),
+        /* 257 */ array(),
+        /* 258 */ array(),
+        /* 259 */ array(),
+        /* 260 */ array(),
+        /* 261 */ array(),
+        /* 262 */ array(),
+        /* 263 */ array(),
+        /* 264 */ array(),
+        /* 265 */ array(),
+        /* 266 */ array(),
+        /* 267 */ array(),
+        /* 268 */ array(),
+        /* 269 */ array(),
+        /* 270 */ array(),
+        /* 271 */ array(),
+        /* 272 */ array(),
+        /* 273 */ array(),
+        /* 274 */ array(),
+        /* 275 */ array(),
+        /* 276 */ array(),
+        /* 277 */ array(),
+        /* 278 */ array(),
+        /* 279 */ array(),
+        /* 280 */ array(),
+        /* 281 */ array(),
+        /* 282 */ array(),
+        /* 283 */ array(),
+        /* 284 */ array(),
+        /* 285 */ array(),
+        /* 286 */ array(),
+        /* 287 */ array(),
+        /* 288 */ array(),
+        /* 289 */ array(),
+        /* 290 */ array(),
+        /* 291 */ array(),
+        /* 292 */ array(),
+        /* 293 */ array(),
+        /* 294 */ array(),
+        /* 295 */ array(),
+        /* 296 */ array(),
+        /* 297 */ array(),
+        /* 298 */ array(),
+        /* 299 */ array(),
+        /* 300 */ array(),
+        /* 301 */ array(),
+        /* 302 */ array(),
+        /* 303 */ array(),
+        /* 304 */ array(),
+        /* 305 */ array(),
+        /* 306 */ array(),
+        /* 307 */ array(),
+        /* 308 */ array(),
+        /* 309 */ array(),
+        /* 310 */ array(),
+        /* 311 */ array(),
+        /* 312 */ array(),
+        /* 313 */ array(),
+        /* 314 */ array(),
+        /* 315 */ array(),
+        /* 316 */ array(),
+        /* 317 */ array(),
+        /* 318 */ array(),
+        /* 319 */ array(),
+        /* 320 */ array(),
+        /* 321 */ array(),
+        /* 322 */ array(),
+        /* 323 */ array(),
+        /* 324 */ array(),
+        /* 325 */ array(),
+        /* 326 */ array(),
+        /* 327 */ array(),
+        /* 328 */ array(),
+        /* 329 */ array(),
+        /* 330 */ array(),
+        /* 331 */ array(),
+        /* 332 */ array(),
+        /* 333 */ array(),
+        /* 334 */ array(),
+        /* 335 */ array(),
+        /* 336 */ array(),
+        /* 337 */ array(),
+        /* 338 */ array(),
+        /* 339 */ array(),
+        /* 340 */ array(),
+        /* 341 */ array(),
+        /* 342 */ array(),
+        /* 343 */ array(),
+        /* 344 */ array(),
+        /* 345 */ array(),
+        /* 346 */ array(),
+        /* 347 */ array(),
+        /* 348 */ array(),
+        /* 349 */ array(),
+        /* 350 */ array(),
+        /* 351 */ array(),
+        /* 352 */ array(),
+        /* 353 */ array(),
+        /* 354 */ array(),
+        /* 355 */ array(),
+        /* 356 */ array(),
+        /* 357 */ array(),
+        /* 358 */ array(),
+        /* 359 */ array(),
+        /* 360 */ array(),
+        /* 361 */ array(),
+        /* 362 */ array(),
+        /* 363 */ array(),
+        /* 364 */ array(),
+        /* 365 */ array(),
+        /* 366 */ array(),
+        /* 367 */ array(),
+        /* 368 */ array(),
+        /* 369 */ array(),
+        /* 370 */ array(),
+        /* 371 */ array(),
+        /* 372 */ array(),
+        /* 373 */ array(),
+        /* 374 */ array(),
+        /* 375 */ array(),
+        /* 376 */ array(),
+        /* 377 */ array(),
+        /* 378 */ array(),
+        /* 379 */ array(),
+        /* 380 */ array(),
+        /* 381 */ array(),
+        /* 382 */ array(),
+);
+    static public $yy_default = array(
+ /*     0 */   386,  565,  582,  536,  582,  536,  536,  582,  582,  582,
+ /*    10 */   582,  582,  582,  582,  582,  582,  582,  582,  582,  582,
+ /*    20 */   582,  582,  582,  582,  582,  582,  582,  582,  582,  582,
+ /*    30 */   582,  582,  582,  582,  582,  582,  582,  582,  582,  582,
+ /*    40 */   582,  582,  582,  582,  582,  582,  582,  582,  582,  582,
+ /*    50 */   582,  582,  582,  582,  582,  582,  444,  444,  444,  444,
+ /*    60 */   582,  582,  582,  582,  582,  582,  582,  582,  449,  582,
+ /*    70 */   582,  446,  535,  428,  534,  566,  567,  478,  477,  468,
+ /*    80 */   465,  449,  568,  455,  469,  474,  473,  470,  454,  451,
+ /*    90 */   482,  481,  493,  457,  444,  383,  582,  444,  444,  444,
+ /*   100 */   444,  548,  464,  582,  444,  582,  444,  582,  582,  457,
+ /*   110 */   509,  457,  457,  582,  509,  509,  502,  457,  483,  502,
+ /*   120 */   483,  582,  457,  582,  502,  582,  582,  582,  582,  582,
+ /*   130 */   582,  582,  509,  582,  582,  582,  582,  582,  457,  582,
+ /*   140 */   582,  582,  582,  582,  582,  480,  461,  484,  444,  444,
+ /*   150 */   486,  485,  462,  467,  502,  460,  545,  543,  510,  582,
+ /*   160 */   582,  582,  582,  582,  582,  582,  527,  509,  582,  526,
+ /*   170 */   529,  582,  582,  582,  582,  582,  582,  582,  582,  582,
+ /*   180 */   507,  582,  582,  528,  582,  582,  582,  582,  537,  523,
+ /*   190 */   501,  546,  538,  464,  549,  509,  401,  581,  581,  542,
+ /*   200 */   509,  509,  542,  459,  493,  582,  493,  493,  493,  493,
+ /*   210 */   479,  582,  521,  483,  582,  489,  582,  582,  582,  582,
+ /*   220 */   582,  582,  582,  582,  582,  582,  582,  582,  491,  582,
+ /*   230 */   582,  582,  489,  521,  582,  582,  521,  582,  547,  582,
+ /*   240 */   582,  521,  582,  582,  582,  582,  582,  582,  483,  452,
+ /*   250 */   495,  544,  514,  516,  515,  518,  495,  531,  532,  409,
+ /*   260 */   395,  431,  394,  425,  396,  430,  398,  456,  397,  426,
+ /*   270 */   393,  387,  385,  384,  388,  389,  392,  391,  390,  399,
+ /*   280 */   400,  435,  442,  436,  410,  423,  424,  441,  443,  522,
+ /*   290 */   408,  407,  404,  403,  402,  405,  453,  429,  580,  406,
+ /*   300 */   440,  573,  558,  559,  560,  561,  557,  556,  553,  554,
+ /*   310 */   555,  463,  492,  540,  539,  411,  525,  488,  490,  500,
+ /*   320 */   504,  552,  551,  508,  503,  459,  466,  499,  496,  497,
+ /*   330 */   511,  471,  472,  498,  513,  550,  512,  476,  519,  458,
+ /*   340 */   475,  576,  487,  575,  578,  579,  572,  574,  494,  434,
+ /*   350 */   570,  571,  433,  562,  530,  505,  506,  427,  533,  564,
+ /*   360 */   563,  432,  569,  421,  415,  416,  439,  414,  541,  412,
+ /*   370 */   413,  577,  438,  417,  521,  422,  520,  420,  437,  524,
+ /*   380 */   418,  419,  517,
+);
+    const YYNOCODE = 121;
+    const YYSTACKDEPTH = 100;
+    const YYNSTATE = 383;
+    const YYNRULE = 199;
+    const YYERRORSYMBOL = 78;
+    const YYERRSYMDT = 'yy0';
+    const YYFALLBACK = 0;
+    static public $yyFallback = array(
+    );
+    static function Trace($TraceFILE, $zTracePrompt)
+    {
+        if (!$TraceFILE) {
+            $zTracePrompt = 0;
+        } elseif (!$zTracePrompt) {
+            $TraceFILE = 0;
+        }
+        self::$yyTraceFILE = $TraceFILE;
+        self::$yyTracePrompt = $zTracePrompt;
+    }
+
+    static function PrintTrace()
+    {
+        self::$yyTraceFILE = fopen('php://output', 'w');
+        self::$yyTracePrompt = '<br>';
+    }
+
+    static public $yyTraceFILE;
+    static public $yyTracePrompt;
+    public $yyidx;                    /* Index of top element in stack */
+    public $yyerrcnt;                 /* Shifts left before out of the error */
+    public $yystack = array();  /* The parser's stack */
+
+    public $yyTokenName = array( 
+  '$',             'VERT',          'COLON',         'COMMENT',     
+  'PHPSTARTTAG',   'PHPENDTAG',     'ASPSTARTTAG',   'ASPENDTAG',   
+  'FAKEPHPSTARTTAG',  'XMLTAG',        'OTHER',         'LINEBREAK',   
+  'LITERALSTART',  'LITERALEND',    'LITERAL',       'LDEL',        
+  'RDEL',          'DOLLAR',        'ID',            'EQUAL',       
+  'PTR',           'LDELIF',        'SPACE',         'LDELFOR',     
+  'SEMICOLON',     'INCDEC',        'TO',            'STEP',        
+  'LDELFOREACH',   'AS',            'APTR',          'SMARTYBLOCKCHILD',
+  'LDELSLASH',     'INTEGER',       'COMMA',         'OPENP',       
+  'CLOSEP',        'MATH',          'UNIMATH',       'ANDSYM',      
+  'ISIN',          'ISDIVBY',       'ISNOTDIVBY',    'ISEVEN',      
+  'ISNOTEVEN',     'ISEVENBY',      'ISNOTEVENBY',   'ISODD',       
+  'ISNOTODD',      'ISODDBY',       'ISNOTODDBY',    'INSTANCEOF',  
+  'QMARK',         'NOT',           'TYPECAST',      'HEX',         
+  'DOT',           'SINGLEQUOTESTRING',  'DOUBLECOLON',   'AT',          
+  'HATCH',         'OPENB',         'CLOSEB',        'EQUALS',      
+  'NOTEQUALS',     'GREATERTHAN',   'LESSTHAN',      'GREATEREQUAL',
+  'LESSEQUAL',     'IDENTITY',      'NONEIDENTITY',  'MOD',         
+  'LAND',          'LOR',           'LXOR',          'QUOTE',       
+  'BACKTICK',      'DOLLARID',      'error',         'start',       
+  'template',      'template_element',  'smartytag',     'literal',     
+  'literal_elements',  'literal_element',  'value',         'attributes',  
+  'variable',      'expr',          'modifierlist',  'varindexed',  
+  'statement',     'statements',    'optspace',      'varvar',      
+  'foraction',     'attribute',     'ternary',       'array',       
+  'ifcond',        'lop',           'function',      'doublequoted_with_quotes',
+  'static_class_access',  'object',        'arrayindex',    'indexdef',    
+  'varvarele',     'objectchain',   'objectelement',  'method',      
+  'params',        'modifier',      'modparameters',  'modparameter',
+  'arrayelements',  'arrayelement',  'doublequoted',  'doublequotedcontent',
+    );
+
+    static public $yyRuleName = array(
+ /*   0 */ "start ::= template",
+ /*   1 */ "template ::= template_element",
+ /*   2 */ "template ::= template template_element",
+ /*   3 */ "template ::=",
+ /*   4 */ "template_element ::= smartytag",
+ /*   5 */ "template_element ::= COMMENT",
+ /*   6 */ "template_element ::= literal",
+ /*   7 */ "template_element ::= PHPSTARTTAG",
+ /*   8 */ "template_element ::= PHPENDTAG",
+ /*   9 */ "template_element ::= ASPSTARTTAG",
+ /*  10 */ "template_element ::= ASPENDTAG",
+ /*  11 */ "template_element ::= FAKEPHPSTARTTAG",
+ /*  12 */ "template_element ::= XMLTAG",
+ /*  13 */ "template_element ::= OTHER",
+ /*  14 */ "template_element ::= LINEBREAK",
+ /*  15 */ "literal ::= LITERALSTART LITERALEND",
+ /*  16 */ "literal ::= LITERALSTART literal_elements LITERALEND",
+ /*  17 */ "literal_elements ::= literal_elements literal_element",
+ /*  18 */ "literal_elements ::=",
+ /*  19 */ "literal_element ::= literal",
+ /*  20 */ "literal_element ::= LITERAL",
+ /*  21 */ "literal_element ::= PHPSTARTTAG",
+ /*  22 */ "literal_element ::= FAKEPHPSTARTTAG",
+ /*  23 */ "literal_element ::= PHPENDTAG",
+ /*  24 */ "literal_element ::= ASPSTARTTAG",
+ /*  25 */ "literal_element ::= ASPENDTAG",
+ /*  26 */ "smartytag ::= LDEL value RDEL",
+ /*  27 */ "smartytag ::= LDEL value attributes RDEL",
+ /*  28 */ "smartytag ::= LDEL variable attributes RDEL",
+ /*  29 */ "smartytag ::= LDEL expr modifierlist attributes RDEL",
+ /*  30 */ "smartytag ::= LDEL expr attributes RDEL",
+ /*  31 */ "smartytag ::= LDEL DOLLAR ID EQUAL value RDEL",
+ /*  32 */ "smartytag ::= LDEL DOLLAR ID EQUAL expr RDEL",
+ /*  33 */ "smartytag ::= LDEL DOLLAR ID EQUAL expr attributes RDEL",
+ /*  34 */ "smartytag ::= LDEL varindexed EQUAL expr attributes RDEL",
+ /*  35 */ "smartytag ::= LDEL ID attributes RDEL",
+ /*  36 */ "smartytag ::= LDEL ID RDEL",
+ /*  37 */ "smartytag ::= LDEL ID PTR ID attributes RDEL",
+ /*  38 */ "smartytag ::= LDEL ID modifierlist attributes RDEL",
+ /*  39 */ "smartytag ::= LDEL ID PTR ID modifierlist attributes RDEL",
+ /*  40 */ "smartytag ::= LDELIF SPACE expr RDEL",
+ /*  41 */ "smartytag ::= LDELIF SPACE expr attributes RDEL",
+ /*  42 */ "smartytag ::= LDELIF SPACE statement RDEL",
+ /*  43 */ "smartytag ::= LDELIF SPACE statement attributes RDEL",
+ /*  44 */ "smartytag ::= LDELFOR SPACE statements SEMICOLON optspace expr SEMICOLON optspace DOLLAR varvar foraction attributes RDEL",
+ /*  45 */ "foraction ::= EQUAL expr",
+ /*  46 */ "foraction ::= INCDEC",
+ /*  47 */ "smartytag ::= LDELFOR SPACE statement TO expr attributes RDEL",
+ /*  48 */ "smartytag ::= LDELFOR SPACE statement TO expr STEP expr attributes RDEL",
+ /*  49 */ "smartytag ::= LDELFOREACH attributes RDEL",
+ /*  50 */ "smartytag ::= LDELFOREACH SPACE value AS DOLLAR varvar attributes RDEL",
+ /*  51 */ "smartytag ::= LDELFOREACH SPACE value AS DOLLAR varvar APTR DOLLAR varvar attributes RDEL",
+ /*  52 */ "smartytag ::= LDELFOREACH SPACE expr AS DOLLAR varvar attributes RDEL",
+ /*  53 */ "smartytag ::= LDELFOREACH SPACE expr AS DOLLAR varvar APTR DOLLAR varvar attributes RDEL",
+ /*  54 */ "smartytag ::= SMARTYBLOCKCHILD",
+ /*  55 */ "smartytag ::= LDELSLASH ID RDEL",
+ /*  56 */ "smartytag ::= LDELSLASH ID modifierlist RDEL",
+ /*  57 */ "smartytag ::= LDELSLASH ID PTR ID RDEL",
+ /*  58 */ "smartytag ::= LDELSLASH ID PTR ID modifierlist RDEL",
+ /*  59 */ "attributes ::= attributes attribute",
+ /*  60 */ "attributes ::= attribute",
+ /*  61 */ "attributes ::=",
+ /*  62 */ "attribute ::= SPACE ID EQUAL ID",
+ /*  63 */ "attribute ::= SPACE ID EQUAL expr",
+ /*  64 */ "attribute ::= SPACE ID EQUAL value",
+ /*  65 */ "attribute ::= SPACE ID",
+ /*  66 */ "attribute ::= SPACE expr",
+ /*  67 */ "attribute ::= SPACE value",
+ /*  68 */ "attribute ::= SPACE INTEGER EQUAL expr",
+ /*  69 */ "statements ::= statement",
+ /*  70 */ "statements ::= statements COMMA statement",
+ /*  71 */ "statement ::= DOLLAR varvar EQUAL expr",
+ /*  72 */ "statement ::= varindexed EQUAL expr",
+ /*  73 */ "statement ::= OPENP statement CLOSEP",
+ /*  74 */ "expr ::= value",
+ /*  75 */ "expr ::= ternary",
+ /*  76 */ "expr ::= DOLLAR ID COLON ID",
+ /*  77 */ "expr ::= expr MATH value",
+ /*  78 */ "expr ::= expr UNIMATH value",
+ /*  79 */ "expr ::= expr ANDSYM value",
+ /*  80 */ "expr ::= array",
+ /*  81 */ "expr ::= expr modifierlist",
+ /*  82 */ "expr ::= expr ifcond expr",
+ /*  83 */ "expr ::= expr ISIN array",
+ /*  84 */ "expr ::= expr ISIN value",
+ /*  85 */ "expr ::= expr lop expr",
+ /*  86 */ "expr ::= expr ISDIVBY expr",
+ /*  87 */ "expr ::= expr ISNOTDIVBY expr",
+ /*  88 */ "expr ::= expr ISEVEN",
+ /*  89 */ "expr ::= expr ISNOTEVEN",
+ /*  90 */ "expr ::= expr ISEVENBY expr",
+ /*  91 */ "expr ::= expr ISNOTEVENBY expr",
+ /*  92 */ "expr ::= expr ISODD",
+ /*  93 */ "expr ::= expr ISNOTODD",
+ /*  94 */ "expr ::= expr ISODDBY expr",
+ /*  95 */ "expr ::= expr ISNOTODDBY expr",
+ /*  96 */ "expr ::= value INSTANCEOF ID",
+ /*  97 */ "expr ::= value INSTANCEOF value",
+ /*  98 */ "ternary ::= OPENP expr CLOSEP QMARK DOLLAR ID COLON expr",
+ /*  99 */ "ternary ::= OPENP expr CLOSEP QMARK expr COLON expr",
+ /* 100 */ "value ::= variable",
+ /* 101 */ "value ::= UNIMATH value",
+ /* 102 */ "value ::= NOT value",
+ /* 103 */ "value ::= TYPECAST value",
+ /* 104 */ "value ::= variable INCDEC",
+ /* 105 */ "value ::= HEX",
+ /* 106 */ "value ::= INTEGER",
+ /* 107 */ "value ::= INTEGER DOT INTEGER",
+ /* 108 */ "value ::= INTEGER DOT",
+ /* 109 */ "value ::= DOT INTEGER",
+ /* 110 */ "value ::= ID",
+ /* 111 */ "value ::= function",
+ /* 112 */ "value ::= OPENP expr CLOSEP",
+ /* 113 */ "value ::= SINGLEQUOTESTRING",
+ /* 114 */ "value ::= doublequoted_with_quotes",
+ /* 115 */ "value ::= ID DOUBLECOLON static_class_access",
+ /* 116 */ "value ::= varindexed DOUBLECOLON static_class_access",
+ /* 117 */ "value ::= smartytag",
+ /* 118 */ "value ::= value modifierlist",
+ /* 119 */ "variable ::= varindexed",
+ /* 120 */ "variable ::= DOLLAR varvar AT ID",
+ /* 121 */ "variable ::= object",
+ /* 122 */ "variable ::= HATCH ID HATCH",
+ /* 123 */ "variable ::= HATCH variable HATCH",
+ /* 124 */ "varindexed ::= DOLLAR varvar arrayindex",
+ /* 125 */ "arrayindex ::= arrayindex indexdef",
+ /* 126 */ "arrayindex ::=",
+ /* 127 */ "indexdef ::= DOT DOLLAR varvar",
+ /* 128 */ "indexdef ::= DOT DOLLAR varvar AT ID",
+ /* 129 */ "indexdef ::= DOT ID",
+ /* 130 */ "indexdef ::= DOT INTEGER",
+ /* 131 */ "indexdef ::= DOT LDEL expr RDEL",
+ /* 132 */ "indexdef ::= OPENB ID CLOSEB",
+ /* 133 */ "indexdef ::= OPENB ID DOT ID CLOSEB",
+ /* 134 */ "indexdef ::= OPENB expr CLOSEB",
+ /* 135 */ "indexdef ::= OPENB CLOSEB",
+ /* 136 */ "varvar ::= varvarele",
+ /* 137 */ "varvar ::= varvar varvarele",
+ /* 138 */ "varvarele ::= ID",
+ /* 139 */ "varvarele ::= LDEL expr RDEL",
+ /* 140 */ "object ::= varindexed objectchain",
+ /* 141 */ "objectchain ::= objectelement",
+ /* 142 */ "objectchain ::= objectchain objectelement",
+ /* 143 */ "objectelement ::= PTR ID arrayindex",
+ /* 144 */ "objectelement ::= PTR DOLLAR varvar arrayindex",
+ /* 145 */ "objectelement ::= PTR LDEL expr RDEL arrayindex",
+ /* 146 */ "objectelement ::= PTR ID LDEL expr RDEL arrayindex",
+ /* 147 */ "objectelement ::= PTR method",
+ /* 148 */ "function ::= ID OPENP params CLOSEP",
+ /* 149 */ "method ::= ID OPENP params CLOSEP",
+ /* 150 */ "method ::= DOLLAR ID OPENP params CLOSEP",
+ /* 151 */ "params ::= params COMMA expr",
+ /* 152 */ "params ::= expr",
+ /* 153 */ "params ::=",
+ /* 154 */ "modifierlist ::= modifierlist modifier modparameters",
+ /* 155 */ "modifierlist ::= modifier modparameters",
+ /* 156 */ "modifier ::= VERT AT ID",
+ /* 157 */ "modifier ::= VERT ID",
+ /* 158 */ "modparameters ::= modparameters modparameter",
+ /* 159 */ "modparameters ::=",
+ /* 160 */ "modparameter ::= COLON value",
+ /* 161 */ "modparameter ::= COLON array",
+ /* 162 */ "static_class_access ::= method",
+ /* 163 */ "static_class_access ::= method objectchain",
+ /* 164 */ "static_class_access ::= ID",
+ /* 165 */ "static_class_access ::= DOLLAR ID arrayindex",
+ /* 166 */ "static_class_access ::= DOLLAR ID arrayindex objectchain",
+ /* 167 */ "ifcond ::= EQUALS",
+ /* 168 */ "ifcond ::= NOTEQUALS",
+ /* 169 */ "ifcond ::= GREATERTHAN",
+ /* 170 */ "ifcond ::= LESSTHAN",
+ /* 171 */ "ifcond ::= GREATEREQUAL",
+ /* 172 */ "ifcond ::= LESSEQUAL",
+ /* 173 */ "ifcond ::= IDENTITY",
+ /* 174 */ "ifcond ::= NONEIDENTITY",
+ /* 175 */ "ifcond ::= MOD",
+ /* 176 */ "lop ::= LAND",
+ /* 177 */ "lop ::= LOR",
+ /* 178 */ "lop ::= LXOR",
+ /* 179 */ "array ::= OPENB arrayelements CLOSEB",
+ /* 180 */ "arrayelements ::= arrayelement",
+ /* 181 */ "arrayelements ::= arrayelements COMMA arrayelement",
+ /* 182 */ "arrayelements ::=",
+ /* 183 */ "arrayelement ::= value APTR expr",
+ /* 184 */ "arrayelement ::= ID APTR expr",
+ /* 185 */ "arrayelement ::= expr",
+ /* 186 */ "doublequoted_with_quotes ::= QUOTE QUOTE",
+ /* 187 */ "doublequoted_with_quotes ::= QUOTE doublequoted QUOTE",
+ /* 188 */ "doublequoted ::= doublequoted doublequotedcontent",
+ /* 189 */ "doublequoted ::= doublequotedcontent",
+ /* 190 */ "doublequotedcontent ::= BACKTICK variable BACKTICK",
+ /* 191 */ "doublequotedcontent ::= BACKTICK expr BACKTICK",
+ /* 192 */ "doublequotedcontent ::= DOLLARID",
+ /* 193 */ "doublequotedcontent ::= LDEL variable RDEL",
+ /* 194 */ "doublequotedcontent ::= LDEL expr RDEL",
+ /* 195 */ "doublequotedcontent ::= smartytag",
+ /* 196 */ "doublequotedcontent ::= OTHER",
+ /* 197 */ "optspace ::= SPACE",
+ /* 198 */ "optspace ::=",
+    );
+
+    function tokenName($tokenType)
+    {
+        if ($tokenType === 0) {
+            return 'End of Input';
+        }
+        if ($tokenType > 0 && $tokenType < count($this->yyTokenName)) {
+            return $this->yyTokenName[$tokenType];
+        } else {
+            return "Unknown";
+        }
+    }
+
+    static function yy_destructor($yymajor, $yypminor)
+    {
+        switch ($yymajor) {
+            default:  break;   /* If no destructor action specified: do nothing */
+        }
+    }
+
+    function yy_pop_parser_stack()
+    {
+        if (!count($this->yystack)) {
+            return;
+        }
+        $yytos = array_pop($this->yystack);
+        if (self::$yyTraceFILE && $this->yyidx >= 0) {
+            fwrite(self::$yyTraceFILE,
+                self::$yyTracePrompt . 'Popping ' . $this->yyTokenName[$yytos->major] .
+                    "\n");
+        }
+        $yymajor = $yytos->major;
+        self::yy_destructor($yymajor, $yytos->minor);
+        $this->yyidx--;
+        return $yymajor;
+    }
+
+    function __destruct()
+    {
+        while ($this->yystack !== Array()) {
+            $this->yy_pop_parser_stack();
+        }
+        if (is_resource(self::$yyTraceFILE)) {
+            fclose(self::$yyTraceFILE);
+        }
+    }
+
+    function yy_get_expected_tokens($token)
+    {
+        $state = $this->yystack[$this->yyidx]->stateno;
+        $expected = self::$yyExpectedTokens[$state];
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return $expected;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return array_unique($expected);
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate])) {
+                       $expected = array_merge($expected, self::$yyExpectedTokens[$nextstate]);
+                            if (in_array($token,
+                                  self::$yyExpectedTokens[$nextstate], true)) {
+                            $this->yyidx = $yyidx;
+                            $this->yystack = $stack;
+                            return array_unique($expected);
+                        }
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new TP_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return array_unique($expected);
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return $expected;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+       $this->yyidx = $yyidx;
+       $this->yystack = $stack;
+        return array_unique($expected);
+    }
+
+    function yy_is_expected_token($token)
+    {
+        if ($token === 0) {
+            return true; // 0 is not part of this
+        }
+        $state = $this->yystack[$this->yyidx]->stateno;
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return true;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return true;
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate]) &&
+                          in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        return true;
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new TP_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        if (!$token) {
+                            // end of input: this is valid
+                            return true;
+                        }
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return false;
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return true;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+        $this->yyidx = $yyidx;
+        $this->yystack = $stack;
+        return true;
+    }
+
+   function yy_find_shift_action($iLookAhead)
+    {
+        $stateno = $this->yystack[$this->yyidx]->stateno;
+     
+        /* if ($this->yyidx < 0) return self::YY_NO_ACTION;  */
+        if (!isset(self::$yy_shift_ofst[$stateno])) {
+            // no shift actions
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_shift_ofst[$stateno];
+        if ($i === self::YY_SHIFT_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
+                   && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
+                if (self::$yyTraceFILE) {
+                    fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
+                        $this->yyTokenName[$iLookAhead] . " => " .
+                        $this->yyTokenName[$iFallback] . "\n");
+                }
+                return $this->yy_find_shift_action($iFallback);
+            }
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    function yy_find_reduce_action($stateno, $iLookAhead)
+    {
+        /* $stateno = $this->yystack[$this->yyidx]->stateno; */
+
+        if (!isset(self::$yy_reduce_ofst[$stateno])) {
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_reduce_ofst[$stateno];
+        if ($i == self::YY_REDUCE_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    function yy_shift($yyNewState, $yyMajor, $yypMinor)
+    {
+        $this->yyidx++;
+        if ($this->yyidx >= self::YYSTACKDEPTH) {
+            $this->yyidx--;
+            if (self::$yyTraceFILE) {
+                fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
+            }
+            while ($this->yyidx >= 0) {
+                $this->yy_pop_parser_stack();
+            }
+#line 73 "smarty_internal_templateparser.y"
+
+    $this->internalError = true;
+    $this->compiler->trigger_template_error("Stack overflow in template parser");
+#line 1731 "smarty_internal_templateparser.php"
+            return;
+        }
+        $yytos = new TP_yyStackEntry;
+        $yytos->stateno = $yyNewState;
+        $yytos->major = $yyMajor;
+        $yytos->minor = $yypMinor;
+        array_push($this->yystack, $yytos);
+        if (self::$yyTraceFILE && $this->yyidx > 0) {
+            fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
+                $yyNewState);
+            fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
+            for($i = 1; $i <= $this->yyidx; $i++) {
+                fprintf(self::$yyTraceFILE, " %s",
+                    $this->yyTokenName[$this->yystack[$i]->major]);
+            }
+            fwrite(self::$yyTraceFILE,"\n");
+        }
+    }
+
+    static public $yyRuleInfo = array(
+  array( 'lhs' => 79, 'rhs' => 1 ),
+  array( 'lhs' => 80, 'rhs' => 1 ),
+  array( 'lhs' => 80, 'rhs' => 2 ),
+  array( 'lhs' => 80, 'rhs' => 0 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 2 ),
+  array( 'lhs' => 83, 'rhs' => 3 ),
+  array( 'lhs' => 84, 'rhs' => 2 ),
+  array( 'lhs' => 84, 'rhs' => 0 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 85, 'rhs' => 1 ),
+  array( 'lhs' => 82, 'rhs' => 3 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 5 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 6 ),
+  array( 'lhs' => 82, 'rhs' => 6 ),
+  array( 'lhs' => 82, 'rhs' => 7 ),
+  array( 'lhs' => 82, 'rhs' => 6 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 3 ),
+  array( 'lhs' => 82, 'rhs' => 6 ),
+  array( 'lhs' => 82, 'rhs' => 5 ),
+  array( 'lhs' => 82, 'rhs' => 7 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 5 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 5 ),
+  array( 'lhs' => 82, 'rhs' => 13 ),
+  array( 'lhs' => 96, 'rhs' => 2 ),
+  array( 'lhs' => 96, 'rhs' => 1 ),
+  array( 'lhs' => 82, 'rhs' => 7 ),
+  array( 'lhs' => 82, 'rhs' => 9 ),
+  array( 'lhs' => 82, 'rhs' => 3 ),
+  array( 'lhs' => 82, 'rhs' => 8 ),
+  array( 'lhs' => 82, 'rhs' => 11 ),
+  array( 'lhs' => 82, 'rhs' => 8 ),
+  array( 'lhs' => 82, 'rhs' => 11 ),
+  array( 'lhs' => 82, 'rhs' => 1 ),
+  array( 'lhs' => 82, 'rhs' => 3 ),
+  array( 'lhs' => 82, 'rhs' => 4 ),
+  array( 'lhs' => 82, 'rhs' => 5 ),
+  array( 'lhs' => 82, 'rhs' => 6 ),
+  array( 'lhs' => 87, 'rhs' => 2 ),
+  array( 'lhs' => 87, 'rhs' => 1 ),
+  array( 'lhs' => 87, 'rhs' => 0 ),
+  array( 'lhs' => 97, 'rhs' => 4 ),
+  array( 'lhs' => 97, 'rhs' => 4 ),
+  array( 'lhs' => 97, 'rhs' => 4 ),
+  array( 'lhs' => 97, 'rhs' => 2 ),
+  array( 'lhs' => 97, 'rhs' => 2 ),
+  array( 'lhs' => 97, 'rhs' => 2 ),
+  array( 'lhs' => 97, 'rhs' => 4 ),
+  array( 'lhs' => 93, 'rhs' => 1 ),
+  array( 'lhs' => 93, 'rhs' => 3 ),
+  array( 'lhs' => 92, 'rhs' => 4 ),
+  array( 'lhs' => 92, 'rhs' => 3 ),
+  array( 'lhs' => 92, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 1 ),
+  array( 'lhs' => 89, 'rhs' => 1 ),
+  array( 'lhs' => 89, 'rhs' => 4 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 1 ),
+  array( 'lhs' => 89, 'rhs' => 2 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 2 ),
+  array( 'lhs' => 89, 'rhs' => 2 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 2 ),
+  array( 'lhs' => 89, 'rhs' => 2 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 89, 'rhs' => 3 ),
+  array( 'lhs' => 98, 'rhs' => 8 ),
+  array( 'lhs' => 98, 'rhs' => 7 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 3 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 3 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 3 ),
+  array( 'lhs' => 86, 'rhs' => 3 ),
+  array( 'lhs' => 86, 'rhs' => 1 ),
+  array( 'lhs' => 86, 'rhs' => 2 ),
+  array( 'lhs' => 88, 'rhs' => 1 ),
+  array( 'lhs' => 88, 'rhs' => 4 ),
+  array( 'lhs' => 88, 'rhs' => 1 ),
+  array( 'lhs' => 88, 'rhs' => 3 ),
+  array( 'lhs' => 88, 'rhs' => 3 ),
+  array( 'lhs' => 91, 'rhs' => 3 ),
+  array( 'lhs' => 106, 'rhs' => 2 ),
+  array( 'lhs' => 106, 'rhs' => 0 ),
+  array( 'lhs' => 107, 'rhs' => 3 ),
+  array( 'lhs' => 107, 'rhs' => 5 ),
+  array( 'lhs' => 107, 'rhs' => 2 ),
+  array( 'lhs' => 107, 'rhs' => 2 ),
+  array( 'lhs' => 107, 'rhs' => 4 ),
+  array( 'lhs' => 107, 'rhs' => 3 ),
+  array( 'lhs' => 107, 'rhs' => 5 ),
+  array( 'lhs' => 107, 'rhs' => 3 ),
+  array( 'lhs' => 107, 'rhs' => 2 ),
+  array( 'lhs' => 95, 'rhs' => 1 ),
+  array( 'lhs' => 95, 'rhs' => 2 ),
+  array( 'lhs' => 108, 'rhs' => 1 ),
+  array( 'lhs' => 108, 'rhs' => 3 ),
+  array( 'lhs' => 105, 'rhs' => 2 ),
+  array( 'lhs' => 109, 'rhs' => 1 ),
+  array( 'lhs' => 109, 'rhs' => 2 ),
+  array( 'lhs' => 110, 'rhs' => 3 ),
+  array( 'lhs' => 110, 'rhs' => 4 ),
+  array( 'lhs' => 110, 'rhs' => 5 ),
+  array( 'lhs' => 110, 'rhs' => 6 ),
+  array( 'lhs' => 110, 'rhs' => 2 ),
+  array( 'lhs' => 102, 'rhs' => 4 ),
+  array( 'lhs' => 111, 'rhs' => 4 ),
+  array( 'lhs' => 111, 'rhs' => 5 ),
+  array( 'lhs' => 112, 'rhs' => 3 ),
+  array( 'lhs' => 112, 'rhs' => 1 ),
+  array( 'lhs' => 112, 'rhs' => 0 ),
+  array( 'lhs' => 90, 'rhs' => 3 ),
+  array( 'lhs' => 90, 'rhs' => 2 ),
+  array( 'lhs' => 113, 'rhs' => 3 ),
+  array( 'lhs' => 113, 'rhs' => 2 ),
+  array( 'lhs' => 114, 'rhs' => 2 ),
+  array( 'lhs' => 114, 'rhs' => 0 ),
+  array( 'lhs' => 115, 'rhs' => 2 ),
+  array( 'lhs' => 115, 'rhs' => 2 ),
+  array( 'lhs' => 104, 'rhs' => 1 ),
+  array( 'lhs' => 104, 'rhs' => 2 ),
+  array( 'lhs' => 104, 'rhs' => 1 ),
+  array( 'lhs' => 104, 'rhs' => 3 ),
+  array( 'lhs' => 104, 'rhs' => 4 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 100, 'rhs' => 1 ),
+  array( 'lhs' => 101, 'rhs' => 1 ),
+  array( 'lhs' => 101, 'rhs' => 1 ),
+  array( 'lhs' => 101, 'rhs' => 1 ),
+  array( 'lhs' => 99, 'rhs' => 3 ),
+  array( 'lhs' => 116, 'rhs' => 1 ),
+  array( 'lhs' => 116, 'rhs' => 3 ),
+  array( 'lhs' => 116, 'rhs' => 0 ),
+  array( 'lhs' => 117, 'rhs' => 3 ),
+  array( 'lhs' => 117, 'rhs' => 3 ),
+  array( 'lhs' => 117, 'rhs' => 1 ),
+  array( 'lhs' => 103, 'rhs' => 2 ),
+  array( 'lhs' => 103, 'rhs' => 3 ),
+  array( 'lhs' => 118, 'rhs' => 2 ),
+  array( 'lhs' => 118, 'rhs' => 1 ),
+  array( 'lhs' => 119, 'rhs' => 3 ),
+  array( 'lhs' => 119, 'rhs' => 3 ),
+  array( 'lhs' => 119, 'rhs' => 1 ),
+  array( 'lhs' => 119, 'rhs' => 3 ),
+  array( 'lhs' => 119, 'rhs' => 3 ),
+  array( 'lhs' => 119, 'rhs' => 1 ),
+  array( 'lhs' => 119, 'rhs' => 1 ),
+  array( 'lhs' => 94, 'rhs' => 1 ),
+  array( 'lhs' => 94, 'rhs' => 0 ),
+    );
+
+    static public $yyReduceMap = array(
+        0 => 0,
+        1 => 1,
+        2 => 1,
+        4 => 4,
+        5 => 5,
+        6 => 6,
+        7 => 7,
+        8 => 8,
+        9 => 9,
+        10 => 10,
+        11 => 11,
+        12 => 12,
+        13 => 13,
+        14 => 14,
+        15 => 15,
+        18 => 15,
+        16 => 16,
+        17 => 17,
+        101 => 17,
+        103 => 17,
+        104 => 17,
+        163 => 17,
+        19 => 19,
+        20 => 19,
+        74 => 19,
+        75 => 19,
+        100 => 19,
+        105 => 19,
+        106 => 19,
+        111 => 19,
+        113 => 19,
+        114 => 19,
+        121 => 19,
+        162 => 19,
+        180 => 19,
+        21 => 21,
+        22 => 21,
+        23 => 23,
+        24 => 24,
+        25 => 25,
+        26 => 26,
+        27 => 27,
+        28 => 27,
+        30 => 27,
+        29 => 29,
+        31 => 31,
+        32 => 31,
+        33 => 33,
+        34 => 34,
+        35 => 35,
+        36 => 36,
+        37 => 37,
+        38 => 38,
+        39 => 39,
+        40 => 40,
+        42 => 40,
+        41 => 41,
+        43 => 41,
+        44 => 44,
+        45 => 45,
+        46 => 46,
+        66 => 46,
+        67 => 46,
+        164 => 46,
+        185 => 46,
+        47 => 47,
+        48 => 48,
+        49 => 49,
+        50 => 50,
+        51 => 51,
+        52 => 52,
+        53 => 53,
+        54 => 54,
+        55 => 55,
+        56 => 56,
+        57 => 57,
+        58 => 58,
+        59 => 59,
+        60 => 60,
+        69 => 60,
+        152 => 60,
+        156 => 60,
+        61 => 61,
+        153 => 61,
+        62 => 62,
+        63 => 63,
+        64 => 63,
+        65 => 65,
+        68 => 68,
+        70 => 70,
+        71 => 71,
+        72 => 71,
+        73 => 73,
+        76 => 76,
+        77 => 77,
+        78 => 77,
+        79 => 77,
+        80 => 80,
+        136 => 80,
+        197 => 80,
+        81 => 81,
+        118 => 81,
+        82 => 82,
+        85 => 82,
+        96 => 82,
+        83 => 83,
+        84 => 84,
+        86 => 86,
+        87 => 87,
+        88 => 88,
+        93 => 88,
+        89 => 89,
+        92 => 89,
+        90 => 90,
+        95 => 90,
+        91 => 91,
+        94 => 91,
+        97 => 97,
+        98 => 98,
+        99 => 99,
+        102 => 102,
+        107 => 107,
+        108 => 108,
+        109 => 109,
+        110 => 110,
+        112 => 112,
+        115 => 115,
+        116 => 116,
+        117 => 117,
+        119 => 119,
+        120 => 120,
+        122 => 122,
+        123 => 123,
+        124 => 124,
+        125 => 125,
+        126 => 126,
+        127 => 127,
+        128 => 128,
+        129 => 129,
+        130 => 130,
+        131 => 131,
+        134 => 131,
+        132 => 132,
+        133 => 133,
+        135 => 135,
+        137 => 137,
+        138 => 138,
+        139 => 139,
+        140 => 140,
+        141 => 141,
+        142 => 142,
+        143 => 143,
+        144 => 144,
+        145 => 145,
+        146 => 146,
+        147 => 147,
+        148 => 148,
+        149 => 149,
+        150 => 150,
+        151 => 151,
+        154 => 154,
+        155 => 155,
+        157 => 157,
+        158 => 158,
+        159 => 159,
+        160 => 160,
+        161 => 160,
+        165 => 165,
+        166 => 166,
+        167 => 167,
+        168 => 168,
+        169 => 169,
+        170 => 170,
+        171 => 171,
+        172 => 172,
+        173 => 173,
+        174 => 174,
+        175 => 175,
+        176 => 176,
+        177 => 177,
+        178 => 178,
+        179 => 179,
+        181 => 181,
+        182 => 182,
+        183 => 183,
+        184 => 184,
+        186 => 186,
+        187 => 187,
+        188 => 188,
+        189 => 189,
+        190 => 190,
+        191 => 190,
+        193 => 190,
+        192 => 192,
+        194 => 194,
+        195 => 195,
+        196 => 196,
+        198 => 198,
+    );
+#line 84 "smarty_internal_templateparser.y"
+    function yy_r0(){ $this->_retvalue = $this->root_buffer->to_smarty_php();     }
+#line 2155 "smarty_internal_templateparser.php"
+#line 90 "smarty_internal_templateparser.y"
+    function yy_r1(){ $this->current_buffer->append_subtree($this->yystack[$this->yyidx + 0]->minor);     }
+#line 2158 "smarty_internal_templateparser.php"
+#line 102 "smarty_internal_templateparser.y"
+    function yy_r4(){
+                                          if ($this->compiler->has_code) {
+                                            $tmp =''; foreach ($this->compiler->prefix_code as $code) {$tmp.=$code;} $this->compiler->prefix_code=array();
+                                            $this->_retvalue = new _smarty_tag($this, $this->compiler->processNocacheCode($tmp.$this->yystack[$this->yyidx + 0]->minor,true));
+                                         } else { 
+                                           $this->_retvalue = new _smarty_tag($this, $this->yystack[$this->yyidx + 0]->minor);
+                                         }  
+                                         $this->compiler->has_variable_string = false;
+                                         $this->block_nesting_level = count($this->compiler->_tag_stack);
+                                            }
+#line 2170 "smarty_internal_templateparser.php"
+#line 114 "smarty_internal_templateparser.y"
+    function yy_r5(){ $this->_retvalue = new _smarty_tag($this, '');    }
+#line 2173 "smarty_internal_templateparser.php"
+#line 117 "smarty_internal_templateparser.y"
+    function yy_r6(){ $this->_retvalue = new _smarty_text($this, $this->yystack[$this->yyidx + 0]->minor);     }
+#line 2176 "smarty_internal_templateparser.php"
+#line 120 "smarty_internal_templateparser.y"
+    function yy_r7(){
+                                      if ($this->php_handling == Smarty::PHP_PASSTHRU) {
+                                                                    $this->_retvalue = new _smarty_text($this, self::escape_start_tag($this->yystack[$this->yyidx + 0]->minor));
+                                      } elseif ($this->php_handling == Smarty::PHP_QUOTE) {
+                                       $this->_retvalue = new _smarty_text($this, htmlspecialchars($this->yystack[$this->yyidx + 0]->minor, ENT_QUOTES));
+                                      }elseif ($this->php_handling == Smarty::PHP_ALLOW) {
+                                       $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode('<?php', true));
+                                      }elseif ($this->php_handling == Smarty::PHP_REMOVE) {
+                                       $this->_retvalue = new _smarty_text($this, '');
+                                      }
+                                         }
+#line 2189 "smarty_internal_templateparser.php"
+#line 132 "smarty_internal_templateparser.y"
+    function yy_r8(){if ($this->is_xml) {
+                                       $this->compiler->tag_nocache = true; 
+                                       $this->is_xml = true; 
+                                       $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode("<?php echo '?>';?>", $this->compiler, true));
+                                      }elseif ($this->php_handling == Smarty::PHP_PASSTHRU) {
+                                                                    $this->_retvalue = new _smarty_text($this, '?<?php ?>>');
+                                      } elseif ($this->php_handling == Smarty::PHP_QUOTE) {
+                                       $this->_retvalue = new _smarty_text($this, htmlspecialchars('?>', ENT_QUOTES));
+                                      }elseif ($this->php_handling == Smarty::PHP_ALLOW) {
+                                       $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode('?>', true));
+                                      }elseif ($this->php_handling == Smarty::PHP_REMOVE) {
+                                       $this->_retvalue = new _smarty_text($this, '');
+                                      }
+                                         }
+#line 2205 "smarty_internal_templateparser.php"
+#line 148 "smarty_internal_templateparser.y"
+    function yy_r9(){
+                                      if ($this->php_handling == Smarty::PHP_PASSTHRU) {
+                                                                    $this->_retvalue = new _smarty_text($this, '<<?php ?>%');
+                                      } elseif ($this->php_handling == Smarty::PHP_QUOTE) {
+                                       $this->_retvalue = new _smarty_text($this, htmlspecialchars($this->yystack[$this->yyidx + 0]->minor, ENT_QUOTES));
+                                      }elseif ($this->php_handling == Smarty::PHP_ALLOW) {
+                                        if ($this->asp_tags) {
+                                          $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode('<%', true));
+                                        } else {
+                                         $this->_retvalue = new _smarty_text($this, '<<?php ?>%');
+                                        }
+                                      }elseif ($this->php_handling == Smarty::PHP_REMOVE) {
+                                        if ($this->asp_tags) {
+                                         $this->_retvalue = new _smarty_text($this, '');
+                                        } else {
+                                         $this->_retvalue = new _smarty_text($this, '<<?php ?>%');
+                                        }
+                                      }
+                                        }
+#line 2226 "smarty_internal_templateparser.php"
+#line 169 "smarty_internal_templateparser.y"
+    function yy_r10(){
+                                      if ($this->php_handling == Smarty::PHP_PASSTHRU) {
+                                                                    $this->_retvalue = new _smarty_text($this, '%<?php ?>>');
+                                      } elseif ($this->php_handling == Smarty::PHP_QUOTE) {
+                                       $this->_retvalue = new _smarty_text($this, htmlspecialchars('%>', ENT_QUOTES));
+                                      }elseif ($this->php_handling == Smarty::PHP_ALLOW) {
+                                        if ($this->asp_tags) {
+                                          $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode('%>', true));
+                                        } else {
+                                         $this->_retvalue = new _smarty_text($this, '%<?php ?>>');
+                                        }
+                                      }elseif ($this->php_handling == Smarty::PHP_REMOVE) {
+                                        if ($this->asp_tags) {
+                                         $this->_retvalue = new _smarty_text($this, '');
+                                        } else {
+                                         $this->_retvalue = new _smarty_text($this, '%<?php ?>>');
+                                        }
+                                      }
+                                        }
+#line 2247 "smarty_internal_templateparser.php"
+#line 189 "smarty_internal_templateparser.y"
+    function yy_r11(){if ($this->lex->strip) {
+                                       $this->_retvalue = new _smarty_text($this, preg_replace('![\$this->yystack[$this->yyidx + 0]->minor ]*[\r\n]+[\$this->yystack[$this->yyidx + 0]->minor ]*!', '', self::escape_start_tag($this->yystack[$this->yyidx + 0]->minor)));     
+                                     } else {
+                                       $this->_retvalue = new _smarty_text($this, self::escape_start_tag($this->yystack[$this->yyidx + 0]->minor));    
+                                     }
+                                        }
+#line 2255 "smarty_internal_templateparser.php"
+#line 197 "smarty_internal_templateparser.y"
+    function yy_r12(){ $this->compiler->tag_nocache = true; $this->is_xml = true; $this->_retvalue = new _smarty_text($this, $this->compiler->processNocacheCode("<?php echo '<?xml';?>", $this->compiler, true));    }
+#line 2258 "smarty_internal_templateparser.php"
+#line 200 "smarty_internal_templateparser.y"
+    function yy_r13(){if ($this->lex->strip) {
+                                       $this->_retvalue = new _smarty_text($this, preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $this->yystack[$this->yyidx + 0]->minor)); 
+                                     } else {
+                                       $this->_retvalue = new _smarty_text($this, $this->yystack[$this->yyidx + 0]->minor);    
+                                     }
+                                        }
+#line 2266 "smarty_internal_templateparser.php"
+#line 206 "smarty_internal_templateparser.y"
+    function yy_r14(){
+                                     $this->_retvalue = new _smarty_linebreak($this, $this->yystack[$this->yyidx + 0]->minor);
+                                       }
+#line 2271 "smarty_internal_templateparser.php"
+#line 211 "smarty_internal_templateparser.y"
+    function yy_r15(){ $this->_retvalue = '';     }
+#line 2274 "smarty_internal_templateparser.php"
+#line 212 "smarty_internal_templateparser.y"
+    function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;     }
+#line 2277 "smarty_internal_templateparser.php"
+#line 214 "smarty_internal_templateparser.y"
+    function yy_r17(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2280 "smarty_internal_templateparser.php"
+#line 217 "smarty_internal_templateparser.y"
+    function yy_r19(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;     }
+#line 2283 "smarty_internal_templateparser.php"
+#line 219 "smarty_internal_templateparser.y"
+    function yy_r21(){ $this->_retvalue = self::escape_start_tag($this->yystack[$this->yyidx + 0]->minor);     }
+#line 2286 "smarty_internal_templateparser.php"
+#line 221 "smarty_internal_templateparser.y"
+    function yy_r23(){ $this->_retvalue = self::escape_end_tag($this->yystack[$this->yyidx + 0]->minor);     }
+#line 2289 "smarty_internal_templateparser.php"
+#line 222 "smarty_internal_templateparser.y"
+    function yy_r24(){ $this->_retvalue = '<<?php ?>%';     }
+#line 2292 "smarty_internal_templateparser.php"
+#line 223 "smarty_internal_templateparser.y"
+    function yy_r25(){ $this->_retvalue = '%<?php ?>>';     }
+#line 2295 "smarty_internal_templateparser.php"
+#line 231 "smarty_internal_templateparser.y"
+    function yy_r26(){ $this->_retvalue = $this->compiler->compileTag('private_print_expression',array(),array('value'=>$this->yystack[$this->yyidx + -1]->minor));    }
+#line 2298 "smarty_internal_templateparser.php"
+#line 232 "smarty_internal_templateparser.y"
+    function yy_r27(){ $this->_retvalue = $this->compiler->compileTag('private_print_expression',$this->yystack[$this->yyidx + -1]->minor,array('value'=>$this->yystack[$this->yyidx + -2]->minor));    }
+#line 2301 "smarty_internal_templateparser.php"
+#line 234 "smarty_internal_templateparser.y"
+    function yy_r29(){ $this->_retvalue = $this->compiler->compileTag('private_print_expression',$this->yystack[$this->yyidx + -1]->minor,array('value'=>$this->yystack[$this->yyidx + -3]->minor,'modifierlist'=>$this->yystack[$this->yyidx + -2]->minor));    }
+#line 2304 "smarty_internal_templateparser.php"
+#line 242 "smarty_internal_templateparser.y"
+    function yy_r31(){ $this->_retvalue = $this->compiler->compileTag('assign',array(array('value'=>$this->yystack[$this->yyidx + -1]->minor),array('var'=>"'".$this->yystack[$this->yyidx + -3]->minor."'")));    }
+#line 2307 "smarty_internal_templateparser.php"
+#line 244 "smarty_internal_templateparser.y"
+    function yy_r33(){ $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + -2]->minor),array('var'=>"'".$this->yystack[$this->yyidx + -4]->minor."'")),$this->yystack[$this->yyidx + -1]->minor));    }
+#line 2310 "smarty_internal_templateparser.php"
+#line 245 "smarty_internal_templateparser.y"
+    function yy_r34(){ $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + -2]->minor),array('var'=>$this->yystack[$this->yyidx + -4]->minor['var'])),$this->yystack[$this->yyidx + -1]->minor),array('smarty_internal_index'=>$this->yystack[$this->yyidx + -4]->minor['smarty_internal_index']));    }
+#line 2313 "smarty_internal_templateparser.php"
+#line 247 "smarty_internal_templateparser.y"
+    function yy_r35(){ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor,$this->yystack[$this->yyidx + -1]->minor);    }
+#line 2316 "smarty_internal_templateparser.php"
+#line 248 "smarty_internal_templateparser.y"
+    function yy_r36(){ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor,array());    }
+#line 2319 "smarty_internal_templateparser.php"
+#line 250 "smarty_internal_templateparser.y"
+    function yy_r37(){ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -4]->minor,$this->yystack[$this->yyidx + -1]->minor,array('object_methode'=>$this->yystack[$this->yyidx + -2]->minor));    }
+#line 2322 "smarty_internal_templateparser.php"
+#line 252 "smarty_internal_templateparser.y"
+    function yy_r38(){  $this->_retvalue = '<?php ob_start();?>'.$this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor,$this->yystack[$this->yyidx + -1]->minor).'<?php echo ';
+                                                                                    $this->_retvalue .= $this->compiler->compileTag('private_modifier',array(),array('modifierlist'=>$this->yystack[$this->yyidx + -2]->minor,'value'=>'ob_get_clean()')).'?>';
+                                                                                     }
+#line 2327 "smarty_internal_templateparser.php"
+#line 256 "smarty_internal_templateparser.y"
+    function yy_r39(){  $this->_retvalue = '<?php ob_start();?>'.$this->compiler->compileTag($this->yystack[$this->yyidx + -5]->minor,$this->yystack[$this->yyidx + -1]->minor,array('object_methode'=>$this->yystack[$this->yyidx + -3]->minor)).'<?php echo ';
+                                                                                               $this->_retvalue .= $this->compiler->compileTag('private_modifier',array(),array('modifierlist'=>$this->yystack[$this->yyidx + -2]->minor,'value'=>'ob_get_clean()')).'?>';
+                                                                                                }
+#line 2332 "smarty_internal_templateparser.php"
+#line 260 "smarty_internal_templateparser.y"
+    function yy_r40(){ $tag = trim(substr($this->yystack[$this->yyidx + -3]->minor,$this->lex->ldel_length)); $this->_retvalue = $this->compiler->compileTag(($tag == 'else if')? 'elseif' : $tag,array(),array('if condition'=>$this->yystack[$this->yyidx + -1]->minor));    }
+#line 2335 "smarty_internal_templateparser.php"
+#line 261 "smarty_internal_templateparser.y"
+    function yy_r41(){ $tag = trim(substr($this->yystack[$this->yyidx + -4]->minor,$this->lex->ldel_length)); $this->_retvalue = $this->compiler->compileTag(($tag == 'else if')? 'elseif' : $tag,$this->yystack[$this->yyidx + -1]->minor,array('if condition'=>$this->yystack[$this->yyidx + -2]->minor));    }
+#line 2338 "smarty_internal_templateparser.php"
+#line 265 "smarty_internal_templateparser.y"
+    function yy_r44(){
+                                                             $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('start'=>$this->yystack[$this->yyidx + -10]->minor),array('ifexp'=>$this->yystack[$this->yyidx + -7]->minor),array('var'=>$this->yystack[$this->yyidx + -3]->minor),array('step'=>$this->yystack[$this->yyidx + -2]->minor))),1);    }
+#line 2342 "smarty_internal_templateparser.php"
+#line 268 "smarty_internal_templateparser.y"
+    function yy_r45(){ $this->_retvalue = '='.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2345 "smarty_internal_templateparser.php"
+#line 269 "smarty_internal_templateparser.y"
+    function yy_r46(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;    }
+#line 2348 "smarty_internal_templateparser.php"
+#line 270 "smarty_internal_templateparser.y"
+    function yy_r47(){ $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('start'=>$this->yystack[$this->yyidx + -4]->minor),array('to'=>$this->yystack[$this->yyidx + -2]->minor))),0);    }
+#line 2351 "smarty_internal_templateparser.php"
+#line 271 "smarty_internal_templateparser.y"
+    function yy_r48(){ $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('start'=>$this->yystack[$this->yyidx + -6]->minor),array('to'=>$this->yystack[$this->yyidx + -4]->minor),array('step'=>$this->yystack[$this->yyidx + -2]->minor))),0);    }
+#line 2354 "smarty_internal_templateparser.php"
+#line 273 "smarty_internal_templateparser.y"
+    function yy_r49(){ $this->_retvalue = $this->compiler->compileTag('foreach',$this->yystack[$this->yyidx + -1]->minor);    }
+#line 2357 "smarty_internal_templateparser.php"
+#line 275 "smarty_internal_templateparser.y"
+    function yy_r50(){
+                                                            $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('from'=>$this->yystack[$this->yyidx + -5]->minor),array('item'=>$this->yystack[$this->yyidx + -2]->minor))));    }
+#line 2361 "smarty_internal_templateparser.php"
+#line 277 "smarty_internal_templateparser.y"
+    function yy_r51(){
+                                                            $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('from'=>$this->yystack[$this->yyidx + -8]->minor),array('item'=>$this->yystack[$this->yyidx + -2]->minor),array('key'=>$this->yystack[$this->yyidx + -5]->minor))));    }
+#line 2365 "smarty_internal_templateparser.php"
+#line 279 "smarty_internal_templateparser.y"
+    function yy_r52(){ 
+                                                            $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('from'=>$this->yystack[$this->yyidx + -5]->minor),array('item'=>$this->yystack[$this->yyidx + -2]->minor))));    }
+#line 2369 "smarty_internal_templateparser.php"
+#line 281 "smarty_internal_templateparser.y"
+    function yy_r53(){ 
+                                                            $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + -1]->minor,array(array('from'=>$this->yystack[$this->yyidx + -8]->minor),array('item'=>$this->yystack[$this->yyidx + -2]->minor),array('key'=>$this->yystack[$this->yyidx + -5]->minor))));    }
+#line 2373 "smarty_internal_templateparser.php"
+#line 285 "smarty_internal_templateparser.y"
+    function yy_r54(){ $this->_retvalue = SMARTY_INTERNAL_COMPILE_BLOCK::compileChildBlock($this->compiler);    }
+#line 2376 "smarty_internal_templateparser.php"
+#line 289 "smarty_internal_templateparser.y"
+    function yy_r55(){ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor.'close',array());    }
+#line 2379 "smarty_internal_templateparser.php"
+#line 291 "smarty_internal_templateparser.y"
+    function yy_r56(){  $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor.'close',array(),array('modifier_list'=>$this->yystack[$this->yyidx + -1]->minor));
+                                                                                          }
+#line 2383 "smarty_internal_templateparser.php"
+#line 294 "smarty_internal_templateparser.y"
+    function yy_r57(){  $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor.'close',array(),array('object_methode'=>$this->yystack[$this->yyidx + -1]->minor));    }
+#line 2386 "smarty_internal_templateparser.php"
+#line 295 "smarty_internal_templateparser.y"
+    function yy_r58(){  $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -4]->minor.'close',array(),array('object_methode'=>$this->yystack[$this->yyidx + -2]->minor, 'modifier_list'=>$this->yystack[$this->yyidx + -1]->minor));    }
+#line 2389 "smarty_internal_templateparser.php"
+#line 301 "smarty_internal_templateparser.y"
+    function yy_r59(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; $this->_retvalue[] = $this->yystack[$this->yyidx + 0]->minor;    }
+#line 2392 "smarty_internal_templateparser.php"
+#line 303 "smarty_internal_templateparser.y"
+    function yy_r60(){ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);    }
+#line 2395 "smarty_internal_templateparser.php"
+#line 305 "smarty_internal_templateparser.y"
+    function yy_r61(){ $this->_retvalue = array();    }
+#line 2398 "smarty_internal_templateparser.php"
+#line 308 "smarty_internal_templateparser.y"
+    function yy_r62(){ if (preg_match('~^true$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                                  $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>'true');
+                                                 } elseif (preg_match('~^false$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                                  $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>'false');
+                                                 } elseif (preg_match('~^null$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                                  $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>'null');
+                                                 } else
+                                                  $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>"'".$this->yystack[$this->yyidx + 0]->minor."'");    }
+#line 2408 "smarty_internal_templateparser.php"
+#line 316 "smarty_internal_templateparser.y"
+    function yy_r63(){ $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor);    }
+#line 2411 "smarty_internal_templateparser.php"
+#line 318 "smarty_internal_templateparser.y"
+    function yy_r65(){ $this->_retvalue = "'".$this->yystack[$this->yyidx + 0]->minor."'";    }
+#line 2414 "smarty_internal_templateparser.php"
+#line 321 "smarty_internal_templateparser.y"
+    function yy_r68(){$this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor);    }
+#line 2417 "smarty_internal_templateparser.php"
+#line 328 "smarty_internal_templateparser.y"
+    function yy_r70(){ $this->yystack[$this->yyidx + -2]->minor[]=$this->yystack[$this->yyidx + 0]->minor; $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;    }
+#line 2420 "smarty_internal_templateparser.php"
+#line 330 "smarty_internal_templateparser.y"
+    function yy_r71(){ $this->_retvalue = array('var' => $this->yystack[$this->yyidx + -2]->minor, 'value'=>$this->yystack[$this->yyidx + 0]->minor);    }
+#line 2423 "smarty_internal_templateparser.php"
+#line 332 "smarty_internal_templateparser.y"
+    function yy_r73(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;    }
+#line 2426 "smarty_internal_templateparser.php"
+#line 343 "smarty_internal_templateparser.y"
+    function yy_r76(){$this->_retvalue = '$_smarty_tpl->getStreamVariable(\''. $this->yystack[$this->yyidx + -2]->minor .'://'. $this->yystack[$this->yyidx + 0]->minor . '\')';    }
+#line 2429 "smarty_internal_templateparser.php"
+#line 345 "smarty_internal_templateparser.y"
+    function yy_r77(){ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor . trim($this->yystack[$this->yyidx + -1]->minor) . $this->yystack[$this->yyidx + 0]->minor;     }
+#line 2432 "smarty_internal_templateparser.php"
+#line 351 "smarty_internal_templateparser.y"
+    function yy_r80(){$this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;    }
+#line 2435 "smarty_internal_templateparser.php"
+#line 354 "smarty_internal_templateparser.y"
+    function yy_r81(){  $this->_retvalue = $this->compiler->compileTag('private_modifier',array(),array('value'=>$this->yystack[$this->yyidx + -1]->minor,'modifierlist'=>$this->yystack[$this->yyidx + 0]->minor));     }
+#line 2438 "smarty_internal_templateparser.php"
+#line 358 "smarty_internal_templateparser.y"
+    function yy_r82(){$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2441 "smarty_internal_templateparser.php"
+#line 359 "smarty_internal_templateparser.y"
+    function yy_r83(){$this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2444 "smarty_internal_templateparser.php"
+#line 360 "smarty_internal_templateparser.y"
+    function yy_r84(){$this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.',(array)'.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2447 "smarty_internal_templateparser.php"
+#line 362 "smarty_internal_templateparser.y"
+    function yy_r86(){$this->_retvalue = '!('.$this->yystack[$this->yyidx + -2]->minor.' % '.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2450 "smarty_internal_templateparser.php"
+#line 363 "smarty_internal_templateparser.y"
+    function yy_r87(){$this->_retvalue = '('.$this->yystack[$this->yyidx + -2]->minor.' % '.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2453 "smarty_internal_templateparser.php"
+#line 364 "smarty_internal_templateparser.y"
+    function yy_r88(){$this->_retvalue = '!(1 & '.$this->yystack[$this->yyidx + -1]->minor.')';    }
+#line 2456 "smarty_internal_templateparser.php"
+#line 365 "smarty_internal_templateparser.y"
+    function yy_r89(){$this->_retvalue = '(1 & '.$this->yystack[$this->yyidx + -1]->minor.')';    }
+#line 2459 "smarty_internal_templateparser.php"
+#line 366 "smarty_internal_templateparser.y"
+    function yy_r90(){$this->_retvalue = '!(1 & '.$this->yystack[$this->yyidx + -2]->minor.' / '.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2462 "smarty_internal_templateparser.php"
+#line 367 "smarty_internal_templateparser.y"
+    function yy_r91(){$this->_retvalue = '(1 & '.$this->yystack[$this->yyidx + -2]->minor.' / '.$this->yystack[$this->yyidx + 0]->minor.')';    }
+#line 2465 "smarty_internal_templateparser.php"
+#line 373 "smarty_internal_templateparser.y"
+    function yy_r97(){$this->prefix_number++; $this->compiler->prefix_code[] = '<?php $_tmp'.$this->prefix_number.'='.$this->yystack[$this->yyidx + 0]->minor.';?>'; $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.'$_tmp'.$this->prefix_number;    }
+#line 2468 "smarty_internal_templateparser.php"
+#line 379 "smarty_internal_templateparser.y"
+    function yy_r98(){ $this->_retvalue = $this->yystack[$this->yyidx + -6]->minor.' ? $_smarty_tpl->getVariable(\''. $this->yystack[$this->yyidx + -2]->minor .'\')->value : '.$this->yystack[$this->yyidx + 0]->minor;  $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable('$this->yystack[$this->yyidx + -2]->minor', null, true, false)->nocache;    }
+#line 2471 "smarty_internal_templateparser.php"
+#line 380 "smarty_internal_templateparser.y"
+    function yy_r99(){ $this->_retvalue = $this->yystack[$this->yyidx + -5]->minor.' ? '.$this->yystack[$this->yyidx + -2]->minor.' : '.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2474 "smarty_internal_templateparser.php"
+#line 387 "smarty_internal_templateparser.y"
+    function yy_r102(){ $this->_retvalue = '!'.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2477 "smarty_internal_templateparser.php"
+#line 393 "smarty_internal_templateparser.y"
+    function yy_r107(){ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2480 "smarty_internal_templateparser.php"
+#line 394 "smarty_internal_templateparser.y"
+    function yy_r108(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.';     }
+#line 2483 "smarty_internal_templateparser.php"
+#line 395 "smarty_internal_templateparser.y"
+    function yy_r109(){ $this->_retvalue = '.'.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2486 "smarty_internal_templateparser.php"
+#line 397 "smarty_internal_templateparser.y"
+    function yy_r110(){ if (preg_match('~^true$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                $this->_retvalue = 'true';
+                               } elseif (preg_match('~^false$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                $this->_retvalue = 'false';
+                               } elseif (preg_match('~^null$~i', $this->yystack[$this->yyidx + 0]->minor)) {
+                                $this->_retvalue = 'null';
+                               } else
+                               $this->_retvalue = "'".$this->yystack[$this->yyidx + 0]->minor."'";     }
+#line 2496 "smarty_internal_templateparser.php"
+#line 408 "smarty_internal_templateparser.y"
+    function yy_r112(){ $this->_retvalue = "(". $this->yystack[$this->yyidx + -1]->minor .")";     }
+#line 2499 "smarty_internal_templateparser.php"
+#line 414 "smarty_internal_templateparser.y"
+    function yy_r115(){if (!$this->security || isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor]) || $this->smarty->security_policy->isTrustedStaticClass($this->yystack[$this->yyidx + -2]->minor, $this->compiler)) {
+                                                                                                                                                                                  if (isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor])) {
+                                                                  $this->_retvalue = $this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor].'::'.$this->yystack[$this->yyidx + 0]->minor;
+                                                                                                                                                                                  } else {
+                                                                  $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor;
+                                                                 } 
+                                                                } else {
+                                                                 $this->compiler->trigger_template_error ("static class '".$this->yystack[$this->yyidx + -2]->minor."' is undefined or not allowed by security setting");
+                                                                }
+                                                                   }
+#line 2511 "smarty_internal_templateparser.php"
+#line 424 "smarty_internal_templateparser.y"
+    function yy_r116(){ if ($this->yystack[$this->yyidx + -2]->minor['var'] == '\'smarty\'') { $this->_retvalue =  $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index']).'::'.$this->yystack[$this->yyidx + 0]->minor;} else {
+                                                         $this->_retvalue = '$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -2]->minor['var'] .')->value'.$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index'].'::'.$this->yystack[$this->yyidx + 0]->minor; $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + -2]->minor['var'],"'"), null, true, false)->nocache;}    }
+#line 2515 "smarty_internal_templateparser.php"
+#line 427 "smarty_internal_templateparser.y"
+    function yy_r117(){ $this->prefix_number++; $this->compiler->prefix_code[] = '<?php ob_start();?>'.$this->yystack[$this->yyidx + 0]->minor.'<?php $_tmp'.$this->prefix_number.'=ob_get_clean();?>'; $this->_retvalue = '$_tmp'.$this->prefix_number;     }
+#line 2518 "smarty_internal_templateparser.php"
+#line 437 "smarty_internal_templateparser.y"
+    function yy_r119(){if ($this->yystack[$this->yyidx + 0]->minor['var'] == '\'smarty\'') {
+                                                                                                                                                               $smarty_var = $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index']);
+                                                                                                                                                               $this->_retvalue = $smarty_var;
+                                      } else {
+                                       // used for array reset,next,prev,end,current 
+                                       $this->last_variable = $this->yystack[$this->yyidx + 0]->minor['var'];
+                                       $this->last_index = $this->yystack[$this->yyidx + 0]->minor['smarty_internal_index'];
+                                       if (isset($this->compiler->local_var[$this->yystack[$this->yyidx + 0]->minor['var']])) {
+                                          $this->_retvalue = '$_smarty_tpl->tpl_vars['. $this->yystack[$this->yyidx + 0]->minor['var'] .']->value'.$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index'];
+                                       } else {
+                                          $this->_retvalue = '$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + 0]->minor['var'] .')->value'.$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index'];
+                                       }
+                                       $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + 0]->minor['var'],"'"), null, true, false)->nocache;
+                                     }
+                                        }
+#line 2535 "smarty_internal_templateparser.php"
+#line 453 "smarty_internal_templateparser.y"
+    function yy_r120(){if (isset($this->compiler->local_var[$this->yystack[$this->yyidx + -2]->minor])) {
+                                                  $this->_retvalue = '$_smarty_tpl->tpl_vars['. $this->yystack[$this->yyidx + -2]->minor .']->'.$this->yystack[$this->yyidx + 0]->minor;
+                                                 } else {
+                                                  $this->_retvalue = '$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -2]->minor .')->'.$this->yystack[$this->yyidx + 0]->minor;
+                                                 }
+                                                  $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + -2]->minor,"'"), null, true, false)->nocache;    }
+#line 2543 "smarty_internal_templateparser.php"
+#line 462 "smarty_internal_templateparser.y"
+    function yy_r122(){$this->_retvalue = '$_smarty_tpl->getConfigVariable(\''. $this->yystack[$this->yyidx + -1]->minor .'\')';    }
+#line 2546 "smarty_internal_templateparser.php"
+#line 463 "smarty_internal_templateparser.y"
+    function yy_r123(){$this->_retvalue = '$_smarty_tpl->getConfigVariable('. $this->yystack[$this->yyidx + -1]->minor .')';    }
+#line 2549 "smarty_internal_templateparser.php"
+#line 466 "smarty_internal_templateparser.y"
+    function yy_r124(){$this->_retvalue = array('var'=>$this->yystack[$this->yyidx + -1]->minor, 'smarty_internal_index'=>$this->yystack[$this->yyidx + 0]->minor);    }
+#line 2552 "smarty_internal_templateparser.php"
+#line 472 "smarty_internal_templateparser.y"
+    function yy_r125(){$this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2555 "smarty_internal_templateparser.php"
+#line 474 "smarty_internal_templateparser.y"
+    function yy_r126(){return;    }
+#line 2558 "smarty_internal_templateparser.php"
+#line 478 "smarty_internal_templateparser.y"
+    function yy_r127(){ $this->_retvalue = '[$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + 0]->minor .')->value]'; $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable('$this->yystack[$this->yyidx + 0]->minor', null, true, false)->nocache;    }
+#line 2561 "smarty_internal_templateparser.php"
+#line 479 "smarty_internal_templateparser.y"
+    function yy_r128(){ $this->_retvalue = '[$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -2]->minor .')->'.$this->yystack[$this->yyidx + 0]->minor.']'; $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + -2]->minor,"'"), null, true, false)->nocache;    }
+#line 2564 "smarty_internal_templateparser.php"
+#line 480 "smarty_internal_templateparser.y"
+    function yy_r129(){ $this->_retvalue = "['". $this->yystack[$this->yyidx + 0]->minor ."']";    }
+#line 2567 "smarty_internal_templateparser.php"
+#line 481 "smarty_internal_templateparser.y"
+    function yy_r130(){ $this->_retvalue = "[". $this->yystack[$this->yyidx + 0]->minor ."]";    }
+#line 2570 "smarty_internal_templateparser.php"
+#line 482 "smarty_internal_templateparser.y"
+    function yy_r131(){ $this->_retvalue = "[". $this->yystack[$this->yyidx + -1]->minor ."]";    }
+#line 2573 "smarty_internal_templateparser.php"
+#line 484 "smarty_internal_templateparser.y"
+    function yy_r132(){ $this->_retvalue = '['.$this->compiler->compileTag('private_special_variable',array(),'[\'section\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\'][\'index\']').']';    }
+#line 2576 "smarty_internal_templateparser.php"
+#line 485 "smarty_internal_templateparser.y"
+    function yy_r133(){ $this->_retvalue = '['.$this->compiler->compileTag('private_special_variable',array(),'[\'section\'][\''.$this->yystack[$this->yyidx + -3]->minor.'\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\']').']';    }
+#line 2579 "smarty_internal_templateparser.php"
+#line 489 "smarty_internal_templateparser.y"
+    function yy_r135(){$this->_retvalue = '[]';    }
+#line 2582 "smarty_internal_templateparser.php"
+#line 497 "smarty_internal_templateparser.y"
+    function yy_r137(){$this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2585 "smarty_internal_templateparser.php"
+#line 499 "smarty_internal_templateparser.y"
+    function yy_r138(){$this->_retvalue = '\''.$this->yystack[$this->yyidx + 0]->minor.'\'';    }
+#line 2588 "smarty_internal_templateparser.php"
+#line 501 "smarty_internal_templateparser.y"
+    function yy_r139(){$this->_retvalue = '('.$this->yystack[$this->yyidx + -1]->minor.')';    }
+#line 2591 "smarty_internal_templateparser.php"
+#line 506 "smarty_internal_templateparser.y"
+    function yy_r140(){ if ($this->yystack[$this->yyidx + -1]->minor['var'] == '\'smarty\'') { $this->_retvalue =  $this->compiler->compileTag('private_special_variable',array(),$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index']).$this->yystack[$this->yyidx + 0]->minor;} else {
+                                                         $this->_retvalue = '$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -1]->minor['var'] .')->value'.$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index'].$this->yystack[$this->yyidx + 0]->minor; $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + -1]->minor['var'],"'"), null, true, false)->nocache;}    }
+#line 2595 "smarty_internal_templateparser.php"
+#line 509 "smarty_internal_templateparser.y"
+    function yy_r141(){$this->_retvalue  = $this->yystack[$this->yyidx + 0]->minor;     }
+#line 2598 "smarty_internal_templateparser.php"
+#line 511 "smarty_internal_templateparser.y"
+    function yy_r142(){$this->_retvalue  = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2601 "smarty_internal_templateparser.php"
+#line 513 "smarty_internal_templateparser.y"
+    function yy_r143(){if ($this->security && substr($this->yystack[$this->yyidx + -1]->minor,0,1) == '_') {
+                                                      $this->compiler->trigger_template_error (self::Err1);
+                                                                                                                                                                                                          }
+                                                     $this->_retvalue = '->'.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+                                                         }
+#line 2608 "smarty_internal_templateparser.php"
+#line 518 "smarty_internal_templateparser.y"
+    function yy_r144(){if ($this->security) {
+                                                                 $this->compiler->trigger_template_error (self::Err2);
+                                                                                                                                                                                                                                                        }
+                                                               $this->_retvalue = '->{$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -1]->minor .')->value'.$this->yystack[$this->yyidx + 0]->minor.'}'; $this->compiler->tag_nocache=$this->compiler->tag_nocache|$this->template->getVariable(trim($this->yystack[$this->yyidx + -1]->minor,"'"), null, true, false)->nocache;
+                                                                   }
+#line 2615 "smarty_internal_templateparser.php"
+#line 523 "smarty_internal_templateparser.y"
+    function yy_r145(){if ($this->security) {
+                                                                 $this->compiler->trigger_template_error (self::Err2);
+                                                                                                                                                                                                                                                          }
+                                                                                                                                                                                                $this->_retvalue = '->{'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}';
+                                                                                                                                                                                                   }
+#line 2622 "smarty_internal_templateparser.php"
+#line 528 "smarty_internal_templateparser.y"
+    function yy_r146(){if ($this->security) {
+                                                                         $this->compiler->trigger_template_error (self::Err2);
+                                                                                                                                                                                                                                                                }
+                                                                       $this->_retvalue = '->{\''.$this->yystack[$this->yyidx + -4]->minor.'\'.'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}';
+                                                                          }
+#line 2629 "smarty_internal_templateparser.php"
+#line 534 "smarty_internal_templateparser.y"
+    function yy_r147(){ $this->_retvalue = '->'.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2632 "smarty_internal_templateparser.php"
+#line 540 "smarty_internal_templateparser.y"
+    function yy_r148(){if (!$this->security || $this->smarty->security_policy->isTrustedPhpFunction($this->yystack[$this->yyidx + -3]->minor, $this->compiler)) {
+                                                                                                                                                                                   if (strcasecmp($this->yystack[$this->yyidx + -3]->minor,'isset') === 0 || strcasecmp($this->yystack[$this->yyidx + -3]->minor,'empty') === 0 || strcasecmp($this->yystack[$this->yyidx + -3]->minor,'array') === 0 || is_callable($this->yystack[$this->yyidx + -3]->minor)) {
+                                                                                                                                                                                       $func_name = strtolower($this->yystack[$this->yyidx + -3]->minor);
+                                                                                                                                                                                       if ($func_name == 'isset') {
+                                                                                                                                                                                         if (count($this->yystack[$this->yyidx + -1]->minor) == 0) {
+                                                                                                                                                                                          $this->compiler->trigger_template_error ('Illegal number of paramer in "isset()"');
+                                                                                                                                                                                         }
+                                                                                                                                                                                         $isset_par=str_replace("')->value","',null,true,false)->value",implode(',',$this->yystack[$this->yyidx + -1]->minor));
+                                                                                                                                                                                         $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor . "(". $isset_par .")";
+                                                                                                                                                                                             } elseif (in_array($func_name,array('empty','reset','current','end','prev','next'))){
+                                                                                                                                                                                         if (count($this->yystack[$this->yyidx + -1]->minor) != 1) {
+                                                                                                                                                                                          $this->compiler->trigger_template_error ('Illegal number of paramer in "empty()"');
+                                                                                                                                                                                         }
+                                                                                                                                                                                         if ($func_name == 'empty') {
+                                                                                                                                                                                               $this->_retvalue = $func_name.'('.str_replace("')->value","',null,true,false)->value",$this->yystack[$this->yyidx + -1]->minor[0]).')';
+                                                                                                                                                                                         } else {
+                                                                                                                                                                                               $this->_retvalue = $func_name.'('.$this->yystack[$this->yyidx + -1]->minor[0].')';
+                                                                                                                                                                                         }
+                                                                                                                                                                                       } else {
+                                                                                                                                                                                         $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor . "(". implode(',',$this->yystack[$this->yyidx + -1]->minor) .")";
+                                                                                                                                                                                       }
+                                                                                                                                                                                   } else {
+                                                       $this->compiler->trigger_template_error ("unknown function \"" . $this->yystack[$this->yyidx + -3]->minor . "\"");
+                                                      }
+                                                     }
+                                                        }
+#line 2660 "smarty_internal_templateparser.php"
+#line 570 "smarty_internal_templateparser.y"
+    function yy_r149(){if ($this->security && substr($this->yystack[$this->yyidx + -3]->minor,0,1) == '_') {
+                                                      $this->compiler->trigger_template_error (self::Err1);
+                                                                                                                                                                                                        }
+                                                   $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor . "(". implode(',',$this->yystack[$this->yyidx + -1]->minor) .")";
+                                                      }
+#line 2667 "smarty_internal_templateparser.php"
+#line 575 "smarty_internal_templateparser.y"
+    function yy_r150(){if ($this->security) {
+                                                              $this->compiler->trigger_template_error (self::Err2);
+                                                                                                                                                                                                                                        }
+                                                           $this->prefix_number++; $this->compiler->prefix_code[] = '<?php $_tmp'.$this->prefix_number.'=$_smarty_tpl->getVariable(\''. $this->yystack[$this->yyidx + -3]->minor .'\')->value;?>'; $this->_retvalue = '$_tmp'.$this->prefix_number.'('. implode(',',$this->yystack[$this->yyidx + -1]->minor) .')';
+                                                              }
+#line 2674 "smarty_internal_templateparser.php"
+#line 583 "smarty_internal_templateparser.y"
+    function yy_r151(){ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array($this->yystack[$this->yyidx + 0]->minor));    }
+#line 2677 "smarty_internal_templateparser.php"
+#line 592 "smarty_internal_templateparser.y"
+    function yy_r154(){$this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor)));    }
+#line 2680 "smarty_internal_templateparser.php"
+#line 593 "smarty_internal_templateparser.y"
+    function yy_r155(){$this->_retvalue = array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor));    }
+#line 2683 "smarty_internal_templateparser.php"
+#line 596 "smarty_internal_templateparser.y"
+    function yy_r157(){ $this->_retvalue =  array($this->yystack[$this->yyidx + 0]->minor);    }
+#line 2686 "smarty_internal_templateparser.php"
+#line 601 "smarty_internal_templateparser.y"
+    function yy_r158(){ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor);    }
+#line 2689 "smarty_internal_templateparser.php"
+#line 603 "smarty_internal_templateparser.y"
+    function yy_r159(){$this->_retvalue = array();    }
+#line 2692 "smarty_internal_templateparser.php"
+#line 605 "smarty_internal_templateparser.y"
+    function yy_r160(){$this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);    }
+#line 2695 "smarty_internal_templateparser.php"
+#line 615 "smarty_internal_templateparser.y"
+    function yy_r165(){ $this->_retvalue = '$'.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2698 "smarty_internal_templateparser.php"
+#line 617 "smarty_internal_templateparser.y"
+    function yy_r166(){ $this->_retvalue = '$'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2701 "smarty_internal_templateparser.php"
+#line 626 "smarty_internal_templateparser.y"
+    function yy_r167(){$this->_retvalue = '==';    }
+#line 2704 "smarty_internal_templateparser.php"
+#line 627 "smarty_internal_templateparser.y"
+    function yy_r168(){$this->_retvalue = '!=';    }
+#line 2707 "smarty_internal_templateparser.php"
+#line 628 "smarty_internal_templateparser.y"
+    function yy_r169(){$this->_retvalue = '>';    }
+#line 2710 "smarty_internal_templateparser.php"
+#line 629 "smarty_internal_templateparser.y"
+    function yy_r170(){$this->_retvalue = '<';    }
+#line 2713 "smarty_internal_templateparser.php"
+#line 630 "smarty_internal_templateparser.y"
+    function yy_r171(){$this->_retvalue = '>=';    }
+#line 2716 "smarty_internal_templateparser.php"
+#line 631 "smarty_internal_templateparser.y"
+    function yy_r172(){$this->_retvalue = '<=';    }
+#line 2719 "smarty_internal_templateparser.php"
+#line 632 "smarty_internal_templateparser.y"
+    function yy_r173(){$this->_retvalue = '===';    }
+#line 2722 "smarty_internal_templateparser.php"
+#line 633 "smarty_internal_templateparser.y"
+    function yy_r174(){$this->_retvalue = '!==';    }
+#line 2725 "smarty_internal_templateparser.php"
+#line 634 "smarty_internal_templateparser.y"
+    function yy_r175(){$this->_retvalue = '%';    }
+#line 2728 "smarty_internal_templateparser.php"
+#line 636 "smarty_internal_templateparser.y"
+    function yy_r176(){$this->_retvalue = '&&';    }
+#line 2731 "smarty_internal_templateparser.php"
+#line 637 "smarty_internal_templateparser.y"
+    function yy_r177(){$this->_retvalue = '||';    }
+#line 2734 "smarty_internal_templateparser.php"
+#line 638 "smarty_internal_templateparser.y"
+    function yy_r178(){$this->_retvalue = ' XOR ';    }
+#line 2737 "smarty_internal_templateparser.php"
+#line 643 "smarty_internal_templateparser.y"
+    function yy_r179(){ $this->_retvalue = 'array('.$this->yystack[$this->yyidx + -1]->minor.')';    }
+#line 2740 "smarty_internal_templateparser.php"
+#line 645 "smarty_internal_templateparser.y"
+    function yy_r181(){ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor;     }
+#line 2743 "smarty_internal_templateparser.php"
+#line 646 "smarty_internal_templateparser.y"
+    function yy_r182(){ return;     }
+#line 2746 "smarty_internal_templateparser.php"
+#line 647 "smarty_internal_templateparser.y"
+    function yy_r183(){ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'=>'.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2749 "smarty_internal_templateparser.php"
+#line 648 "smarty_internal_templateparser.y"
+    function yy_r184(){ $this->_retvalue = '\''.$this->yystack[$this->yyidx + -2]->minor.'\'=>'.$this->yystack[$this->yyidx + 0]->minor;    }
+#line 2752 "smarty_internal_templateparser.php"
+#line 655 "smarty_internal_templateparser.y"
+    function yy_r186(){ $this->_retvalue = "''";     }
+#line 2755 "smarty_internal_templateparser.php"
+#line 656 "smarty_internal_templateparser.y"
+    function yy_r187(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor->to_smarty_php();     }
+#line 2758 "smarty_internal_templateparser.php"
+#line 658 "smarty_internal_templateparser.y"
+    function yy_r188(){ $this->yystack[$this->yyidx + -1]->minor->append_subtree($this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;     }
+#line 2761 "smarty_internal_templateparser.php"
+#line 659 "smarty_internal_templateparser.y"
+    function yy_r189(){ $this->_retvalue = new _smarty_doublequoted($this, $this->yystack[$this->yyidx + 0]->minor);     }
+#line 2764 "smarty_internal_templateparser.php"
+#line 661 "smarty_internal_templateparser.y"
+    function yy_r190(){ $this->_retvalue = new _smarty_code($this, $this->yystack[$this->yyidx + -1]->minor);     }
+#line 2767 "smarty_internal_templateparser.php"
+#line 663 "smarty_internal_templateparser.y"
+    function yy_r192(){if (isset($this->compiler->local_var["'".substr($this->yystack[$this->yyidx + 0]->minor,1)."'"])) {
+                                                       $this->_retvalue = new _smarty_code($this, '$_smarty_tpl->tpl_vars[\''. substr($this->yystack[$this->yyidx + 0]->minor,1) .'\']->value');
+                                                      } else {
+                                                       $this->_retvalue = new _smarty_code($this, '$_smarty_tpl->getVariable(\''. substr($this->yystack[$this->yyidx + 0]->minor,1) .'\')->value');
+                                                      }
+                                                      $this->compiler->tag_nocache = $this->compiler->tag_nocache | $this->template->getVariable(trim($this->yystack[$this->yyidx + 0]->minor,"'"), null, true, false)->nocache;
+      }
+#line 2776 "smarty_internal_templateparser.php"
+#line 671 "smarty_internal_templateparser.y"
+    function yy_r194(){ $this->_retvalue = new _smarty_code($this, '('.$this->yystack[$this->yyidx + -1]->minor.')');     }
+#line 2779 "smarty_internal_templateparser.php"
+#line 672 "smarty_internal_templateparser.y"
+    function yy_r195(){
+   $this->_retvalue = new _smarty_tag($this, $this->yystack[$this->yyidx + 0]->minor);
+      }
+#line 2784 "smarty_internal_templateparser.php"
+#line 675 "smarty_internal_templateparser.y"
+    function yy_r196(){ $this->_retvalue = new _smarty_dq_content($this, $this->yystack[$this->yyidx + 0]->minor);     }
+#line 2787 "smarty_internal_templateparser.php"
+#line 682 "smarty_internal_templateparser.y"
+    function yy_r198(){$this->_retvalue = '';    }
+#line 2790 "smarty_internal_templateparser.php"
+
+    private $_retvalue;
+
+    function yy_reduce($yyruleno)
+    {
+        $yymsp = $this->yystack[$this->yyidx];
+        if (self::$yyTraceFILE && $yyruleno >= 0 
+              && $yyruleno < count(self::$yyRuleName)) {
+            fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
+                self::$yyTracePrompt, $yyruleno,
+                self::$yyRuleName[$yyruleno]);
+        }
+
+        $this->_retvalue = $yy_lefthand_side = null;
+        if (array_key_exists($yyruleno, self::$yyReduceMap)) {
+            // call the action
+            $this->_retvalue = null;
+            $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
+            $yy_lefthand_side = $this->_retvalue;
+        }
+        $yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
+        $yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
+        $this->yyidx -= $yysize;
+        for($i = $yysize; $i; $i--) {
+            // pop all of the right-hand side parameters
+            array_pop($this->yystack);
+        }
+        $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
+        if ($yyact < self::YYNSTATE) {
+            if (!self::$yyTraceFILE && $yysize) {
+                $this->yyidx++;
+                $x = new TP_yyStackEntry;
+                $x->stateno = $yyact;
+                $x->major = $yygoto;
+                $x->minor = $yy_lefthand_side;
+                $this->yystack[$this->yyidx] = $x;
+            } else {
+                $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
+            }
+        } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
+            $this->yy_accept();
+        }
+    }
+
+    function yy_parse_failed()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $this->yy_pop_parser_stack();
+        }
+    }
+
+    function yy_syntax_error($yymajor, $TOKEN)
+    {
+#line 66 "smarty_internal_templateparser.y"
+
+    $this->internalError = true;
+    $this->yymajor = $yymajor;
+    $this->compiler->trigger_template_error();
+#line 2853 "smarty_internal_templateparser.php"
+    }
+
+    function yy_accept()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $stack = $this->yy_pop_parser_stack();
+        }
+#line 58 "smarty_internal_templateparser.y"
+
+    $this->successful = !$this->internalError;
+    $this->internalError = false;
+    $this->retvalue = $this->_retvalue;
+    //echo $this->retvalue."\n\n";
+#line 2871 "smarty_internal_templateparser.php"
+    }
+
+    function doParse($yymajor, $yytokenvalue)
+    {
+        $yyerrorhit = 0;   /* True if yymajor has invoked an error */
+        
+        if ($this->yyidx === null || $this->yyidx < 0) {
+            $this->yyidx = 0;
+            $this->yyerrcnt = -1;
+            $x = new TP_yyStackEntry;
+            $x->stateno = 0;
+            $x->major = 0;
+            $this->yystack = array();
+            array_push($this->yystack, $x);
+        }
+        $yyendofinput = ($yymajor==0);
+        
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sInput %s\n",
+                self::$yyTracePrompt, $this->yyTokenName[$yymajor]);
+        }
+        
+        do {
+            $yyact = $this->yy_find_shift_action($yymajor);
+            if ($yymajor < self::YYERRORSYMBOL &&
+                  !$this->yy_is_expected_token($yymajor)) {
+                // force a syntax error
+                $yyact = self::YY_ERROR_ACTION;
+            }
+            if ($yyact < self::YYNSTATE) {
+                $this->yy_shift($yyact, $yymajor, $yytokenvalue);
+                $this->yyerrcnt--;
+                if ($yyendofinput && $this->yyidx >= 0) {
+                    $yymajor = 0;
+                } else {
+                    $yymajor = self::YYNOCODE;
+                }
+            } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
+                $this->yy_reduce($yyact - self::YYNSTATE);
+            } elseif ($yyact == self::YY_ERROR_ACTION) {
+                if (self::$yyTraceFILE) {
+                    fprintf(self::$yyTraceFILE, "%sSyntax Error!\n",
+                        self::$yyTracePrompt);
+                }
+                if (self::YYERRORSYMBOL) {
+                    if ($this->yyerrcnt < 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $yymx = $this->yystack[$this->yyidx]->major;
+                    if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){
+                        if (self::$yyTraceFILE) {
+                            fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n",
+                                self::$yyTracePrompt, $this->yyTokenName[$yymajor]);
+                        }
+                        $this->yy_destructor($yymajor, $yytokenvalue);
+                        $yymajor = self::YYNOCODE;
+                    } else {
+                        while ($this->yyidx >= 0 &&
+                                 $yymx != self::YYERRORSYMBOL &&
+        ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
+                              ){
+                            $this->yy_pop_parser_stack();
+                        }
+                        if ($this->yyidx < 0 || $yymajor==0) {
+                            $this->yy_destructor($yymajor, $yytokenvalue);
+                            $this->yy_parse_failed();
+                            $yymajor = self::YYNOCODE;
+                        } elseif ($yymx != self::YYERRORSYMBOL) {
+                            $u2 = 0;
+                            $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
+                        }
+                    }
+                    $this->yyerrcnt = 3;
+                    $yyerrorhit = 1;
+                } else {
+                    if ($this->yyerrcnt <= 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $this->yyerrcnt = 3;
+                    $this->yy_destructor($yymajor, $yytokenvalue);
+                    if ($yyendofinput) {
+                        $this->yy_parse_failed();
+                    }
+                    $yymajor = self::YYNOCODE;
+                }
+            } else {
+                $this->yy_accept();
+                $yymajor = self::YYNOCODE;
+            }            
+        } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_utility.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_utility.php
new file mode 100644 (file)
index 0000000..b532e4a
--- /dev/null
@@ -0,0 +1,300 @@
+<?php
+
+/**
+ * Project:     Smarty: the PHP compiling template engine
+ * File:        smarty_internal_utility.php
+ * SVN:         $Id: $
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ * For questions, help, comments, discussion, etc., please join the
+ * Smarty mailing list. Send a blank e-mail to
+ * smarty-discussion-subscribe@googlegroups.com
+ * 
+ * @link http://www.smarty.net/
+ * @copyright 2008 New Digital Group, Inc.
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author Uwe Tews 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @version 3-SVN$Rev: 3286 $
+ */
+
+class Smarty_Internal_Utility {
+    protected $smarty;
+
+    function __construct($smarty)
+    {
+        $this->smarty = $smarty;
+    } 
+
+    /**
+     * Compile all template files
+     * 
+     * @param string $extension file extension
+     * @param bool $force_compile force all to recompile
+     * @param int $time_limit 
+     * @param int $max_errors 
+     * @return integer number of template files recompiled
+     */
+    function compileAllTemplates($extention = '.tpl', $force_compile = false, $time_limit = 0, $max_errors = null)
+    { 
+        // switch off time limit
+        if (function_exists('set_time_limit')) {
+            @set_time_limit($time_limit);
+        } 
+        $this->smarty->force_compile = $force_compile;
+        $_count = 0;
+        $_error_count = 0; 
+        // loop over array of template directories
+        foreach((array)$this->smarty->template_dir as $_dir) {
+            if (strpos('/\\', substr($_dir, -1)) === false) {
+                $_dir .= DS;
+            } 
+            $_compileDirs = new RecursiveDirectoryIterator($_dir);
+            $_compile = new RecursiveIteratorIterator($_compileDirs);
+            foreach ($_compile as $_fileinfo) {
+                if (strpos($_fileinfo, '.svn') !== false) continue;
+                $_file = $_fileinfo->getFilename();
+                if (!substr_compare($_file, $extention, - strlen($extention)) == 0) continue;
+                if ($_fileinfo->getPath() == substr($_dir, 0, -1)) {
+                   $_template_file = $_file;
+                } else {
+                   $_template_file = substr($_fileinfo->getPath(), strlen($_dir)) . DS . $_file;
+                }
+                echo '<br>', $_dir, '---', $_template_file;
+                flush();
+                $_start_time = microtime(true);
+                try {
+                    $_tpl = $this->smarty->createTemplate($_template_file,null,null,null,false);
+                    if ($_tpl->mustCompile()) {
+                        $_tpl->compileTemplateSource();
+                        echo ' compiled in  ', microtime(true) - $_start_time, ' seconds';
+                        flush();
+                    } else {
+                        echo ' is up to date';
+                        flush();
+                    }
+                }
+                catch (Exception $e) {
+                    echo 'Error: ', $e->getMessage(), "<br><br>";
+                    $_error_count++;
+                } 
+                               // free memory
+                $this->smarty->template_objects = array();
+                $_tpl->smarty->template_objects = array();
+                $_tpl = null;
+                if ($max_errors !== null && $_error_count == $max_errors) {
+                    echo '<br><br>too many errors';
+                    exit();
+                } 
+            } 
+        } 
+        return $_count;
+    } 
+
+    /**
+     * Compile all config files
+     * 
+     * @param string $extension file extension
+     * @param bool $force_compile force all to recompile
+     * @param int $time_limit 
+     * @param int $max_errors 
+     * @return integer number of template files recompiled
+     */
+    function compileAllConfig($extention = '.conf', $force_compile = false, $time_limit = 0, $max_errors = null)
+    { 
+        // switch off time limit
+        if (function_exists('set_time_limit')) {
+            @set_time_limit($time_limit);
+        } 
+        $this->smarty->force_compile = $force_compile;
+        $_count = 0;
+        $_error_count = 0; 
+        // loop over array of template directories
+        foreach((array)$this->smarty->config_dir as $_dir) {
+            if (strpos('/\\', substr($_dir, -1)) === false) {
+                $_dir .= DS;
+            } 
+            $_compileDirs = new RecursiveDirectoryIterator($_dir);
+            $_compile = new RecursiveIteratorIterator($_compileDirs);
+            foreach ($_compile as $_fileinfo) {
+                if (strpos($_fileinfo, '.svn') !== false) continue;
+                $_file = $_fileinfo->getFilename();
+                if (!substr_compare($_file, $extention, - strlen($extention)) == 0) continue;
+                if ($_fileinfo->getPath() == substr($_dir, 0, -1)) {
+                    $_config_file = $_file;
+                } else {
+                    $_config_file = substr($_fileinfo->getPath(), strlen($_dir)) . DS . $_file;
+                } 
+                echo '<br>', $_dir, '---', $_config_file;
+                flush();
+                $_start_time = microtime(true);
+                try {
+                    $_config = new Smarty_Internal_Config($_config_file, $this->smarty);
+                    if ($_config->mustCompile()) {
+                        $_config->compileConfigSource();
+                        echo ' compiled in  ', microtime(true) - $_start_time, ' seconds';
+                        flush();
+                    } else {
+                        echo ' is up to date';
+                        flush();
+                    } 
+                } 
+                catch (Exception $e) {
+                    echo 'Error: ', $e->getMessage(), "<br><br>";
+                    $_error_count++;
+                } 
+                if ($max_errors !== null && $_error_count == $max_errors) {
+                    echo '<br><br>too many errors';
+                    exit();
+                } 
+            } 
+        } 
+        return $_count;
+    } 
+
+    /**
+     * Delete compiled template file
+     * 
+     * @param string $resource_name template name
+     * @param string $compile_id compile id
+     * @param integer $exp_time expiration time
+     * @return integer number of template files deleted
+     */
+    function clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null)
+    {
+        $_compile_id = isset($compile_id) ? preg_replace('![^\w\|]+!', '_', $compile_id) : null;
+        $_dir_sep = $this->smarty->use_sub_dirs ? DS : '^';
+        if (isset($resource_name)) {
+            $_resource_part_1 = $resource_name . '.php';
+            $_resource_part_2 = $resource_name . '.cache' . '.php';
+        } else {
+            $_resource_part = '';
+        } 
+        $_dir = $this->smarty->compile_dir;
+        if ($this->smarty->use_sub_dirs && isset($_compile_id)) {
+            $_dir .= $_compile_id . $_dir_sep;
+        } 
+        if (isset($_compile_id)) {
+            $_compile_id_part = $this->smarty->compile_dir . $_compile_id . $_dir_sep;
+        } 
+        $_count = 0;
+        $_compileDirs = new RecursiveDirectoryIterator($_dir);
+        $_compile = new RecursiveIteratorIterator($_compileDirs, RecursiveIteratorIterator::CHILD_FIRST);
+        foreach ($_compile as $_file) {
+            if (strpos($_file, '.svn') !== false) continue;
+            if ($_file->isDir()) {
+                if (!$_compile->isDot()) {
+                    // delete folder if empty
+                    @rmdir($_file->getPathname());
+                } 
+            } else {
+                if ((!isset($_compile_id) || (strlen((string)$_file) > strlen($_compile_id_part) && substr_compare((string)$_file, $_compile_id_part, 0, strlen($_compile_id_part)) == 0)) &&
+                        (!isset($resource_name) || (strlen((string)$_file) > strlen($_resource_part_1) && substr_compare((string)$_file, $_resource_part_1, - strlen($_resource_part_1), strlen($_resource_part_1)) == 0) ||
+                            (strlen((string)$_file) > strlen($_resource_part_2) && substr_compare((string)$_file, $_resource_part_2, - strlen($_resource_part_2), strlen($_resource_part_2)) == 0))) {
+                    if (isset($exp_time)) {
+                        if (time() - @filemtime($_file) >= $exp_time) {
+                            $_count += @unlink((string) $_file) ? 1 : 0;
+                        } 
+                    } else {
+                        $_count += @unlink((string) $_file) ? 1 : 0;
+                    } 
+                } 
+            } 
+        } 
+        return $_count;
+    } 
+
+    /**
+     * Return array of tag/attributes of all tags used by an template
+     * 
+     * @param object $templae template object
+     * @return array of tag/attributes
+     */
+       function getTags(Smarty_Internal_Template $template) 
+       {
+               $template->smarty->get_used_tags = true;
+               $template->compileTemplateSource();
+               return $template->compiler_object->used_tags;
+       }       
+       
+    function testInstall()
+    {
+        echo "<PRE>\n";
+
+        echo "Smarty Installation test...\n";
+
+        echo "Testing template directory...\n";
+
+        foreach((array)$this->smarty->template_dir as $template_dir) {
+            if (!is_dir($template_dir))
+                echo "FAILED: $template_dir is not a directory.\n";
+            elseif (!is_readable($template_dir))
+                echo "FAILED: $template_dir is not readable.\n";
+            else
+                echo "$template_dir is OK.\n";
+        } 
+
+        echo "Testing compile directory...\n";
+
+        if (!is_dir($this->smarty->compile_dir))
+            echo "FAILED: {$this->smarty->compile_dir} is not a directory.\n";
+        elseif (!is_readable($this->smarty->compile_dir))
+            echo "FAILED: {$this->smarty->compile_dir} is not readable.\n";
+        elseif (!is_writable($this->smarty->compile_dir))
+            echo "FAILED: {$this->smarty->compile_dir} is not writable.\n";
+        else
+            echo "{$this->smarty->compile_dir} is OK.\n";
+
+        echo "Testing plugins directory...\n";
+
+        foreach((array)$this->smarty->plugins_dir as $plugin_dir) {
+            if (!is_dir($plugin_dir))
+                echo "FAILED: $plugin_dir is not a directory.\n";
+            elseif (!is_readable($plugin_dir))
+                echo "FAILED: $plugin_dir is not readable.\n";
+            else
+                echo "$plugin_dir is OK.\n";
+        } 
+
+        echo "Testing cache directory...\n";
+
+        if (!is_dir($this->smarty->cache_dir))
+            echo "FAILED: {$this->smarty->cache_dir} is not a directory.\n";
+        elseif (!is_readable($this->smarty->cache_dir))
+            echo "FAILED: {$this->smarty->cache_dir} is not readable.\n";
+        elseif (!is_writable($this->smarty->cache_dir))
+            echo "FAILED: {$this->smarty->cache_dir} is not writable.\n";
+        else
+            echo "{$this->smarty->cache_dir} is OK.\n";
+
+        echo "Testing configs directory...\n";
+
+        if (!is_dir($this->smarty->config_dir))
+            echo "FAILED: {$this->smarty->config_dir} is not a directory.\n";
+        elseif (!is_readable($this->smarty->config_dir))
+            echo "FAILED: {$this->smarty->config_dir} is not readable.\n";
+        else
+            echo "{$this->smarty->config_dir} is OK.\n";
+
+        echo "Tests complete.\n";
+
+        echo "</PRE>\n";
+
+        return true;
+    } 
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_wrapper.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_wrapper.php
new file mode 100644 (file)
index 0000000..f45153c
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * Project:     Smarty: the PHP compiling template engine
+ * File:        smarty_internal_wrapper.php
+ * SVN:         $Id: $
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ * For questions, help, comments, discussion, etc., please join the
+ * Smarty mailing list. Send a blank e-mail to
+ * smarty-discussion-subscribe@googlegroups.com
+ * 
+ * @link http://www.smarty.net/
+ * @copyright 2008 New Digital Group, Inc.
+ * @author Monte Ohrt <monte at ohrt dot com> 
+ * @author Uwe Tews 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @version 3-SVN$Rev: 3286 $
+ */
+
+/*
+ * Smarty Backward Compatability Wrapper
+ */
+
+class Smarty_Internal_Wrapper {
+  
+    protected $smarty;
+
+    function __construct($smarty) {
+      $this->smarty = $smarty;
+    }
+    
+    /**
+     * Converts smarty2-style function call to smarty 3-style function call
+     * This is expensive, be sure to port your code to Smarty 3!
+     * 
+     * @param string $name Smarty 2 function name
+     * @param array $args Smarty 2 function args
+     */
+    function convert($name, $args) {
+       // throw notice about deprecated function
+       if($this->smarty->deprecation_notices)
+         trigger_error("function call '$name' is unknown or deprecated.",E_USER_NOTICE);
+       // get first and last part of function name
+       $name_parts = explode('_',$name,2);
+       switch($name_parts[0]) {
+         case 'register':
+         case 'unregister':
+           switch($name_parts[1]) {
+              case 'object':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Object"),$args);
+              case 'compiler_function':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Plugin"),array_merge(array('compiler'),$args));
+              case 'prefilter':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Filter"),array_merge(array('pre'),$args));
+              case 'postfilter':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Filter"),array_merge(array('post'),$args));
+              case 'outputfilter':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Filter"),array_merge(array('output'),$args));
+             case 'resource':
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Resource"),$args);
+              default:
+                 return call_user_func_array(array($this->smarty,"{$name_parts[0]}Plugin"),array_merge(array($name_parts[1]),$args));
+           }
+           case 'get':
+           switch($name_parts[1]) {
+              case 'template_vars':
+                 return call_user_func_array(array($this->smarty,'getTemplateVars'),$args);
+              case 'config_vars':
+                 return call_user_func_array(array($this->smarty,'getConfigVars'),$args);
+              default:
+                 return call_user_func_array(array($myobj,$name_parts[1]),$args);
+           }
+           case 'clear':
+           switch($name_parts[1]) {
+              case 'all_assign':
+                 return call_user_func_array(array($this->smarty,'clearAllAssign'),$args);
+              case 'assign':
+                 return call_user_func_array(array($this->smarty,'clearAssign'),$args);
+              case 'all_cache':
+                 return call_user_func_array(array($this->smarty,'clearAllCache'),$args);
+              case 'cache':
+                 return call_user_func_array(array($this->smarty,'clearCache'),$args);
+              case 'compiled_template':
+                 return call_user_func_array(array($this->smarty,'clearCompiledTemplate'),$args);
+           }
+           case 'config':
+           switch($name_parts[1]) {
+              case 'load':
+                 return call_user_func_array(array($this->smarty,'configLoad'),$args);
+           }
+           case 'trigger':
+           switch($name_parts[1]) {
+              case 'error':
+                 return call_user_func_array('trigger_error',$args);
+           }
+           case 'load':
+           switch($name_parts[1]) {
+              case 'filter':
+                 return call_user_func_array(array($this->smarty,'loadFilter'),$args);
+           }
+       }
+       throw new SmartyException("unknown method '$name'");
+    }
+
+    /**
+     * trigger Smarty error
+     *
+     * @param string $error_msg
+     * @param integer $error_type
+     */
+    function trigger_error($error_msg, $error_type = E_USER_WARNING)
+    {
+        trigger_error("Smarty error: $error_msg", $error_type);
+    }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_internal_write_file.php b/WEB-INF/lib/smarty/sysplugins/smarty_internal_write_file.php
new file mode 100644 (file)
index 0000000..4e34335
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Smarty write file plugin
+ * 
+ * @package Smarty
+ * @subpackage PluginsInternal
+ * @author Monte Ohrt 
+ */
+
+/**
+ * Smarty Internal Write File Class
+ */
+class Smarty_Internal_Write_File {
+    /**
+     * Writes file in a save way to disk
+     * 
+     * @param string $_filepath complete filepath
+     * @param string $_contents file content
+     * @return boolean true
+     */
+    public static function writeFile($_filepath, $_contents, $smarty)
+    {
+        $old_umask = umask(0);
+        $_dirpath = dirname($_filepath); 
+        // if subdirs, create dir structure
+        if ($_dirpath !== '.' && !file_exists($_dirpath)) {
+            mkdir($_dirpath, $smarty->_dir_perms, true);
+        } 
+        // write to tmp file, then move to overt file lock race condition
+        $_tmp_file = tempnam($_dirpath, 'wrt');
+
+           if (!($fd = @fopen($_tmp_file, 'wb'))) {
+               $_tmp_file = $_dirpath . DS . uniqid('wrt');
+               if (!($fd = @fopen($_tmp_file, 'wb'))) {
+            throw new SmartyException("unable to write file {$_tmp_file}");
+            return false;
+               }
+                }
+
+       fwrite($fd, $_contents);
+       fclose($fd);
+
+        // remove original file
+        if (file_exists($_filepath))
+            @unlink($_filepath); 
+        // rename tmp file
+        rename($_tmp_file, $_filepath); 
+        // set file permissions
+        chmod($_filepath, $smarty->_file_perms);
+        umask($old_umask);
+        return true;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/smarty/sysplugins/smarty_security.php b/WEB-INF/lib/smarty/sysplugins/smarty_security.php
new file mode 100644 (file)
index 0000000..cd066fb
--- /dev/null
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Smarty plugin
+ * 
+ * @package Smarty
+ * @subpackage Security
+ * @author Uwe Tews 
+ */ 
+
+/**
+ * This class does contain the security settings
+ */
+class Smarty_Security {
+    /**
+     * This determines how Smarty handles "<?php ... ?>" tags in templates.
+     * possible values:
+     * <ul>
+     *   <li>Smarty::PHP_PASSTHRU -> echo PHP tags as they are</li>
+     *   <li>Smarty::PHP_QUOTE    -> escape tags as entities</li>
+     *   <li>Smarty::PHP_REMOVE   -> remove php tags</li>
+     *   <li>Smarty::PHP_ALLOW    -> execute php tags</li>
+     * </ul>
+     * 
+     * @var integer 
+     */
+    public $php_handling = Smarty::PHP_PASSTHRU;
+
+    /**
+     * This is the list of template directories that are considered secure.
+     * $template_dir is in this list implicitly.
+     * 
+     * @var array 
+     */
+    public $secure_dir = array();
+
+
+    /**
+     * This is an array of directories where trusted php scripts reside.
+     * {@link $security} is disabled during their inclusion/execution.
+     * 
+     * @var array 
+     */
+    public $trusted_dir = array();
+
+
+    /**
+     * This is an array of trusted static classes.
+     *
+     * If empty access to all static classes is allowed.
+     * If set to 'none' none is allowed.
+     * @var array 
+     */
+    public $static_classes = array();
+
+    /**
+     * This is an array of trusted PHP functions.
+     *
+     * If empty all functions are allowed.
+     * To disable all PHP functions set $php_functions = null.
+     * @var array 
+     */
+    public $php_functions = array('isset', 'empty',
+            'count', 'sizeof','in_array', 'is_array','time','nl2br');
+
+    /**
+     * This is an array of trusted PHP modifers.
+     *
+     * If empty all modifiers are allowed.
+     * To disable all modifier set $modifiers = null.
+     * @var array 
+     */
+    public $php_modifiers = array('escape','count');
+
+    /**
+     * This is an array of trusted streams.
+     *
+     * If empty all streams are allowed.
+     * To disable all streams set $streams = null.
+     * @var array 
+     */
+    public $streams = array('file');
+    /**
+     * + flag if constants can be accessed from template
+     */
+    public $allow_constants = true;
+    /**
+     * + flag if super globals can be accessed from template
+     */
+    public $allow_super_globals = true;
+    /**
+     * + flag if the {php} and {include_php} tag can be executed
+     */
+    public $allow_php_tag = false;
+
+    public function __construct($smarty)
+    {
+        $this->smarty = $smarty; 
+       }
+    /**
+     * Check if PHP function is trusted.
+     * 
+     * @param string $function_name 
+     * @param object $compiler compiler object
+     * @return boolean true if function is trusted
+     */
+    function isTrustedPhpFunction($function_name, $compiler)
+    {
+        if (isset($this->php_functions) && (empty($this->php_functions) || in_array($function_name, $this->php_functions))) {
+            return true;
+        } else {
+            $compiler->trigger_template_error ("PHP function '{$function_name}' not allowed by security setting");
+            return false;
+        } 
+    } 
+
+    /**
+     * Check if static class is trusted.
+     * 
+     * @param string $class_name 
+     * @param object $compiler compiler object
+     * @return boolean true if class is trusted
+     */
+    function isTrustedStaticClass($class_name, $compiler)
+    {
+        if (isset($this->static_classes) && (empty($this->static_classes) || in_array($class_name, $this->static_classes))) {
+            return true;
+        } else {
+            $compiler->trigger_template_error ("access to static class '{$class_name}' not allowed by security setting");
+            return false;
+        } 
+    } 
+    /**
+     * Check if modifier is trusted.
+     * 
+     * @param string $modifier_name 
+     * @param object $compiler compiler object
+     * @return boolean true if modifier is trusted
+     */
+    function isTrustedModifier($modifier_name, $compiler)
+    {
+        if (isset($this->php_modifiers) && (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))) {
+            return true;
+        } else {
+            $compiler->trigger_template_error ("modifier '{$modifier_name}' not allowed by security setting");
+            return false;
+        } 
+    } 
+    /**
+     * Check if stream is trusted.
+     * 
+     * @param string $stream_name 
+     * @param object $compiler compiler object
+     * @return boolean true if stream is trusted
+     */
+    function isTrustedStream($stream_name)
+    {
+        if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) {
+            return true;
+        } else {
+            throw new SmartyException ("stream '{$stream_name}' not allowed by security setting");
+            return false;
+        } 
+    } 
+
+    /**
+     * Check if directory of file resource is trusted.
+     * 
+     * @param string $filepath 
+     * @param object $compiler compiler object
+     * @return boolean true if directory is trusted
+     */
+    function isTrustedResourceDir($filepath)
+    {
+        $_rp = realpath($filepath);
+        if (isset($this->smarty->template_dir)) {
+            foreach ((array)$this->smarty->template_dir as $curr_dir) {
+                if (($_cd = realpath($curr_dir)) !== false &&
+                        strncmp($_rp, $_cd, strlen($_cd)) == 0 &&
+                        (strlen($_rp) == strlen($_cd) || substr($_rp, strlen($_cd), 1) == DS)) {
+                    return true;
+                } 
+            } 
+        } 
+        if (!empty($this->smarty->security_policy->secure_dir)) {
+            foreach ((array)$this->smarty->security_policy->secure_dir as $curr_dir) {
+                if (($_cd = realpath($curr_dir)) !== false) {
+                    if ($_cd == $_rp) {
+                        return true;
+                    } elseif (strncmp($_rp, $_cd, strlen($_cd)) == 0 &&
+                            (strlen($_rp) == strlen($_cd) || substr($_rp, strlen($_cd), 1) == DS)) {
+                        return true;
+                    } 
+                } 
+            } 
+        } 
+
+        throw new SmartyException ("directory '{$_rp}' not allowed by security setting");
+        return false;
+    } 
+    
+    /**
+     * Check if directory of file resource is trusted.
+     * 
+     * @param string $filepath 
+     * @param object $compiler compiler object
+     * @return boolean true if directory is trusted
+     */
+    function isTrustedPHPDir($filepath)
+    {
+        $_rp = realpath($filepath);
+        if (!empty($this->trusted_dir)) {
+            foreach ((array)$this->trusted_dir as $curr_dir) {
+                if (($_cd = realpath($curr_dir)) !== false) {
+                    if ($_cd == $_rp) {
+                        return true;
+                    } elseif (strncmp($_rp, $_cd, strlen($_cd)) == 0 &&
+                            substr($_rp, strlen($_cd), 1) == DS) {
+                        return true;
+                    } 
+                } 
+            } 
+        } 
+
+        throw new SmartyException ("directory '{$_rp}' not allowed by security setting");
+        return false;
+    } 
+} 
+
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/tdcron/class.tdcron.entry.php b/WEB-INF/lib/tdcron/class.tdcron.entry.php
new file mode 100644 (file)
index 0000000..d0f4d3c
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+
+       /**
+        * tinyCronEntry is part of tdCron. Its a class to parse Cron-Expressions like "1-45 1,2,3 1-30/5 January,February Mon,Tue"
+        * and convert it to an easily useable format.
+        *
+        * The parser is quite powerful and understands pretty much everything you will ever find in a Cron-Expression.
+        *
+        * A Cron-Expression consists of 5 segments:
+        *
+        * <pre>
+        *  .---------------- minute (0 - 59)
+        *  |   .------------- hour (0 - 23)
+        *  |   |   .---------- day of month (1 - 31)
+        *  |   |   |   .------- month (1 - 12)
+        *  |   |   |   |  .----- day of week (0 - 6)
+        *  |   |   |   |  |
+        *  *   *   *   *  *
+        * </pre>
+        *
+        * Each segment can contain values, ranges and intervals. A range is always written as "value1-value2" and
+        * intervals as "value1/value2".
+        *
+        * Of course each segment can contain multiple values seperated by commas.
+        *
+        * Some valid examples:
+        *
+        * <pre>
+        * 1,2,3,4,5
+        * 1-5
+        * 10-20/*
+        * Jan,Feb,Oct
+        * Monday-Friday
+        * 1-10,15,20,40-50/2
+        * </pre>
+        *
+        * The current version of the parser understands all weekdays and month names in german and english!
+        *
+        * Usually you won't need to call this class directly.
+        *
+        * Copyright (c) 2010 Christian Land / tagdocs.de
+        *
+        * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+        * associated documentation files (the "Software"), to deal in the Software without restriction,
+        * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+        * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+        * subject to the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be included in all copies or substantial
+        * portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+        * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+        * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+        * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * @author      Christian Land <devel@tagdocs.de>
+        * @package     tinyCron
+        * @subpackage  tinyCronEntry
+        * @copyright   Copyright (c) 2010, Christian Land / tagdocs.de
+        * @version     v0.0.1 beta
+        */
+
+       class tdCronEntry {
+
+               /**
+                * The parsed cron-expression.
+                * @var mixed
+                */
+               static private $cron            = array();
+
+               /**
+                * Ranges.
+                * @var mixed
+                */
+               static private $ranges          = array(
+                                                       IDX_MINUTE              => array( 'min' => 0,
+                                                                                         'max' => 59   ),      // Minutes
+                                                       IDX_HOUR                => array( 'min' => 0,
+                                                                                         'max' => 23   ),      // Hours
+                                                       IDX_DAY                 => array( 'min' => 1,
+                                                                                         'max' => 31   ),      // Days
+                                                       IDX_MONTH               => array( 'min' => 1,
+                                                                                         'max' => 12   ),      // Months
+                                                       IDX_WEEKDAY             => array( 'min' => 0,
+                                                                                         'max' => 7    )       // Weekdays
+                                               );
+
+               /**
+                * Named intervals.
+                * @var mixed
+                */
+               static private $intervals       = array(
+                                                       '@yearly'       => '0 0 1 1 *',
+                                                       '@annualy'      => '0 0 1 1 *',
+                                                       '@monthly'      => '0 0 1 * *',
+                                                       '@weekly'       => '0 0 * * 0',
+                                                       '@midnight'     => '0 0 * * *',
+                                                       '@daily'        => '0 0 * * *',
+                                                       '@hourly'       => '0 * * * *'
+                                                       );
+
+
+               /**
+                * Possible keywords for months/weekdays.
+                * @var mixed
+                */
+               static private $keywords        = array(
+                                                       IDX_MONTH       => array(
+                                                                               '/(january|januar|jan)/i'                       => 1,
+                                                                               '/(february|februar|feb)/i'                     => 2,
+                                                                               '/(march|maerz|märz|mar|mae|mär)/i'             => 3,
+                                                                               '/(april|apr)/i'                                => 4,
+                                                                               '/(may|mai)/i'                                  => 5,
+                                                                               '/(june|juni|jun)/i'                            => 6,
+                                                                               '/(july|juli|jul)/i'                            => 7,
+                                                                               '/(august|aug)/i'                               => 8,
+                                                                               '/(september|sep)/i'                            => 9,
+                                                                               '/(october|oktober|okt|oct)/i'                  => 10,
+                                                                               '/(november|nov)/i'                             => 11,
+                                                                               '/(december|dezember|dec|dez)/i'                => 12
+                                                                               ),
+                                                       IDX_WEEKDAY     => array(
+                                                                               '/(sunday|sonntag|sun|son|su|so)/i'             => 0,
+                                                                               '/(monday|montag|mon|mo)/i'                     => 1,
+                                                                               '/(tuesday|dienstag|die|tue|tu|di)/i'           => 2,
+                                                                               '/(wednesdays|mittwoch|mit|wed|we|mi)/i'        => 3,
+                                                                               '/(thursday|donnerstag|don|thu|th|do)/i'        => 4,
+                                                                               '/(friday|freitag|fre|fri|fr)/i'                => 5,
+                                                                               '/(saturday|samstag|sam|sat|sa)/i'              => 6
+                                                                               )
+                                                       );
+
+               /**
+                * parseExpression() analyses crontab-expressions like "* * 1,2,3 * mon,tue" and returns an array
+                * containing all values. If it can't be parsed, an exception is thrown.
+                *
+                * @access              public
+                * @param               string          $expression     The cron-expression to parse.
+                * @return              mixed
+                */
+
+               static public function parse($expression) {
+
+                       // Convert named expressions if neccessary
+
+                       if (substr($expression,0,1) == '@') {
+
+                               $expression     = strtr($expression, self::$intervals);
+
+                               if (substr($expression,0,1) == '@') {
+
+                                       // Oops... unknown named interval!?!!
+                                       throw new Exception('Unknown named interval ['.$expression.']', 10000);
+
+                               }
+
+                       }
+
+                       // Next basic check... do we have 5 segments?
+
+                       $cron   = explode(' ',$expression);
+
+                       if (count($cron) <> 5) {
+
+                               // No... we haven't...
+                               throw new Exception('Wrong number of segments in expression. Expected: 5, Found: '.count($cron), 10001);
+
+                       } else {
+
+                               // Yup, 5 segments... lets see if we can work with them
+
+                               foreach ($cron as $idx=>$segment) {
+
+                                       try {
+
+                                               $dummy[$idx]    = self::expandSegment($idx, $segment);
+
+                                       } catch (Exception $e) {
+
+                                               throw $e;
+
+                                       }
+
+                               }
+
+                       }
+
+                       return $dummy;
+
+               }
+
+               /**
+                * expandSegment() analyses a single segment
+                *
+                * @access              public
+                * @param               void
+                * @return              void
+                */
+
+               static private function expandSegment($idx, $segment) {
+
+                       // Store original segment for later use
+
+                       $osegment       = $segment;
+
+                       // Replace months/weekdays like "January", "February", etc. with numbers
+
+                       if (isset(self::$keywords[$idx])) {
+
+                               $segment        = preg_replace(
+                                                               array_keys(self::$keywords[$idx]),
+                                                               array_values(self::$keywords[$idx]),
+                                                               $segment
+                                                               );
+
+                       }
+
+                       // Replace wildcards
+
+                       if (substr($segment,0,1) == '*') {
+
+                               $segment        = preg_replace('/^\*(\/\d+)?$/i',
+                                                               self::$ranges[$idx]['min'].'-'.self::$ranges[$idx]['max'].'$1',
+                                                               $segment);
+
+                       }
+
+                       // Make sure that nothing unparsed is left :)
+
+                       $dummy          = preg_replace('/[0-9\-\/\,]/','',$segment);
+
+                       if (!empty($dummy)) {
+
+                               // Ohoh.... thats not good :-)
+                               throw new Exception('Failed to parse segment: '.$osegment, 10002);
+
+                       }
+
+                       // At this point our string should be OK - lets convert it to an array
+
+                       $result         = array();
+                       $atoms          = explode(',',$segment);
+
+                       foreach ($atoms as $curatom) {
+
+                               $result = array_merge($result, self::parseAtom($curatom));
+
+                       }
+
+                       // Get rid of duplicates and sort the array
+
+                       $result         = array_unique($result);
+                       sort($result);
+
+                       // Check for invalid values
+
+                       if ($idx == IDX_WEEKDAY) {
+
+                               if (end($result) == 7) {
+
+                                       if (reset($result) <> 0) {
+                                               array_unshift($result, 0);
+                                       }
+
+                                       array_pop($result);
+
+                               }
+
+                       }
+
+                       foreach ($result as $key=>$value) {
+
+                               if (($value < self::$ranges[$idx]['min']) || ($value > self::$ranges[$idx]['max'])) {
+                                       throw new Exception('Failed to parse segment, invalid value ['.$value.']: '.$osegment, 10003);
+                               }
+
+                       }
+
+                       return $result;
+
+               }
+
+               /**
+                * parseAtom() analyses a single segment
+                *
+                * @access              public
+                * @param               string          $atom           The segment to parse
+                * @return              array
+                */
+
+               static private function parseAtom($atom) {
+
+                       $expanded       = array();
+
+                       if (preg_match('/^(\d+)-(\d+)(\/(\d+))?/i', $atom, $matches)) {
+
+                               $low    = $matches[1];
+                               $high   = $matches[2];
+
+                               if ($low > $high) {
+                                       list($low,$high)        = array($high,$low);
+                               }
+
+                               $step   = isset($matches[4]) ? $matches[4] : 1;
+
+                               for($i = $low; $i <= $high; $i += $step) {
+                                       $expanded[]     = (int)$i;
+                               }
+
+                       } else {
+
+                               $expanded[]     = (int)$atom;
+
+                       }
+
+                       $expanded2      = array_unique($expanded);
+
+                       return $expanded;
+
+               }
+
+       }
\ No newline at end of file
diff --git a/WEB-INF/lib/tdcron/class.tdcron.php b/WEB-INF/lib/tdcron/class.tdcron.php
new file mode 100644 (file)
index 0000000..0167555
--- /dev/null
@@ -0,0 +1,441 @@
+<?php
+
+       define('IDX_MINUTE',                    0);
+       define('IDX_HOUR',                      1);
+       define('IDX_DAY',                       2);
+       define('IDX_MONTH',                     3);
+       define('IDX_WEEKDAY',                   4);
+       define('IDX_YEAR',                      5);
+
+       /*
+        * tdCron v0.0.1 beta - CRON-Parser for PHP
+        *
+        * Copyright (c) 2010 Christian Land / tagdocs.de
+        *
+        * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+        * associated documentation files (the "Software"), to deal in the Software without restriction,
+        * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+        * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+        * subject to the following conditions:
+        *
+        * The above copyright notice and this permission notice shall be included in all copies or substantial
+        * portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+        * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+        * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+        * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+        * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+        *
+        * @author      Christian Land <devel@tagdocs.de>
+        * @package     tdCron
+        * @copyright   Copyright (c) 2010, Christian Land / tagdocs.de
+        * @version     v0.0.1 beta
+        */
+       class tdCron {
+
+               /**
+                * Parsed cron-expressions cache.
+                * @var mixed
+                */
+               static private $pcron = array();
+
+               /**
+                * getNextOccurrence() uses a cron-expression to calculate the time and date at which a cronjob
+                * should be executed the next time. If a reference-time is passed, the next time and date
+                * after that time is calculated.
+                *
+                * @access      public
+                * @param       string          $expression     cron-expression to use
+                * @param       int             $timestamp      optional reference-time
+                * @return      int
+                */
+               static public function getNextOccurrence($expression, $timestamp = null) {
+
+                       try {
+
+                               // Convert timestamp to array
+
+                               $next           = self::getTimestamp($timestamp);
+
+                               // Calculate date/time
+
+                               $next_time      = self::calculateDateTime($expression, $next);
+
+                       } catch (Exception $e) {
+
+                               throw $e;
+
+                       }
+
+                       // return calculated time
+
+                       return $next_time;
+
+               }
+
+               /**
+                * getLastOccurrence() does pretty much the same as getNextOccurrence(). The only difference
+                * is, that it doesn't calculate the next but the last time a cronjob should have been executed.
+                *
+                * @access      public
+                * @param       string          $expression     cron-expression to use
+                * @param       int             $timestamp      optional reference-time
+                * @return      int
+                */
+
+               static public function getLastOccurrence($expression, $timestamp = null) {
+
+                       try {
+
+                               // Convert timestamp to array
+
+                               $last           = self::getTimestamp($timestamp);
+
+                               // Calculate date/time
+
+                               $last_time      = self::calculateDateTime($expression, $last, false);
+
+                       } catch (Exception $e) {
+
+                               throw $e;
+
+                       }
+
+                       // return calculated time
+
+                       return $last_time;
+
+               }
+
+               /**
+                * calculateDateTime() is the function where all the magic happens :-)
+                *
+                * It calculates the time and date at which the next/last call of a cronjob is/was due.
+                *
+                * @access      private
+                * @param       mixed           $value          cron-expression
+                * @param       mixed           $rtime          reference-time
+                * @param       bool            $next           true = nextOccurence, false = lastOccurence
+                * @return      int
+                */
+
+               static private function calculateDateTime($expression, $rtime, $next = true) {
+
+                       // Initialize vars
+                       $calc_date      = true;
+
+                       // Parse cron-expression (if neccessary)
+
+                       $cron           = self::getExpression($expression, !$next);
+
+                       // OK, lets see if the day/month/weekday of the reference-date exist in our
+                       // $cron-array.
+
+                       if (!in_array($rtime[IDX_DAY], $cron[IDX_DAY]) ||
+                           !in_array($rtime[IDX_MONTH], $cron[IDX_MONTH]) ||
+                           !in_array($rtime[IDX_WEEKDAY], $cron[IDX_WEEKDAY])) {
+
+                               // OK, things are easy. The day/month/weekday of the reference time
+                               // can't be found in the $cron-array. This means that no matter what
+                               // happens, we WILL end up at at a different date than that of our
+                               // reference-time. And in this case, the lastOccurrence will ALWAYS
+                               // happen at the latest possible time of the day and the nextOccurrence
+                               // at the earliest possible time.
+                               //
+                               // In both cases, the time can be found in the first elements of the
+                               // hour/minute cron-arrays.
+
+                               $rtime[IDX_HOUR]        = reset($cron[IDX_HOUR]);
+                               $rtime[IDX_MINUTE]      = reset($cron[IDX_MINUTE]);
+
+                       } else {
+
+                               // OK, things are getting a little bit more complicated...
+                               $nhour          = self::findValue($rtime[IDX_HOUR], $cron[IDX_HOUR], $next);
+
+                               // Meh. Such a cruel world. Something has gone awry. Lets see HOW awry it went.
+
+                               if ($nhour === false) { // Fix as per http://www.phpclasses.org/discuss/package/6699/thread/3/
+
+                                       // Ah, the hour-part went wrong. Thats easy. Wrong hour means that no
+                                       // matter what we do we'll end up at a different date. Thus we can use
+                                       // some simple operations to make things look pretty ;-)
+                                       //
+                                       // As alreasy mentioned before -> different date means earliest/latest
+                                       // time:
+
+                                       $rtime[IDX_HOUR]        = reset($cron[IDX_HOUR]);
+                                       $rtime[IDX_MINUTE]      = reset($cron[IDX_MINUTE]);
+
+                                       // Now all we have to do is add/subtract a day to get a new reference time
+                                       // to use later to find the right date. The following line probably looks
+                                       // a little odd but thats the easiest way of adding/substracting a day without
+                                       // screwing up the date. Just trust me on that one ;-)
+
+                                       $rtime                  = explode(',', strftime('%M,%H,%d,%m,%w,%Y', mktime($rtime[IDX_HOUR], $rtime[IDX_MINUTE], 0, $rtime[IDX_MONTH], $rtime[IDX_DAY], $rtime[IDX_YEAR]) + ((($next) ? 1 : -1) * 86400)));
+
+                               } else {
+
+                                       // OK, there is a higher/lower hour available. Check the minutes-part.
+
+                                       $nminute        = self::findValue($rtime[IDX_MINUTE], $cron[IDX_MINUTE], $next);
+
+                                       if ($nminute === false) {
+
+                                               // No matching minute-value found... lets see what happens if we substract/add an hour
+
+                                               $nhour          = self::findValue($rtime[IDX_HOUR] + (($next) ? 1 : -1), $cron[IDX_HOUR], $next);
+
+                                               if ($nhour === false) {
+
+                                                       // No more hours available... add/substract a day... you know what happens ;-)
+
+                                                       $nminute        = reset($cron[IDX_MINUTE]);
+                                                       $nhour          = reset($cron[IDX_HOUR]);
+
+                                                       $rtime          = explode(',', strftime('%M,%H,%d,%m,%w,%Y', mktime($nhour, $nminute, 0, $rtime[IDX_MONTH], $rtime[IDX_DAY], $rtime[IDX_YEAR]) + ((($next) ? 1 : -1) * 86400)));
+
+                                               } else {
+
+                                                       // OK, there was another hour. Set the right minutes-value
+
+                                                       $rtime[IDX_HOUR]        = $nhour;
+                                                       $rtime[IDX_MINUTE]      = (($next) ? reset($cron[IDX_MINUTE]) : end($cron[IDX_MINUTE]));
+
+                                                       $calc_date      = false;
+
+                                               }
+
+                                       } else {
+
+                                               // OK, there is a matching minute... reset minutes if hour has changed
+
+                                               if ($nhour <> $rtime[IDX_HOUR]) {
+                                                       $nminute                = reset($cron[IDX_MINUTE]);
+                                               }
+
+                                               // Set time
+                                               $rtime[IDX_HOUR]        = $nhour;
+                                               $rtime[IDX_MINUTE]      = $nminute;
+                                               $calc_date      = false;
+
+                                       }
+
+                               }
+
+                       }
+
+                       // If we have to calculate the date... we'll do so
+
+                       if ($calc_date) {
+
+                               if (in_array($rtime[IDX_DAY], $cron[IDX_DAY]) &&
+                                   in_array($rtime[IDX_MONTH], $cron[IDX_MONTH]) &&
+                                   in_array($rtime[IDX_WEEKDAY], $cron[IDX_WEEKDAY])) {
+
+                                       return mktime($rtime[1], $rtime[0], 0, $rtime[3], $rtime[2], $rtime[5]);
+
+                               } else {
+
+                                       // OK, some searching necessary...
+
+                                       $cdate  = mktime(0, 0, 0, $rtime[IDX_MONTH], $rtime[IDX_DAY], $rtime[IDX_YEAR]);
+
+                                       // OK, these three nested loops are responsible for finding the date...
+                                       //
+                                       // The class has 2 limitations/bugs right now:
+                                       //
+                                       //      -> it doesn't work for dates in 2036 or later!
+                                       //      -> it will most likely fail if you search for a Feburary, 29th with a given weekday
+                                       //         (this does happen because the class only searches in the next/last 10 years! And
+                                       //         while it usually takes less than 10 years for a "normal" date to iterate through
+                                       //         all weekdays, it can take 20+ years for Feb, 29th to iterate through all weekdays!
+
+                                       for ($nyear = $rtime[IDX_YEAR];(($next) ? ($nyear <= $rtime[IDX_YEAR] + 10) : ($nyear >= $rtime[IDX_YEAR] -10));$nyear = $nyear + (($next) ? 1 : -1)) {
+
+                                               foreach ($cron[IDX_MONTH] as $nmonth) {
+
+                                                       foreach ($cron[IDX_DAY] as $nday) {
+
+                                                               if (checkdate($nmonth,$nday,$nyear)) {
+
+                                                                       $ndate  = mktime(0,0,1,$nmonth,$nday,$nyear);
+
+                                                                       if (($next) ? ($ndate >= $cdate) : ($ndate <= $cdate)) {
+
+                                                                               $dow    = date('w',$ndate);
+
+                                                                               // The date is "OK" - lets see if the weekday matches, too...
+
+                                                                               if (in_array($dow,$cron[IDX_WEEKDAY])) {
+
+                                                                                       // WIN! :-) We found a valid date...
+
+                                                                                       $rtime                  = explode(',', strftime('%M,%H,%d,%m,%w,%Y', mktime($rtime[IDX_HOUR], $rtime[IDX_MINUTE], 0, $nmonth, $nday, $nyear)));
+
+                                                                                       return mktime($rtime[1], $rtime[0], 0, $rtime[3], $rtime[2], $rtime[5]);
+
+                                                                               }
+
+                                                                       }
+
+                                                               }
+
+                                                       }
+
+                                               }
+
+                                       }
+
+                               }
+
+                               throw new Exception('Failed to find date, No matching date found in a 10 years range!', 10004);
+
+                       }
+
+                       return mktime($rtime[1], $rtime[0], 0, $rtime[3], $rtime[2], $rtime[5]);
+
+               }
+
+               /**
+                * getTimestamp() converts an unix-timestamp to an array. The returned array contains the following values:
+                *
+                *      [0]     -> minute
+                *      [1]     -> hour
+                *      [2]     -> day
+                *      [3]     -> month
+                *      [4]     -> weekday
+                *      [5]     -> year
+                *
+                * The array is used by various functions.
+                *
+                * @access      private
+                * @param       int             $timestamp      If none is given, the current time is used
+                * @return      mixed
+                */
+
+               static private function getTimestamp($timestamp = null) {
+
+                       if (is_null($timestamp)) {
+                               $arr    = explode(',', strftime('%M,%H,%d,%m,%w,%Y', time()));
+                       } else {
+                               $arr    = explode(',', strftime('%M,%H,%d,%m,%w,%Y', $timestamp));
+                       }
+
+                       // Remove leading zeros (or we'll get in trouble ;-)
+
+                       foreach ($arr as $key=>$value) {
+                               $arr[$key]      = (int)ltrim($value,'0');
+                       }
+
+                       return $arr;
+
+               }
+
+               /**
+                * findValue() checks if the given value exists in an array. If it does not exist, the next
+                * higher/lower value is returned (depending on $next). If no higher/lower value exists,
+                * false is returned.
+                *
+                * @access      public
+                * @param       int             $value
+                * @param       mixed           $data
+                * @param       bool            $next
+                * @return      mixed
+                */
+
+               static private function findValue($value, $data, $next = true) {
+
+                       if (in_array($value, $data)) {
+
+                               return (int)$value;
+
+                       } else {
+
+                               if (($next) ? ($value <= end($data)) : ($value >= end($data))) {
+
+                                       foreach ($data as $curval) {
+
+                                               if (($next) ? ($value <= (int)$curval) : ($curval <= $value)) {
+
+                                                       return (int)$curval;
+
+                                               }
+
+                                       }
+
+                               }
+
+                       }
+
+                       return false;
+
+               }
+
+               /**
+                * getExpression() returns a parsed cron-expression. Parsed cron-expressions are cached to reduce
+                * unneccessary calls of the parser.
+                *
+                * @access      public
+                * @param       string          $value
+                * @param       bool            $reverse
+                * @return      mixed
+                */
+
+                static private function getExpression($expression, $reverse=false) {
+
+                       // First of all we cleanup the expression and remove all duplicate tabs/spaces/etc.
+                       // For example "*              * *    * *" would be converted to "* * * * *", etc.
+
+                       $expression     = preg_replace('/(\s+)/', ' ', strtolower(trim($expression)));
+
+                       // Lets see if we've already parsed that expression
+
+                       if (!isset(self::$pcron[$expression])) {
+
+                               // Nope - parse it!
+
+                               try {
+
+                                       self::$pcron[$expression]               = tdCronEntry::parse($expression);
+                                       self::$pcron['reverse'][$expression]    = self::arrayReverse(self::$pcron[$expression]);
+
+                               } catch (Exception $e) {
+
+                                       throw $e;
+
+                               }
+
+                       }
+
+                       return ($reverse ? self::$pcron['reverse'][$expression] : self::$pcron[$expression]);
+
+               }
+
+               /**
+                * arrayReverse() reverses all sub-arrays of our cron array. The reversed values are used for calculations
+                * that are run when getLastOccurence() is called.
+                *
+                * @access      public
+                * @param       mixed           $cron
+                * @return      mixed
+                */
+
+               static private function arrayReverse($cron) {
+
+                       foreach ($cron as $key=>$value) {
+
+                               $cron[$key]     = array_reverse($value);
+
+                       }
+
+                       return $cron;
+
+               }
+
+       }
\ No newline at end of file
diff --git a/WEB-INF/lib/ttChartHelper.class.php b/WEB-INF/lib/ttChartHelper.class.php
new file mode 100644 (file)
index 0000000..5d1e8e5
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('Period');
+
+// Definitions for chart types.
+define('CHART_PROJECTS', 1);
+define('CHART_TASKS', 2);
+define('CHART_CLIENTS', 3);
+
+// Class ttChartHelper is a helper class for charts.
+class ttChartHelper {
+
+  // getTotals - returns total times by project or activity for a given user in a specified period.
+  static function getTotals($user_id, $ch_type, $cl_date, $cl_period = null) {
+
+    $period = null;
+    if (isset($cl_period) && isset($cl_date)) {
+      switch ($cl_period) {
+        case INTERVAL_THIS_DAY:
+          $period = new Period(INTERVAL_THIS_DAY, new DateAndTime(DB_DATEFORMAT, $cl_date));
+          break;
+        case INTERVAL_THIS_WEEK:
+          $period = new Period(INTERVAL_THIS_WEEK, new DateAndTime(DB_DATEFORMAT, $cl_date));
+          break;
+
+        case INTERVAL_THIS_MONTH:
+          $period = new Period(INTERVAL_THIS_MONTH, new DateAndTime(DB_DATEFORMAT, $cl_date));
+          break;
+
+        case INTERVAL_THIS_YEAR:
+          $period = new Period(INTERVAL_THIS_YEAR, new DateAndTime(DB_DATEFORMAT, $cl_date));
+          break;
+      }
+    }
+       
+    $result = array();
+    $mdb2 = getConnection();
+
+    $q_period = '';
+    if ($period != null) {
+      $q_period = " and date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."'";
+    }
+    if (CHART_PROJECTS == $ch_type) {
+      // Data for projects.
+      $sql = "select p.name as name, sum(time_to_sec(l.duration)) as time from tt_log l
+        inner join tt_projects p on (p.id = l.project_id)
+        where l.status = 1 and l.duration > 0 and l.user_id = $user_id $q_period group by l.project_id";
+    } else if (CHART_TASKS == $ch_type) {
+      // Data for tasks.
+      $sql = "select t.name as name, sum(time_to_sec(l.duration)) as time from tt_log l
+        inner join tt_tasks t on (t.id = l.task_id)
+        where l.status = 1 and l.duration > 0 and l.user_id = $user_id $q_period group by l.task_id";
+    } else if (CHART_CLIENTS == $ch_type) {
+      // Data for clients.
+      $sql = "select coalesce(c.name, 'NULL') as name, sum(time_to_sec(l.duration)) as time from tt_log l
+        left join tt_clients c on (c.id = l.client_id)
+        where l.status = 1 and l.duration > 0 and l.user_id = $user_id $q_period group by l.client_id";        
+    }
+    
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = array('name'=>$val['name'],'time'=>$val['time']); // name  - project name, time - total for project in seconds.
+      }
+    }
+    
+    // Get total time. We'll need it calculate percentages (for labels to the right of diagram).
+    $total = 0;
+    foreach ($result as $one_val) {
+      $total += $one_val['time'];
+       }
+       // Add a string representation of time + percentage to names. Example: "Time Tracker (1:15 - 6%)".
+       foreach ($result as &$one_val) {
+         $percent = round(100*$one_val['time']/$total).'%';
+      $one_val['name'] .= ' ('.sec_to_time_fmt_hm($one_val['time']).' - '.$percent.')';
+       }
+           
+    // Note: the remaining code here is needed to display labels on the side of a diagram.
+    // We print lables ourselves (not using libchart.php) because quality of libchart labels is not good. 
+       
+       // Note: Optimize this sorting and reversing.
+    $result = mu_sort($result, 'time');
+    $result = array_reverse($result); // This is to assign correct colors to labels.
+        
+    // Add color to array items. This is used in labels on the side of a chart.
+    $colors = array(
+      array(2, 78, 0),
+      array(148, 170, 36),
+      array(233, 191, 49),
+      array(240, 127, 41),
+      array(243, 63, 34),
+      array(190, 71, 47),
+      array(135, 81, 60),
+      array(128, 78, 162),
+      array(121, 75, 255),
+      array(142, 165, 250),
+      array(162, 254, 239),
+      array(137, 240, 166),
+      array(104, 221, 71),
+      array(98, 174, 35),
+      array(93, 129, 1)
+    );
+    for ($i = 0; $i < count($result); $i++) {
+      $color = $colors[$i%count($colors)];
+      $result[$i]['color_html'] = sprintf('#%02x%02x%02x', $color[0], $color[1], $color[2]);
+    }
+                       
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttClientHelper.class.php b/WEB-INF/lib/ttClientHelper.class.php
new file mode 100644 (file)
index 0000000..43cffe0
--- /dev/null
@@ -0,0 +1,329 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Class ttClientHelper is used to help with client related tasks.
+class ttClientHelper {
+       
+  // The getClient looks up a client by id.
+  static function getClient($client_id, $all_fields = false) {
+
+    $mdb2 = getConnection();
+       global $user;
+       
+    $sql = 'select ';
+    if ($all_fields)
+      $sql .= '* ';
+    else
+      $sql .= 'name ';
+    
+    $sql .= "from tt_clients where team_id = $user->team_id
+      and id = $client_id and (status = 1 or status = 0)";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val;
+    }
+    return false;
+  }
+  
+  // getClients - returns an array of active and inactive clients in a team.
+  static function getClients()
+  {
+       global $user;
+               
+       $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select id, name from tt_clients
+      where team_id = $user->team_id and (status = 0 or status = 1) order by name";    
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+       
+  // The getClientByName looks up a client by name.
+  static function getClientByName($client_name) {
+       
+    $mdb2 = getConnection();
+    global $user;
+
+    $sql = "select id from tt_clients where team_id = $user->team_id and name = ".
+      $mdb2->quote($client_name)." and (status = 1 or status = 0)";
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['id']) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  // The getDeletedClient looks up a deleted client by id.
+  static function getDeletedClient($client_id) {
+
+    $mdb2 = getConnection();
+       global $user;
+       
+    $sql = "select name, address from tt_clients where team_id = $user->team_id
+      and id = $client_id and status is NULL";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val;
+    }
+    return false;
+  }
+  
+  // The delete function marks client as deleded.
+  static function delete($id, $delete_client_entries) {
+       
+       $mdb2 = getConnection();
+       global $user;
+       
+    // Handle custom field log records.
+    if ($delete_client_entries) {
+      $sql = "update tt_custom_field_log set status = NULL where log_id in (select id from tt_log where client_id = $id and status = 1)";
+      $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+    }
+    
+    // Handle time records.
+    if ($delete_client_entries) {
+      $sql = "update tt_log set status = NULL where client_id = $id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+           return false;
+    }
+    
+    // Handle expense items.
+    if ($delete_client_entries) {        
+      $sql = "update tt_expense_items set status = NULL where client_id = $id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+    
+    // Handle invoices.
+    if ($delete_client_entries) {        
+      $sql = "update tt_invoices set status = NULL where client_id = $id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+       // Delete project binds to this client.
+    $sql = "delete from tt_client_project_binds where client_id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+       
+       $sql = "update tt_clients set status = NULL where id = $id and team_id = ".$user->team_id;
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // The insert function inserts a new client record into the clients table.
+  static function insert($fields)
+  {
+       global $user;
+    $mdb2 = getConnection();
+    
+    $team_id = (int) $fields['team_id'];
+    $name = $fields['name'];
+    $address = $fields['address'];
+    $tax = $fields['tax'];
+    $projects = $fields['projects'];
+    if ($projects)
+      $comma_separated = implode(',', $projects); // This is a comma-separated list of associated projects ids.
+    $status = $fields['status'];
+
+    $tax = str_replace(',', '.', $tax);
+    if ($tax == '') $tax = 0;
+
+    $sql = "insert into tt_clients (team_id, name, address, tax, projects, status) 
+      values ($team_id, ".$mdb2->quote($name).", ".$mdb2->quote($address).", $tax, ".$mdb2->quote($comma_separated).", ".$mdb2->quote($status).")";
+      
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+      
+    if (count($projects) > 0)
+      foreach ($projects as $p_id) {
+        $sql = "insert into tt_client_project_binds (client_id, project_id) values($last_id, $p_id)";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+
+    return $last_id;
+  }
+  
+  // The update function updates a client record in tt_clients table.  
+  static function update($fields)
+  {
+    $mdb2 = getConnection();
+    global $user;
+
+    $id = $fields['id'];
+    $name = $fields['name'];
+    $address = $fields['address'];
+    $tax = $fields['tax'];
+    $status = $fields['status'];
+    $projects = $fields['projects'];
+    
+    $tax = str_replace(',', '.', $tax);
+       if ($tax == '') $tax = 0;
+
+    // Insert client to project binds into tt_client_project_binds table.
+    $sql = "delete from tt_client_project_binds where client_id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      die($affected->getMessage());
+    if (count($projects) > 0)
+      foreach ($projects as $p_id) {
+        $sql = "insert into tt_client_project_binds (client_id, project_id) values($id, $p_id)";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+
+    // Update client properties in tt_clients table.
+    $comma_separated = implode(",", $projects); // This is a comma-separated list of associated project ids.
+    $sql = "update tt_clients set name = ".$mdb2->quote($name).", address = ".$mdb2->quote($address).
+      ", tax = $tax, projects = ".$mdb2->quote($comma_separated).", status = $status where team_id = ".$user->team_id." and id = ".$id;
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // The setMappedClient function is used during team import to change client_id value for tt_users to a mapped value.
+  static function setMappedClient($team_id, $imported_id, $mapped_id)
+  {
+    $mdb2 = getConnection();
+    $sql = "update tt_users set client_id = $mapped_id where client_id = $imported_id and team_id = $team_id ";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    return true;
+  }
+  
+  // The fillBean function fills the ActionForm object with client data.
+  static function fillBean($client_id, &$bean) {
+       $client = ttClientHelper::getClient($client_id, true);
+    $bean->setAttribute('name', $client['name']);
+    $bean->setAttribute('address', $client['address']);
+    $bean->setAttribute('tax', $client['tax']);
+  }
+  
+  // getAssignedProjects - returns an array of projects associatied with a client.
+  static function getAssignedProjects($client_id)
+  {
+       global $user;
+       
+    $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned projects.
+    $sql = "select p.id, p.name from tt_projects p
+      inner join tt_client_project_binds cpb on (cpb.client_id = $client_id and cpb.project_id = p.id)
+      where p.team_id = $user->team_id and p.status = 1 order by p.name";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getClientsForUser - returns an array of clients that are relevant to a user via assigned projects. 
+  static function getClientsForUser($withProjects = false)
+  {
+       global $user;
+       $user_id = $user->getActiveUser();
+       
+       $result = array();
+       $mdb2 = getConnection();
+       
+    $sql = "select distinct c.id, c.name, c.projects from tt_user_project_binds upb
+      inner join tt_client_project_binds cpb on (cpb.project_id = upb.project_id)
+      inner join tt_clients c on (c.id = cpb.client_id and c.status = 1)
+      where upb.user_id = $user_id and upb.status = 1 order by c.name";
+    
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        // if ($withProjects) {
+         // $projects = ttClientHelper::getAssignedProjectsForUser($val['id']);
+             //$project_ids = array();
+             //foreach ($projects as $project_item)
+               //$project_ids[] = $project_item[id];
+             //$val['projects'] = implode(',', $project_ids);
+       //}
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getAssignedProjectsForUser - returns an array of projects assigned to a user and associatied with a client.
+  static function getAssignedProjectsForUser($client_id)
+  {
+       global $user;
+       $user_id = $user->getActiveUser();
+       
+       $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned projects.
+    $sql = "select p.id, p.name from tt_projects p
+      inner join tt_client_project_binds cpb on (cpb.client_id = $client_id and cpb.project_id = p.id)
+      inner join tt_user_project_binds upb on (upb.user_id = $user_id and upb.project_id = p.id and upb.status = 1)
+      where p.team_id = $user->team_id and p.status = 1 order by p.name";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttCustomFieldHelper.class.php b/WEB-INF/lib/ttCustomFieldHelper.class.php
new file mode 100644 (file)
index 0000000..1743bea
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Class ttCustomFieldHelper is used to help with custom field related tasks.
+class ttCustomFieldHelper {
+       
+  // The insertField function inserts a new custom field in database.
+  static function insertField($fields)
+  {
+    $mdb2 = getConnection();
+
+    $team_id = (int) $fields['team_id'];
+    $type = (int) $fields['type'];
+    $label = $fields['label'];
+    $required = (int) $fields['required'];
+    $status = $fields['status'];
+    
+    $sql = "insert into tt_custom_fields (team_id, type, label, required, status) 
+      values ($team_id, $type, ".$mdb2->quote($label).", $required, ".$mdb2->quote($status).")";
+      
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+      
+    return $last_id;
+  }
+  
+  // The insertOption function inserts a new custom field option in database.
+  static function insertOption($fields)
+  {
+    $mdb2 = getConnection();
+
+    $field_id = (int) $fields['field_id'];
+    $value = $fields['value'];
+    
+    $sql = "insert into tt_custom_field_options (field_id, value) 
+      values ($field_id, ".$mdb2->quote($value).")";
+      
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+      
+    return $last_id;
+  }
+  
+  // The insertLogEntry function inserts a new custom field log entry in database.
+  static function insertLogEntry($fields)
+  {
+    $mdb2 = getConnection();
+
+    $log_id = (int) $fields['log_id'];
+    $field_id = (int) $fields['field_id'];
+    $option_id = $fields['option_id'];
+    $value = $fields['value'];
+    $status = $fields['status'];
+    
+    $sql = "insert into tt_custom_field_log (log_id, field_id, option_id, value, status) 
+      values ($log_id, $field_id, ".$mdb2->quote($option_id).", ".$mdb2->quote($value).", ".$mdb2->quote($status).")";
+      
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttExpenseHelper.class.php b/WEB-INF/lib/ttExpenseHelper.class.php
new file mode 100644 (file)
index 0000000..b73208a
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// The ttExpenseHelper is a class to help with expense items.
+class ttExpenseHelper {
+  // insert - inserts an entry into tt_expense_items table.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+
+    $date = $fields['date'];
+    $user_id = (int) $fields['user_id'];
+    $client_id = $fields['client_id'];
+    $project_id = $fields['project_id'];
+    $name = $fields['name'];
+    $cost = str_replace(',', '.', $fields['cost']);
+    $invoice_id = $fields['invoice_id'];
+    $status = $fields['status'];
+       
+    $sql = "insert into tt_expense_items (date, user_id, client_id, project_id, name, cost, invoice_id, status) ".
+      "values (".$mdb2->quote($date).", $user_id, ".$mdb2->quote($client_id).", ".$mdb2->quote($project_id).
+      ", ".$mdb2->quote($name).", ".$mdb2->quote($cost).", ".$mdb2->quote($invoice_id).", ".$mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+    
+    $id = $mdb2->lastInsertID('tt_expense_items', 'id');
+    return $id;
+  }
+  
+  // update - updates a record in tt_expense_items table.
+  static function update($fields)
+  {
+    $mdb2 = getConnection();
+
+    $id = (int) $fields['id'];
+    $date = $fields['date'];
+    $user_id = (int) $fields['user_id'];
+    $client_id = $fields['client_id'];
+    $project_id = $fields['project_id'];
+    $name = $fields['name'];
+    $cost = str_replace(',', '.', $fields['cost']);
+    $invoice_id = $fields['invoice_id'];
+    
+    $sql = "UPDATE tt_expense_items set date = ".$mdb2->quote($date).", user_id = $user_id, client_id = ".$mdb2->quote($client_id).
+      ", project_id = ".$mdb2->quote($project_id).", name = ".$mdb2->quote($name).
+      ", cost = ".$mdb2->quote($cost).", invoice_id = ".$mdb2->quote($invoice_id).
+      " WHERE id = $id";
+    
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+   
+    return true;
+  }
+  
+  // markDeleted - marks an item as deleted in tt_expense_items table.
+  static function markDeleted($id, $user_id) {
+    $mdb2 = getConnection();
+
+    $sql = "update tt_expense_items set status = NULL where id = $id and user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    return true;
+  }
+  
+  // getTotalForDay - gets total expenses for a user for a specific date.
+  static function getTotalForDay($user_id, $date) {
+       global $user;
+       
+    $mdb2 = getConnection();
+
+    $sql = "select sum(cost) as sm from tt_expense_items where user_id = $user_id and date = ".$mdb2->quote($date)." and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      $val['sm'] = str_replace('.', $user->decimal_mark, $val['sm']);
+      return $val['sm'];
+    }
+    return false;
+  }
+  
+  // getItem - retrieves an entry from tt_expense_items table.
+  static function getItem($id, $user_id) {
+       global $user;
+       
+    $mdb2 = getConnection();
+    
+    $client_field = null;
+    if (in_array('cl', explode(',', $user->plugins)))
+      $client_field = ", c.name as client_name";
+      
+    $left_joins = "";
+    $left_joins = " left join tt_projects p on (ei.project_id = p.id)";
+    if (in_array('cl', explode(',', $user->plugins)))
+      $left_joins .= " left join tt_clients c on (ei.client_id = c.id)";
+      
+    $sql = "select ei.id, ei.date, ei.client_id, ei.project_id, ei.name, ei.cost, ei.invoice_id $client_field, p.name as project_name
+      from tt_expense_items ei
+      $left_joins
+      where ei.id = $id and ei.user_id = $user_id and ei.status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if (!$res->numRows()) {
+        return false;
+      }
+      if ($val = $res->fetchRow()) {
+       $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  /*
+  // getAllItems - returns all expense items for a certain user.
+  static function getAllItems($user_id) {
+    $result = array();
+
+    $mdb2 = getConnection();
+
+    $sql = "select * from tt_expense_items where user_id = $user_id order by id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    } else return false;
+
+    return $result;
+  }*/
+
+  // getItems - returns expense items for a user for a given date.
+  static function getItems($user_id, $date) {
+       global $user;
+               
+    $result = array();
+    $mdb2 = getConnection();
+
+    $client_field = null;
+    if (in_array('cl', explode(',', $user->plugins)))
+      $client_field = ", c.name as client";
+    
+    $left_joins = "";
+    $left_joins = " left join tt_projects p on (ei.project_id = p.id)";
+    if (in_array('cl', explode(',', $user->plugins)))
+      $left_joins .= " left join tt_clients c on (ei.client_id = c.id)";
+
+    $sql = "select ei.id as id $client_field, p.name as project, ei.name as item, ei.cost as cost,
+      ei.invoice_id from tt_expense_items ei
+      $left_joins
+      where ei.date = ".$mdb2->quote($date)." and ei.user_id = $user_id and ei.status = 1
+      order by ei.id";
+      
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+        $result[] = $val;
+      }
+    } else return false;
+
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttExportHelper.class.php b/WEB-INF/lib/ttExportHelper.class.php
new file mode 100644 (file)
index 0000000..4606c39
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+import('ttTimeHelper');
+
+// ttExportHelper - this class is used to export team data to a file.
+class ttExportHelper {
+  var $fileName    = null;    // Name of the file with data.
+  
+  // The following arrays are maps between entity ids in the file versus the database.
+  // We write to the file sequentially (1,2,3...) while in the database the entities have different ids.
+  var $userMap     = array(); // User ids.
+  var $projectMap  = array(); // Project ids.
+  var $taskMap     = array(); // Task ids.
+  var $clientMap   = array(); // Client ids.
+  var $invoiceMap  = array(); // Invoice ids.
+  var $customFieldMap       = array(); // Custom field ids.
+  var $customFieldOptionMap = array(); // Custop field option ids.
+  var $logMap      = array(); // Time log ids.
+    
+  // createDataFile creates a file with all data for a given team.
+  function createDataFile($compress = false) {
+       global $user;
+
+    // Create a temporary file.
+       $dirName = dirname(TEMPLATE_DIR . '_c/.');
+    $tmp_file = tempnam($dirName, 'tt');
+
+    // Open the file for writing.
+    $file = fopen($tmp_file, 'wb');
+    if (!$file) return false;
+    
+    // Write XML to the file.
+    fwrite($file, "<?xml version=\"1.0\"?>\n");
+    fwrite($file, "<pack>\n");
+    
+    // Write team info.
+    fwrite($file, "<team currency=\"".$user->currency."\" lock_interval=\"".$user->lock_interval."\" lang=\"".$user->lang."\" decimal_mark=\"".$user->decimal_mark."\" date_format=\"".$user->date_format."\" time_format=\"".$user->time_format."\" week_start=\"".$user->week_start.
+      "\" plugins=\"".$user->plugins."\" tracking_mode=\"".$user->tracking_mode."\" record_type=\"".$user->record_type."\">\n");
+    fwrite($file, "  <name><![CDATA[".$user->team."]]></name>\n");
+    fwrite($file, "  <address><![CDATA[".$user->address."]]></address>\n");
+    fwrite($file, "</team>\n");
+    
+    // Prepare user map.
+    $users = ttTeamHelper::getAllUsers($user->team_id, true);
+    foreach ($users as $key=>$user_item)
+      $this->userMap[$user_item['id']] = $key + 1;
+      
+    // Prepare project map.
+    $projects = ttTeamHelper::getAllProjects($user->team_id, true);
+    foreach ($projects as $key=>$project_item)
+      $this->projectMap[$project_item['id']] = $key + 1;
+
+    // Prepare task map.
+    $tasks = ttTeamHelper::getAllTasks($user->team_id, true);
+    foreach ($tasks as $key=>$task_item)
+      $this->taskMap[$task_item['id']] = $key + 1;
+      
+    // Prepare client map.
+    $clients = ttTeamHelper::getAllClients($user->team_id, true);
+    foreach ($clients as $key=>$client_item)
+      $this->clientMap[$client_item['id']] = $key + 1;      
+
+    // Prepare invoice map.
+    $invoices = ttTeamHelper::getAllInvoices();
+    foreach ($invoices as $key=>$invoice_item)
+      $this->invoiceMap[$invoice_item['id']] = $key + 1;   
+
+    // Prepare custom fields map.
+    $custom_fields = ttTeamHelper::getAllCustomFields($user->team_id);
+    foreach ($custom_fields as $key=>$custom_field)
+      $this->customFieldMap[$custom_field['id']] = $key + 1;  
+
+    // Prepare custom field options map.
+    $custom_field_options = ttTeamHelper::getAllCustomFieldOptions($user->team_id);
+    foreach ($custom_field_options as $key=>$option)
+      $this->customFieldOptionMap[$option['id']] = $key + 1;  
+      
+    // Write users.
+    fwrite($file, "<users>\n");
+    foreach ($users as $user_item) {
+      fwrite($file, "  <user id=\"".$this->userMap[$user_item['id']]."\" login=\"".htmlentities($user_item['login'])."\" password=\"".$user_item['password']."\" role=\"".$user_item['role']."\" client_id=\"".$this->clientMap[$user_item['client_id']]."\" rate=\"".$user_item['rate']."\" email=\"".$user_item['email']."\" status=\"".$user_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$user_item['name']."]]></name>\n");
+      fwrite($file, "  </user>\n");
+    }
+    fwrite($file, "</users>\n");
+
+    // Write tasks.
+    fwrite($file, "<tasks>\n");
+    foreach ($tasks as $task_item) {
+      fwrite($file, "  <task id=\"".$this->taskMap[$task_item['id']]."\" status=\"".$task_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$task_item['name']."]]></name>\n");
+      fwrite($file, "    <description><![CDATA[".$task_item['description']."]]></description>\n");
+      fwrite($file, "  </task>\n");
+    }
+    fwrite($file, "</tasks>\n");
+    unset($tasks);
+
+    // Write projects.
+    fwrite($file, "<projects>\n");
+    foreach ($projects as $project_item) {
+      if($project_item['tasks']){
+        $tasks = explode(',', $project_item['tasks']);
+        $tasks_mapped = array();
+        foreach ($tasks as $item)
+          $tasks_mapped[] = $this->taskMap[$item];
+        $tasks_str = implode(',', $tasks_mapped);
+      }
+      fwrite($file, "  <project id=\"".$this->projectMap[$project_item['id']]."\" tasks=\"".$tasks_str."\" status=\"".$project_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$project_item['name']."]]></name>\n");
+      fwrite($file, "    <description><![CDATA[".$project_item['description']."]]></description>\n");
+      fwrite($file, "  </project>\n");
+    }
+    fwrite($file, "</projects>\n");
+    unset($projects);
+
+    // Write user to project binds.
+    fwrite($file, "<user_project_binds>\n");
+    $user_binds = ttTeamHelper::getUserToProjectBinds($user->team_id);
+    foreach ($user_binds as $bind) {
+      $user_id = $this->userMap[$bind['user_id']];
+      $project_id = $this->projectMap[$bind['project_id']];
+      fwrite($file, "  <user_project_bind user_id=\"{$user_id}\" project_id=\"{$project_id}\" rate=\"".$bind['rate']."\" status=\"".$bind['status']."\"/>\n");
+    }
+    fwrite($file, "</user_project_binds>\n");
+    unset($user_binds);
+    
+    // Write clients.
+    fwrite($file, "<clients>\n");
+    foreach ($clients as $client_item) {
+      if($client_item['projects']){
+        $projects = explode(',', $client_item['projects']);
+        $projects_mapped = array();
+        foreach ($projects as $item)
+          $projects_mapped[] = $this->projectMap[$item];
+        $projects_str = implode(',', $projects_mapped);
+      }
+      fwrite($file, "  <client id=\"".$this->clientMap[$client_item['id']]."\" tax=\"".$client_item['tax']."\" projects=\"".$projects_str."\" status=\"".$client_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$client_item['name']."]]></name>\n");
+      fwrite($file, "    <address><![CDATA[".$client_item['address']."]]></address>\n");
+      fwrite($file, "  </client>\n");
+    }
+    fwrite($file, "</clients>\n");
+    unset($clients);
+
+    // Write invoices.
+    fwrite($file, "<invoices>\n");
+    foreach ($invoices as $invoice_item) {
+      fwrite($file, "  <invoice id=\"".$this->invoiceMap[$invoice_item['id']]."\" date=\"".$invoice_item['date']."\" client_id=\"".$this->clientMap[$invoice_item['client_id']]."\" status=\"".$invoice_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$invoice_item['name']."]]></name>\n");
+      fwrite($file, "  </invoice>\n");
+    }
+    fwrite($file, "</invoices>\n");
+    unset($invoices);
+
+    // Write custom fields.
+    fwrite($file, "<custom_fields>\n");
+    foreach ($custom_fields as $custom_field) {
+      fwrite($file, "  <custom_field id=\"".$this->customFieldMap[$custom_field['id']]."\" type=\"".$custom_field['type']."\" required=\"".$custom_field['required']."\" status=\"".$custom_field['status']."\">\n");
+      fwrite($file, "    <label><![CDATA[".$custom_field['label']."]]></label>\n");
+      fwrite($file, "  </custom_field>\n");
+    }
+    fwrite($file, "</custom_fields>\n");
+    unset($custom_fields);
+
+    // Write custom field options.
+    fwrite($file, "<custom_field_options>\n");
+    foreach ($custom_field_options as $option) {
+      fwrite($file, "  <custom_field_option id=\"".$this->customFieldOptionMap[$option['id']]."\" field_id=\"".$this->customFieldMap[$option['field_id']]."\">\n");
+      fwrite($file, "    <value><![CDATA[".$option['value']."]]></value>\n");
+      fwrite($file, "  </custom_field_option>\n");
+    }
+    fwrite($file, "</custom_field_options>\n");
+    unset($custom_field_options);
+    
+    // Write time log entries.
+    fwrite($file, "<log>\n");
+    $key = 0;
+    foreach ($users as $user_item) {
+      $records = ttTimeHelper::getAllRecords($user_item['id']);
+      foreach ($records as $record) {
+       $key++;
+       $this->logMap[$record['id']] = $key;   
+        fwrite($file, "  <log_item id=\"$key\" timestamp=\"".$record['timestamp']."\" user_id=\"".$this->userMap[$record['user_id']]."\" date=\"".$record['date']."\" start=\"".$record['start']."\" finish=\"".$record['finish']."\" duration=\"".($record['start']?"":$record['duration'])."\" client_id=\"".$this->clientMap[$record['client_id']]."\" project_id=\"".$this->projectMap[$record['project_id']]."\" task_id=\"".$this->taskMap[$record['task_id']]."\" invoice_id=\"".$this->invoiceMap[$record['invoice_id']]."\" billable=\"".$record['billable']."\" status=\"".$record['status']."\">\n");
+        fwrite($file, "    <comment><![CDATA[".$record['comment']."]]></comment>\n");
+        fwrite($file, "  </log_item>\n");
+      }
+    }
+    fwrite($file, "</log>\n");
+    unset($records);
+    
+    // Write custom field log.
+    $custom_field_log = ttTeamHelper::getCustomFieldLog($user->team_id);
+    fwrite($file, "<custom_field_log>\n");
+    foreach ($custom_field_log as $entry) {
+      fwrite($file, "  <custom_field_log_entry log_id=\"".$this->logMap[$entry['log_id']]."\" field_id=\"".$this->customFieldMap[$entry['field_id']]."\" option_id=\"".$this->customFieldOptionMap[$entry['option_id']]."\" status=\"".$entry['status']."\">\n");
+      fwrite($file, "    <value><![CDATA[".$entry['value']."]]></value>\n");
+      fwrite($file, "  </custom_field_log_entry>\n");
+    }
+    fwrite($file, "</custom_field_log>\n");
+    unset($custom_field_log);
+    
+    // Write expense items.
+    $expense_items = ttTeamHelper::getExpenseItems($user->team_id);
+    fwrite($file, "<expense_items>\n");
+    foreach ($expense_items as $expense_item) {
+      fwrite($file, "  <expense_item date=\"".$expense_item['date']."\" user_id=\"".$this->userMap[$expense_item['user_id']]."\" client_id=\"".$this->clientMap[$expense_item['client_id']]."\" project_id=\"".$this->projectMap[$expense_item['project_id']]."\" cost=\"".$expense_item['cost']."\" invoice_id=\"".$this->invoiceMap[$expense_item['invoice_id']]."\" status=\"".$expense_item['status']."\">\n");
+      fwrite($file, "    <name><![CDATA[".$expense_item['name']."]]></name>\n");
+      fwrite($file, "  </expense_item>\n");
+    }
+    fwrite($file, "</expense_items>\n");
+    unset($expense_items);
+        
+    // Write fav reports.
+    fwrite($file, "<fav_reports>\n");
+    $fav_reports = ttTeamHelper::getFavReports($user->team_id);
+    foreach ($fav_reports as $fav_report) {
+      $user_list = '';
+      if (strlen($fav_report['users']) > 0) {
+        $arr = explode(',', $fav_report['users']);
+        foreach ($arr as $k=>$v) {
+          if (array_key_exists($arr[$k], $this->userMap))
+            $user_list .= (strlen($user_list) == 0? '' : ',').$this->userMap[$v];
+        }
+      }
+      fwrite($file, "\t<fav_report user_id=\"".$this->userMap[$fav_report['user_id']]."\"".
+        " client_id=\"".$this->clientMap[$fav_report['client_id']]."\"".
+        " cf_1_option_id=\"".$this->customFieldOptionMap[$fav_report['cf_1_option_id']]."\"".
+        " project_id=\"".$this->projectMap[$fav_report['project_id']]."\"".
+        " task_id=\"".$this->taskMap[$fav_report['task_id']]."\"".
+        " billable=\"".$fav_report['billable']."\"".
+        " users=\"".$user_list."\"".
+        " period=\"".$fav_report['period']."\"".
+        " period_start=\"".$fav_report['period_start']."\"".
+        " period_end=\"".$fav_report['period_end']."\"".
+        " show_client=\"".$fav_report['show_client']."\"".
+        " show_invoice=\"".$fav_report['show_invoice']."\"".
+        " show_project=\"".$fav_report['show_project']."\"".
+        " show_start=\"".$fav_report['show_start']."\"".
+        " show_duration=\"".$fav_report['show_duration']."\"".
+        " show_cost=\"".$fav_report['show_cost']."\"".
+        " show_task=\"".$fav_report['show_task']."\"".
+        " show_end=\"".$fav_report['show_end']."\"".
+        " show_note=\"".$fav_report['show_note']."\"".
+        " show_custom_field_1=\"".$fav_report['show_custom_field_1']."\"".
+        " group_by=\"".$fav_report['group_by']."\"".
+        " show_totals_only=\"".$fav_report['show_totals_only']."\">\n");
+        //" sort_by=\"".$fav_report['sort_by']."\"".
+        //" show_empty_days=\"".$fav_report['show_empty_days']."\">\n");
+      fwrite($file, "\t\t<name><![CDATA[".$fav_report["name"]."]]></name>\n");
+      fwrite($file, "\t</fav_report>\n");
+    }
+    fwrite($file, "</fav_reports>\n");
+    unset($fav_reports);
+
+    // Cleanup.
+    unset($users);
+    $this->userMap = array();
+    $this->projectMap = array();
+    $this->taskMap = array();
+    
+    fwrite($file, "</pack>\n");
+    fclose($file);
+    
+    if ($compress) {
+      $this->fileName = tempnam($dirName, 'tt');
+      $this->compress($tmp_file, $this->fileName);
+      unlink($tmp_file);
+    } else
+      $this->fileName = $tmp_file;
+       
+       return true;
+  }
+  
+  // getFileName - returns file name.
+  function getFileName() {
+    return $this->fileName;
+  }
+
+  // compress - compresses the content of the $in file into $out file.
+  function compress($in, $out) {
+       // Initial checks of file names and permissions.
+    if (!file_exists($in) || !is_readable ($in))
+      return false;
+    if ((!file_exists($out) && !is_writable(dirname($out))) || (file_exists($out) && !is_writable($out)))
+      return false;
+
+    $in_file = fopen($in, 'rb');
+
+    if (function_exists('bzopen')) {
+      if (!$out_file = bzopen($out, 'w'))
+        return false;
+
+      while (!feof ($in_file)) {
+        $buffer = fread($in_file, 4096);
+        bzwrite($out_file, $buffer, 4096);
+      }
+      bzclose($out_file);
+    }
+    fclose ($in_file);
+    return true;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttFavReportHelper.class.php b/WEB-INF/lib/ttFavReportHelper.class.php
new file mode 100644 (file)
index 0000000..57fdd4b
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+
+// Class ttFavReportHelper is used to help with favorite report related tasks.
+class ttFavReportHelper {
+
+  // getReports - returns an array of favorite reports for user.
+  static function getReports($user_id) {
+    $mdb2 = getConnection();
+
+    $result = array();
+    $sql = "select * from tt_fav_reports where user_id = $user_id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $result[] = $val;
+      }
+      return mu_sort($result, 'name');
+    }
+    return false;
+  }
+
+  // getReport - returns a report identified by its id.
+  static function getReport($id) {
+       $mdb2 = getConnection();
+       
+       $sql = "select * from tt_fav_reports where id = $id";
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      if ($val = $res->fetchRow()) {
+       return $val;
+      }
+    }
+    return false;
+  }
+  
+  // getReportByName - returns a report identified by its name.
+  static function getReportByName($user_id, $report_name) {
+       $mdb2 = getConnection();
+       
+    $sql = "select * from tt_fav_reports where user_id = $user_id and name = ".$mdb2->quote($report_name);
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if ($val = $res->fetchRow()) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  // insertReport - stores reports settings in database.
+  static function insertReport($fields) {
+    $mdb2 = getConnection();
+    
+    $sql = "insert into tt_fav_reports (name, user_id, client_id, cf_1_option_id, project_id, task_id,
+      billable, invoice, users, period, period_start, period_end,
+      show_client, show_invoice,
+      show_project, show_start, show_duration, show_cost,
+      show_task, show_end, show_note, show_custom_field_1,
+      group_by, show_totals_only)
+      values(".
+      $mdb2->quote($fields['name']).", ".$fields['user_id'].", ".
+      $mdb2->quote($fields['client']).", ".$mdb2->quote($fields['option']).", ".
+      $mdb2->quote($fields['project']).", ".$mdb2->quote($fields['task']).", ".
+      $mdb2->quote($fields['billable']).", ".$mdb2->quote($fields['invoice']).", ".
+      $mdb2->quote($fields['users']).", ".$mdb2->quote($fields['period']).", ".
+      $mdb2->quote($fields['from']).", ".$mdb2->quote($fields['to']).", ".
+      $fields['chclient'].", ".$fields['chinvoice'].", ".
+      $fields['chproject'].", ".$fields['chstart'].", ".$fields['chduration'].", ".$fields['chcost'].", ".
+      $fields['chtask'].", ".$fields['chfinish'].", ".$fields['chnote'].", ".$fields['chcf_1'].", ".
+      $mdb2->quote($fields['group_by']).", ".$fields['chtotalsonly'].")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    $sql = "select last_insert_id() as last_id";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error'))
+      return false;
+
+    $val = $res->fetchRow();
+    return $val['last_id'];
+  }
+  
+  // updateReport - updates report options in the database.
+  function updateReport($fields) {
+       $mdb2 = getConnection();
+         $sql = "update tt_fav_reports set ".
+        "name = ".$mdb2->quote($fields['name']).", ".
+        "client_id = ".$mdb2->quote($fields['client']).", ".
+        "cf_1_option_id = ".$mdb2->quote($fields['option']).", ".
+        "project_id = ".$mdb2->quote($fields['project']).", ".
+        "task_id = ".$mdb2->quote($fields['task']).", ".
+        "billable = ".$mdb2->quote($fields['billable']).", ".
+           "invoice = ".$mdb2->quote($fields['invoice']).", ".
+        "users = ".$mdb2->quote($fields['users']).", ".
+        "period = ".$mdb2->quote($fields['period']).", ".
+        "period_start = ".$mdb2->quote($fields['from']).", ".
+        "period_end = ".$mdb2->quote($fields['to']).", ".
+        "show_client = ".$fields['chclient'].", ".
+           "show_invoice = ".$fields['chinvoice'].", ".
+        "show_project = ".$fields['chproject'].", ".
+           "show_start = ".$fields['chstart'].", ".
+           "show_duration = ".$fields['chduration'].", ".
+           "show_cost = ".$fields['chcost'].", ".                
+           "show_task = ".$fields['chtask'].", ".
+           "show_end = ".$fields['chfinish'].", ".
+           "show_note = ".$fields['chnote'].", ".
+        "show_custom_field_1 = ".$fields['chcf_1'].", ".
+        "group_by = ".$mdb2->quote($fields['group_by']).", ".
+           "show_totals_only = ".$fields['chtotalsonly'].                
+           " where id = ".$fields['id'];
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+         
+    return $fields['id'];
+  }
+  
+  // saveReport - saves report options in the database.
+  static function saveReport($user_id, $bean) {
+    global $user;
+       
+    //  Set default value of 0 for not set checkboxes (in bean).
+    //  Later in this function we use it to construct $fields array to update database.
+    if (!$bean->getAttribute('chclient')) $bean->setAttribute('chclient', 0);
+    if (!$bean->getAttribute('chinvoice')) $bean->setAttribute('chinvoice', 0);
+    if (!$bean->getAttribute('chproject')) $bean->setAttribute('chproject', 0);
+    if (!$bean->getAttribute('chstart')) $bean->setAttribute('chstart', 0);    
+    if (!$bean->getAttribute('chduration')) $bean->setAttribute('chduration', 0);
+    if (!$bean->getAttribute('chcost')) $bean->setAttribute('chcost', 0);
+    if (!$bean->getAttribute('chtask')) $bean->setAttribute('chtask', 0);
+    if (!$bean->getAttribute('chfinish')) $bean->setAttribute('chfinish', 0);
+    if (!$bean->getAttribute('chnote')) $bean->setAttribute('chnote', 0);
+    if (!$bean->getAttribute('chcf_1')) $bean->setAttribute('chcf_1', 0);
+    if (!$bean->getAttribute('chtotalsonly')) $bean->setAttribute('chtotalsonly', 0);    
+       
+    if ($bean->getAttribute('users') && is_array($bean->getAttribute('users'))) {
+      $users_in_bean = $bean->getAttribute('users');
+
+      // If all users are selected - use a null value (which means "all users").
+      $all_users_selected = true;
+      if ($user->canManageTeam()) {
+        $all = ttTeamHelper::getActiveUsers();
+        foreach ($all as $one) {
+          if (!in_array($one['id'], $users_in_bean)) {
+            $all_users_selected = false;
+            break;     
+          }
+        }
+      }
+      if ($all_users_selected)
+        $users = null;
+      else
+        $users = join(',', $users_in_bean);            
+       }
+       if ($bean->getAttribute('start_date')) {
+         $dt = new DateAndTime($user->date_format, $bean->getAttribute('start_date'));
+         $from = $dt->toString(DB_DATEFORMAT);
+       }
+       if ($bean->getAttribute('end_date')) {
+      $dt = new DateAndTime($user->date_format, $bean->getAttribute('end_date'));
+      $to = $dt->toString(DB_DATEFORMAT);
+    }
+       
+       $fields = array(
+      'name'=>$bean->getAttribute('new_fav_report'),
+         'client'=>$bean->getAttribute('client'),
+         'option'=>$bean->getAttribute('option'),
+         'project'=>$bean->getAttribute('project'),
+         'task'=>$bean->getAttribute('task'),
+         'billable'=>$bean->getAttribute('include_records'),
+         'invoice'=>$bean->getAttribute('invoice'),
+         'users'=>$users,
+         'period'=>$bean->getAttribute('period'),
+         'from'=>$from,
+         'to'=>$to,
+         'chclient'=>$bean->getAttribute('chclient'),
+         'chinvoice'=>$bean->getAttribute('chinvoice'),
+      'chproject'=>$bean->getAttribute('chproject'),
+      'chstart'=>$bean->getAttribute('chstart'),       
+         'chduration'=>$bean->getAttribute('chduration'),
+         'chcost'=>$bean->getAttribute('chcost'),              
+         'chtask'=>$bean->getAttribute('chtask'),
+         'chfinish'=>$bean->getAttribute('chfinish'),  
+         'chnote'=>$bean->getAttribute('chnote'),
+         'chcf_1'=>$bean->getAttribute('chcf_1'),
+         'group_by'=>$bean->getAttribute('group_by'),
+         'chtotalsonly'=>$bean->getAttribute('chtotalsonly'));
+       
+       $id = false;
+       $report = ttFavReportHelper::getReportByName($user_id, $fields['name']);
+       if ($report) {
+         $fields['id'] = $report['id'];
+         $id = ttFavReportHelper::updateReport($fields);
+    }
+       else {
+         $fields['user_id'] = $user_id;
+         $id = ttFavReportHelper::insertReport($fields);
+       }
+       
+       return $id;
+  }
+  
+  // deleteReport - deletes a favorite report.
+  static function deleteReport($id) {
+    $mdb2 = getConnection();
+    
+    $sql = "delete from tt_fav_reports where id = $id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // loadReport - loads report options from database into a bean.
+  static function loadReport($user_id, &$bean) {
+       global $user;
+       
+       $val = ttFavReportHelper::getReport($bean->getAttribute('favorite_report'));
+       if ($val) {
+      $bean->setAttribute('client', $val['client_id']);
+      $bean->setAttribute('option', $val['cf_1_option_id']);
+      $bean->setAttribute('project', $val['project_id']);
+      $bean->setAttribute('task', $val['task_id']);
+      $bean->setAttribute('include_records', $val['billable']);
+      $bean->setAttribute('invoice', $val['invoice']);
+      if ($val['users'])
+        $bean->setAttribute('users', explode(',', $val['users']));
+      else {
+       // Null users value means "all users". Add them to the bean.
+       if ($user->canManageTeam()) {
+          $all = ttTeamHelper::getActiveUsers();
+          foreach ($all as $one) {
+            $all_user_ids[] = $one['id'];
+          }
+          $bean->setAttribute('users', $all_user_ids);
+        }
+      }
+      $bean->setAttribute('period', $val['period']);
+      if ($val['period_start']) {
+        $dt = new DateAndTime(DB_DATEFORMAT, $val['period_start']);
+        $bean->setAttribute('start_date', $dt->toString($user->date_format));
+      }
+      if ($val['period_end']) {
+        $dt = new DateAndTime(DB_DATEFORMAT, $val['period_end']);
+        $bean->setAttribute('end_date', $dt->toString($user->date_format));
+      }
+      $bean->setAttribute('chclient', $val['show_client']);
+      $bean->setAttribute('chinvoice', $val['show_invoice']);      
+      $bean->setAttribute('chproject', $val['show_project']);
+      $bean->setAttribute('chstart', $val['show_start']);
+      $bean->setAttribute('chduration', $val['show_duration']);      
+      $bean->setAttribute('chcost', $val['show_cost']);      
+      $bean->setAttribute('chtask', $val['show_task']);
+      $bean->setAttribute('chfinish', $val['show_end']);
+      $bean->setAttribute('chnote', $val['show_note']);
+      $bean->setAttribute('chcf_1', $val['show_custom_field_1']);
+      $bean->setAttribute('group_by', $val['group_by']);
+      $bean->setAttribute('chtotalsonly', $val['show_totals_only']);      
+      $bean->setAttribute('new_fav_report', $val['name']);
+       } else {
+         $attrs = $bean->getAttributes();
+         $attrs = array_merge($attrs, array(
+           'client'=>'',
+           'option'=>'',
+           'project'=>'',
+           'task'=>'',
+           'include_records'=>'',
+           'invoice'=>'',
+           'users'=>$user_id,
+           'period'=>'',
+           'chclient'=>'1',
+           'chinvoice'=>'',
+           'chproject'=>'1',
+           'chstart'=>'1',
+           'chduration'=>'1',
+           'chcost'=>'',
+        'chtask'=>'1',
+           'chfinish'=>'1',
+           'chnote'=>'1',
+        'chcf_1'=>'',
+           'group_by'=>'',
+           'chtotalsonly'=>'',
+           'new_fav_report'=>''
+         ));
+         $bean->setAttributes($attrs);
+       }
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttImportHelper.class.php b/WEB-INF/lib/ttImportHelper.class.php
new file mode 100644 (file)
index 0000000..c3e1859
--- /dev/null
@@ -0,0 +1,394 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+import('ttUserHelper');
+import('ttProjectHelper');
+import('ttTaskHelper');
+import('ttInvoiceHelper');
+import('ttTimeHelper');
+import('ttClientHelper');
+import('ttCustomFieldHelper');
+import('ttFavReportHelper');
+import('ttExpenseHelper');
+
+// ttImportHelper - this class is used to import team data from a file.
+class ttImportHelper {
+  var $errors         = null;    // Errors go here. Set in constructor by reference.
+  
+  var $currentElement = array(); // Current element of the XML file we are parsing.
+  var $currentTag     = '';      // XML tag of the current element.  
+  
+  var $canImport      = true;    // False if we cannot import data due to a login collision.
+  var $teamData       = array(); // Array of team data such as team name, etc.
+  var $team_id        = null;    // New team id we are importing. It is created during the import operation.
+  var $users          = array(); // Array of arrays of user properties.
+  
+  // The following arrays are maps between entity ids in the file versus the database.
+  // In the file they are sequential (1,2,3...) while in the database the entities have different ids.
+  var $userMap       = array(); // User ids.
+  var $projectMap    = array(); // Project ids.
+  var $taskMap       = array(); // Task ids.
+  var $clientMap     = array(); // Client ids.
+  var $invoiceMap    = array(); // Invoice ids.
+  
+  var $customFieldMap       = array(); // Custom field ids.
+  var $customFieldOptionMap = array(); // Custop field option ids.
+  var $logMap        = array(); // Time log ids.
+  
+  // Constructor.
+  function ttImportHelper(&$errors) {
+    $this->errors = &$errors;
+  }
+
+  // startElement - callback handler for opening tag of an XML element.
+  // In this function we assign passed in attributes to currentElement.
+  function startElement($parser, $name, $attrs) {
+    if ($name == 'TEAM'
+         || $name == 'USER'
+         || $name == 'TASK'
+         || $name == 'PROJECT'
+      || $name == 'CLIENT'
+      || $name == 'INVOICE'
+         || $name == 'LOG_ITEM'
+         || $name == 'CUSTOM_FIELD'
+         || $name == 'CUSTOM_FIELD_OPTION'
+         || $name == 'CUSTOM_FIELD_LOG_ENTRY'
+         || $name == 'INVOICE_HEADER'
+         || $name == 'USER_PROJECT_BIND'
+         || $name == 'EXPENSE_ITEM'
+         || $name == 'FAV_REPORT') {
+         $this->currentElement = $attrs;
+         }
+       $this->currentTag = $name;
+  }
+
+  // endElement - callback handler for the closing tag of an XML element.
+  // When we are here, currentElement is an array of the element attributes (as set in startElement).
+  // Here we do the actual import of data into the database.
+  function endElement($parser, $name)  {
+    if ($name == 'TEAM') {
+      $this->teamData = $this->currentElement;
+      // Now teamData is an array of team properties. We'll use it later to create a team.
+      // Cannot create the team here. Need to determine whether logins collide with existing logins.
+      $this->currentElement = array();
+    }
+    if ($name == 'USER') {
+      $this->users[$this->currentElement['ID']] = $this->currentElement;
+      $this->currentElement = array();
+    }
+    if ($name == 'USERS') {
+      foreach ($this->users as $user_item) {
+        if (('' != $user_item['STATUS']) && ttUserHelper::getUserByLogin($user_item['LOGIN'])) {
+          // We have a login collision, cannot import any data.
+          $this->canImport = false;
+          break;
+        }
+      }
+      
+      // Now we can create a team.
+      if ($this->canImport) {
+        $team_id = ttTeamHelper::insert(array(
+          'name' => $this->teamData['NAME'],
+          'address' => $this->teamData['ADDRESS'],
+          'currency' => $this->teamData['CURRENCY'],
+          'lock_interval' => $this->teamData['LOCK_INTERVAL'],
+          'lang' => $this->teamData['LANG'],
+          'decimal_mark' => $this->teamData['DECIMAL_MARK'],
+          'date_format' => $this->teamData['DATE_FORMAT'],
+          'time_format' => $this->teamData['TIME_FORMAT'],
+          'week_start' => $this->teamData['WEEK_START'],
+          'plugins' => $this->teamData['PLUGINS'],
+          'tracking_mode' => $this->teamData['TRACKING_MODE'],
+          'record_type' => $this->teamData['RECORD_TYPE']));
+        if ($team_id) {
+          $this->team_id = $team_id;
+          foreach ($this->users as $key=>$user_item) {
+            $user_id = ttUserHelper::insert(array(
+              'team_id' => $this->team_id,
+              'role' => $user_item['ROLE'],
+              'client_id' => $user_item['CLIENT_ID'], // Note: NOT mapped value, replaced in CLIENT handler. 
+              'name' => $user_item['NAME'],
+              'login' => $user_item['LOGIN'],
+              'password' => $user_item['PASSWORD'],
+              'rate' => $user_item['RATE'],
+              'email' => $user_item['EMAIL'],
+              'status' => $user_item['STATUS']), false);
+            $this->userMap[$key] = $user_id;
+          }
+        }
+      }
+    }
+       if ($name == 'TASK' && $this->canImport) {
+      $this->taskMap[$this->currentElement['ID']] =
+        ttTaskHelper::insert(array(
+          'team_id' => $this->team_id,
+          'name' => $this->currentElement['NAME'],
+          'description' => $this->currentElement['DESCRIPTION'],
+          'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'PROJECT' && $this->canImport) {
+      // Prepare a list of task ids.
+      $tasks = explode(',', $this->currentElement['TASKS']);
+      foreach ($tasks as $id)
+        $mapped_tasks[] = $this->taskMap[$id];
+
+      // Add a new project.
+      $this->projectMap[$this->currentElement['ID']] =
+        ttProjectHelper::insert(array(
+          'team_id' => $this->team_id,
+          'name' => $this->currentElement['NAME'],
+          'description' => $this->currentElement['DESCRIPTION'],
+          'tasks' => $mapped_tasks,
+          'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'USER_PROJECT_BIND' && $this->canImport) {
+      ttUserHelper::insertBind(
+        $this->userMap[$this->currentElement['USER_ID']],
+        $this->projectMap[$this->currentElement['PROJECT_ID']],
+        $this->currentElement['RATE'],
+        $this->currentElement['STATUS']);
+    }
+    
+    if ($name == 'CLIENT' && $this->canImport) {
+      // Prepare a list of project ids.
+      if ($this->currentElement['PROJECTS']) {
+        $projects = explode(',', $this->currentElement['PROJECTS']);
+        foreach ($projects as $id)
+          $mapped_projects[] = $this->projectMap[$id];
+      }
+
+      $this->clientMap[$this->currentElement['ID']] =
+        ttClientHelper::insert(array(
+          'team_id' => $this->team_id,
+          'name' => $this->currentElement['NAME'],
+          'address' => $this->currentElement['ADDRESS'],
+          'tax' => $this->currentElement['TAX'],
+          'projects' => $mapped_projects,
+          'status' => $this->currentElement['STATUS']));
+
+        // Update client_id for tt_users to a mapped value.
+        // We did not do it during user insertion because clientMap was not ready then.
+        if ($this->currentElement['ID'] != $this->clientMap[$this->currentElement['ID']])
+          ttClientHelper::setMappedClient($this->team_id, $this->currentElement['ID'], $this->clientMap[$this->currentElement['ID']]);
+    }
+       if ($name == 'INVOICE' && $this->canImport) {
+      $this->invoiceMap[$this->currentElement['ID']] =
+        ttInvoiceHelper::insert(array(
+          'team_id' => $this->team_id,
+          'name' => $this->currentElement['NAME'],
+          'date' => $this->currentElement['DATE'],
+          'client_id' => $this->clientMap[$this->currentElement['CLIENT_ID']],
+          'discount' => $this->currentElement['DISCOUNT'],
+          'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'LOG_ITEM' && $this->canImport) {
+      $this->logMap[$this->currentElement['ID']] =
+        ttTimeHelper::insert(array(
+          'timestamp' => $this->currentElement['TIMESTAMP'],
+          'user_id' => $this->userMap[$this->currentElement['USER_ID']],
+          'date' => $this->currentElement['DATE'],
+          'start' => $this->currentElement['START'],
+          'finish' => $this->currentElement['FINISH'],
+          'duration' => $this->currentElement['DURATION'],
+          'client' => $this->clientMap[$this->currentElement['CLIENT_ID']],
+          'project' => $this->projectMap[$this->currentElement['PROJECT_ID']],
+          'task' => $this->taskMap[$this->currentElement['TASK_ID']],
+          'invoice' => $this->invoiceMap[$this->currentElement['INVOICE_ID']],
+          'note' => (isset($this->currentElement['COMMENT']) ? $this->currentElement['COMMENT'] : ''),
+          'billable' => $this->currentElement['BILLABLE'],
+          'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'CUSTOM_FIELD' && $this->canImport) {
+      $this->customFieldMap[$this->currentElement['ID']] =
+        ttCustomFieldHelper::insertField(array(
+          'team_id' => $this->team_id,
+          'type' => $this->currentElement['TYPE'],
+          'label' => $this->currentElement['LABEL'],
+          'required' => $this->currentElement['REQUIRED'],
+          'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'CUSTOM_FIELD_OPTION' && $this->canImport) {
+      $this->customFieldOptionMap[$this->currentElement['ID']] =
+        ttCustomFieldHelper::insertOption(array(
+          'field_id' => $this->customFieldMap[$this->currentElement['FIELD_ID']],
+          'value' => $this->currentElement['VALUE']));
+    }
+    if ($name == 'CUSTOM_FIELD_LOG_ENTRY' && $this->canImport) {
+      ttCustomFieldHelper::insertLogEntry(array(
+        'log_id' => $this->logMap[$this->currentElement['LOG_ID']],
+        'field_id' => $this->customFieldMap[$this->currentElement['FIELD_ID']],
+        'option_id' => $this->customFieldOptionMap[$this->currentElement['OPTION_ID']],
+        'value' => $this->currentElement['VALUE'],
+        'status' => $this->currentElement['STATUS']));
+    }
+    if ($name == 'EXPENSE_ITEM' && $this->canImport) {
+      ttExpenseHelper::insert(array(
+        'date' => $this->currentElement['DATE'],
+        'user_id' => $this->userMap[$this->currentElement['USER_ID']],
+        'client_id' => $this->clientMap[$this->currentElement['CLIENT_ID']],
+        'project_id' => $this->projectMap[$this->currentElement['PROJECT_ID']],
+        'name' => $this->currentElement['NAME'],
+        'cost' => $this->currentElement['COST'],
+        'invoice_id' => $this->invoiceMap[$this->currentElement['INVOICE_ID']],
+        'status' => $this->currentElement['STATUS']));
+    }    
+    if ($name == 'FAV_REPORT' && $this->canImport) {
+      $user_list = '';
+      if (strlen($this->currentElement['USERS']) > 0) {
+        $arr = explode(',', $this->currentElement['USERS']);
+               foreach ($arr as $v)
+                 $user_list .= (strlen($user_list) == 0 ? '' : ',').$this->userMap[$v];
+      }
+      ttFavReportHelper::insertReport(array(
+        'name' => $this->currentElement['NAME'],
+        'user_id' => $this->userMap[$this->currentElement['USER_ID']],
+        'client' => $this->clientMap[$this->currentElement['CLIENT_ID']],
+        'option' => $this->customFieldOptionMap[$this->currentElement['CF_1_OPTION_ID']],
+        'project' => $this->projectMap[$this->currentElement['PROJECT_ID']],
+        'task' => $this->taskMap[$this->currentElement['TASK_ID']],
+        'billable' => $this->currentElement['BILLABLE'],
+        'users' => $user_list,
+        'period' => $this->currentElement['PERIOD'],
+        'from' => $this->currentElement['PERIOD_START'],
+        'to' => $this->currentElement['PERIOD_END'],
+        'chclient' => $this->currentElement['SHOW_CLIENT'],
+        'chinvoice' => $this->currentElement['SHOW_INVOICE'],
+        'chproject' => $this->currentElement['SHOW_PROJECT'],
+        'chstart' => $this->currentElement['SHOW_START'],
+        'chduration' => $this->currentElement['SHOW_DURATION'],
+        'chcost' => $this->currentElement['SHOW_COST'],
+        'chtask' => $this->currentElement['SHOW_TASK'],
+        'chfinish' => $this->currentElement['SHOW_END'],
+        'chnote' => $this->currentElement['SHOW_NOTE'],
+        'chcf_1' => $this->currentElement['SHOW_CUSTOM_FIELD_1'],
+        'group_by' => $this->currentElement['GROUP_BY'],
+        'chtotalsonly' => $this->currentElement['SHOW_TOTALS_ONLY']));
+        //'sortby' => $this->currentElement['SORT_BY'],
+        //'chemptydays' => $this->currentElement['SHOW_EMPTY_DAYS']));
+    }
+    $this->currentTag = '';
+  }
+
+  // dataElement - callback handler for text data fragments. It builds up currentElement array with text pieces from XML.
+  function dataElement($parser, $data) {
+    if ($this->currentTag == 'NAME'
+      || $this->currentTag == 'DESCRIPTION'
+      || $this->currentTag == 'LABEL'
+      || $this->currentTag == 'VALUE'
+      || $this->currentTag == 'COMMENT'
+      || $this->currentTag == 'ADDRESS'
+      || $this->currentTag == 'CLIENT_NAME'
+      || $this->currentTag == 'CLIENT_ADDRESS') {
+      if (isset($this->currentElement[$this->currentTag]))
+        $this->currentElement[$this->currentTag] .= trim($data);
+      else
+        $this->currentElement[$this->currentTag] = trim($data);
+    }
+  }
+
+  // importXml - uncomresses the file, reads and parses its content. During parsing,
+  // startElement, endElement, and dataElement functions are called as many times as necessary.
+  // Actual import occurs in the endElement handler.
+  function importXml() {
+       // Do we have a compressed file?
+       $compressed = false;    
+       $file_ext = substr($_FILES['xmlfile']['name'], strrpos($_FILES['xmlfile']['name'], '.') + 1);
+    if (in_array($file_ext, array('bz','tbz','bz2','tbz2'))) {
+      $compressed = true;
+    }
+    
+    // Create a temporary file.
+       $dirName = dirname(TEMPLATE_DIR . '_c/.');
+    $filename = tempnam($dirName, 'import_');
+    
+    // If the file is compressed - uncompress it.
+    if ($compressed) {
+      if (!$this->uncompress($_FILES['xmlfile']['tmp_name'], $filename)) {
+       $this->errors->add($GLOBALS['I18N']->getKey('error.sys'));
+       return;
+      }
+      unlink($_FILES['xmlfile']['tmp_name']);
+    } else {
+      if (!move_uploaded_file($_FILES['xmlfile']['tmp_name'], $filename)) {
+        $this->errors->add($GLOBALS['I18N']->getKey('error.upload'));
+        return;
+      }
+    }
+
+    // Initialize XML parser.
+    $parser = xml_parser_create();
+    xml_set_object($parser, $this);
+    xml_set_element_handler($parser, 'startElement', 'endElement');
+    xml_set_character_data_handler($parser, 'dataElement');
+
+    // Read and parse the content of the file. During parsing, startElement, endElement, and dataElement functions are called.
+    $file = fopen($filename, 'r');
+    while ($data = fread($file, 4096)) {
+      if (!xml_parse($parser, $data, feof($file))) {
+       $this->errors->add(sprintf("XML error: %s at line %d",
+         xml_error_string(xml_get_error_code($parser)),
+         xml_get_current_line_number($parser)));
+         }
+         if (!$this->canImport) {
+               $this->errors->add($GLOBALS['I18N']->getKey('error.user_exists'));
+           break;
+         }
+       }
+       xml_parser_free($parser);
+       if ($file) fclose($file);
+       unlink($filename);
+  }
+
+  // uncompress - uncompresses the content of the $in file into the $out file.
+  function uncompress($in, $out) {
+    // Do we have the uncompress function?
+    if (!function_exists('bzopen'))
+      return false;
+               
+    // Initial checks of file names and permissions.
+    if (!file_exists($in) || !is_readable ($in))
+      return false;
+    if ((!file_exists($out) && !is_writable(dirname($out))) || (file_exists($out) && !is_writable($out)))
+      return false;
+
+    if (!$out_file = fopen($out, 'wb'))
+      return false;
+    if (!$in_file = bzopen ($in, 'r'))
+      return false;
+
+    while (!feof($in_file)) {
+      $buffer = bzread($in_file, 4096);
+      fwrite($out_file, $buffer, 4096);
+    }
+    bzclose($in_file);
+    fclose ($out_file);
+    return true;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttInvoiceHelper.class.php b/WEB-INF/lib/ttInvoiceHelper.class.php
new file mode 100644 (file)
index 0000000..b4d1d88
--- /dev/null
@@ -0,0 +1,438 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttClientHelper');
+import('DateAndTime');
+
+// Class ttInvoiceHelper is used for help with invoices.
+class ttInvoiceHelper {
+       
+  // insert - inserts an invoice in database.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+    
+    $team_id = (int) $fields['team_id'];
+    $name = $fields['name'];
+       if (!$name) return false;
+       $client_id = (int) $fields['client_id'];
+       $date = $fields['date'];
+    if (array_key_exists('status', $fields)) { // Key exists and may be NULL during migration of data.
+      $status_f = ', status';
+      $status_v = ', '.$mdb2->quote($fields['status']);
+    }
+    
+    // Insert a new invoice record.
+    $sql = "insert into tt_invoices (team_id, name, date, client_id $status_f)
+      values($team_id, ".$mdb2->quote($name).", ".$mdb2->quote($date).", $client_id $status_v)"; 
+    $affected = $mdb2->exec($sql);
+    
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+       
+       return $last_id;
+  }
+       
+  // getInvoice - obtains invoice data from the database.
+  static function getInvoice($invoice_id) {
+       global $user;
+    $mdb2 = getConnection();
+    
+    $sql = "select * from tt_invoices where id = $invoice_id and team_id = $user->team_id and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if ($val = $res->fetchRow())
+        return $val;
+    }
+    return false;
+  }
+  
+  // The getInvoiceByName looks up an invoice by name.
+  static function getInvoiceByName($invoice_name) {
+       
+    $mdb2 = getConnection();
+    global $user;
+
+    $sql = "select id from tt_invoices where team_id = $user->team_id and name = ".$mdb2->quote($invoice_name)." and status = 1";
+    
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['id']) {
+        return $val;
+      }
+    }
+    return false;
+  }
+
+  // The getInvoiceItems retrieves tt_log items associated with the invoice. 
+  static function getInvoiceItems($invoice_id) {
+    global $user;
+    global $i18n;
+    $mdb2 = getConnection();
+
+    // At this time only detailed invoice is supported.
+    // It is anticipated to support "totals only" option later on.
+    
+    // Our query is different depending on tracking mode.
+    if (MODE_TIME == $user->tracking_mode) {
+      // In "time only" tracking mode there is a single user rate.
+      $sql = "select l.date as date, 1 as type, u.name as user_name, p.name as project_name,
+      t.name as task_name, l.comment as note,
+      time_format(l.duration, '%k:%i') as duration,
+      cast(l.billable * u.rate * time_to_sec(l.duration)/3600 as decimal(10, 2)) as cost from tt_log l
+      inner join tt_users u on (l.user_id = u.id)
+      left join tt_projects p on (p.id = l.project_id)
+      left join tt_tasks t on (t.id = l.task_id)
+      where l.status = 1 and l.billable = 1 and l.invoice_id = $invoice_id order by l.date, u.name";
+    } else {
+      $sql = "select l.date as date, 1 as type, u.name as user_name, p.name as project_name,
+        t.name as task_name, l.comment as note,
+        time_format(l.duration, '%k:%i') as duration,
+        cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2)) as cost from tt_log l
+        inner join tt_users u on (l.user_id = u.id)
+        left join tt_projects p on (p.id = l.project_id)
+        left join tt_tasks t on (t.id = l.task_id)
+        left join tt_user_project_binds upb on (upb.user_id = l.user_id and upb.project_id = l.project_id)
+        where l.status = 1 and l.billable = 1 and l.invoice_id = $invoice_id order by l.date, u.name";
+    }
+        
+    // If we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
+    if (in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
+      $sql_for_expense_items = "select ei.date as date, 2 as type, u.name as user_name, p.name as project_name,
+        null as task_name, ei.name as note,
+        null as duration, ei.cost as cost from tt_expense_items ei
+        inner join tt_users u on (ei.user_id = u.id)
+        left join tt_projects p on (p.id = ei.project_id)
+        where ei.invoice_id = $invoice_id and ei.status = 1";
+     
+      // Construct a union.
+      $sql = "($sql) union all ($sql_for_expense_items)";
+      
+      $sort_part = " order by date, user_name, type";
+      $sql .= $sort_part;
+    }
+    
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $dt = new DateAndTime(DB_DATEFORMAT);
+      while ($val = $res->fetchRow()) {
+        $dt->parseVal($val['date']);
+        $val['date'] = $dt->toString($user->date_format);
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // delete - deletes the invoice data from the database.
+  static function delete($invoice_id, $delete_invoice_items) {
+       global $user;
+    $mdb2 = getConnection();
+
+    // Handle custom field log records.
+    if ($delete_invoice_items) {
+      $sql = "update tt_custom_field_log set status = NULL where log_id in (select id from tt_log where invoice_id = $invoice_id and status = 1)";
+      $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+    }
+    
+    // Handle time records.
+    if ($delete_invoice_items)
+      $sql = "update tt_log set status = NULL where invoice_id = $invoice_id";
+    else
+      $sql = "update tt_log set invoice_id = NULL where invoice_id = $invoice_id";
+    $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+    // Handle expense items.
+    if ($delete_invoice_items)           
+      $sql = "update tt_expense_items set status = NULL where invoice_id = $invoice_id";
+    else         
+      $sql = "update tt_expense_items set invoice_id = NULL where invoice_id = $invoice_id";
+    $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+       $sql = "update tt_invoices set status = NULL where id = $invoice_id and team_id = $user->team_id";
+       $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+
+  // The invoiceableItemsExist determines whether invoiceable records exist in the specified period.
+  static function invoiceableItemsExist($fields) {
+       
+       $mdb2 = getConnection();
+    global $user;
+
+    $client_id = (int) $fields['client_id'];
+   
+    $start_date = new DateAndTime($user->date_format, $fields['start_date']);
+    $start = $start_date->toString(DB_DATEFORMAT);
+    
+    $end_date = new DateAndTime($user->date_format, $fields['end_date']);
+    $end = $end_date->toString(DB_DATEFORMAT);
+    
+    if (isset($fields['project_id'])) $project_id = (int) $fields['project_id'];
+    
+    // Our query is different depending on tracking mode.
+    if (MODE_TIME == $user->tracking_mode) {
+      // In "time only" tracking mode there is a single user rate.
+      $sql = "select count(*) as num from tt_log l, tt_users u
+        where l.status = 1 and l.client_id = $client_id and l.invoice_id is NULL
+        and l.date >= ".$mdb2->quote($start)." and l.date <= ".$mdb2->quote($end)."
+        and l.billable * u.rate * time_to_sec(l.duration)/3600 > 0
+        and l.user_id = u.id";
+    } else {
+      // sql part for project id.
+      if ($project_id) $project_part = " and l.project_id = $project_id";
+               
+      // When we have projects, rates are defined for each project in tt_user_project_binds table.
+      $sql = "select count(*) as num from tt_log l, tt_user_project_binds upb
+        where l.status = 1 and l.client_id = $client_id $project_part and l.invoice_id is NULL
+        and l.date >= ".$mdb2->quote($start)." and l.date <= ".$mdb2->quote($end)."
+        and l.billable * upb.rate * time_to_sec(l.duration)/3600 > 0
+        and upb.user_id = l.user_id and upb.project_id = l.project_id";
+    }
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['num']) {
+        return true;
+      }
+    }
+    
+    // sql part for project id.
+    if ($project_id) $project_part = " and ei.project_id = $project_id";
+    
+    $sql = "select count(*) as num from tt_expense_items ei
+      where ei.client_id = $client_id $project_part and ei.invoice_id is NULL
+      and ei.date >= ".$mdb2->quote($start)." and ei.date <= ".$mdb2->quote($end)."
+      and ei.cost <> 0 and ei.status = 1";
+    $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['num']) {
+        return true;
+      }
+    }
+      
+    return false;
+  }
+  
+  // createInvoice - marks items for invoice as belonging to it (with its reference number).
+  static function createInvoice($fields) {
+       
+       $mdb2 = getConnection();
+    global $user;
+       
+       $name = $fields['name'];
+       if (!$name)
+         return false;
+
+       $client_id = (int) $fields['client_id'];
+
+       $invoice_date = new DateAndTime($user->date_format, $fields['date']);
+    $date = $invoice_date->toString(DB_DATEFORMAT);
+       
+    $start_date = new DateAndTime($user->date_format, $fields['start_date']);
+    $start = $start_date->toString(DB_DATEFORMAT);
+    
+    $end_date = new DateAndTime($user->date_format, $fields['end_date']);
+    $end = $end_date->toString(DB_DATEFORMAT);
+    
+    if (isset($fields['project_id'])) $project_id = (int) $fields['project_id'];
+    
+    // Create a new invoice record.
+    $sql = "insert into tt_invoices (team_id, name, date, client_id)
+      values($user->team_id, ".$mdb2->quote($name).", ".$mdb2->quote($date).", $client_id)"; 
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Mark associated invoice items with invoice id.
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+    
+    // Our update sql is different depending on tracking mode.
+    if (MODE_TIME == $user->tracking_mode) {
+      // In "time only" tracking mode there is a single user rate.
+      $sql = "update tt_log l
+        left join tt_users u on (u.id = l.user_id)
+        set l.invoice_id = $last_id
+        where l.status = 1 and l.client_id = $client_id and l.invoice_id is NULL
+        and l.date >= ".$mdb2->quote($start)." and l.date <= ".$mdb2->quote($end)."
+        and l.billable * u.rate * time_to_sec(l.duration)/3600 > 0";
+    } else {
+       // sql part for project id.
+      if ($project_id) $project_part = " and l.project_id = $project_id";
+      
+      // When we have projects, rates are defined for each project in tt_user_project_binds.
+      $sql = "update tt_log l
+        left join tt_user_project_binds upb on (upb.user_id = l.user_id and upb.project_id = l.project_id)
+        set l.invoice_id = $last_id
+        where l.status = 1 and l.client_id = $client_id $project_part and l.invoice_id is NULL
+        and l.date >= ".$mdb2->quote($start)." and l.date <= ".$mdb2->quote($end)."
+        and l.billable * upb.rate * time_to_sec(l.duration)/3600 > 0";
+    }
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    // sql part for project id.
+    if ($project_id) $project_part = " and project_id = $project_id";
+    
+    $sql = "update tt_expense_items set invoice_id = $last_id where client_id = $client_id $project_part and invoice_id is NULL
+      and date >= ".$mdb2->quote($start)." and date <= ".$mdb2->quote($end)." and cost <> 0 and status = 1";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // prepareInvoiceBody - prepares an email body for invoice.
+  static function prepareInvoiceBody($invoice_id, $comment)
+  {
+       global $user;
+       global $i18n;
+       
+       $invoice = ttInvoiceHelper::getInvoice($invoice_id);
+       $client = ttClientHelper::getClient($invoice['client_id'], true);
+       $invoice_items = ttInvoiceHelper::getInvoiceItems($invoice_id);
+       
+       $tax_percent = $client['tax'];
+       
+       $subtotal = 0;
+    $tax = 0;
+    foreach($invoice_items as $item)
+      $subtotal += $item['cost'];
+    if ($tax_percent) {
+      $tax_expenses = in_array('et', explode(',', $user->plugins));
+      foreach($invoice_items as $item) {
+           if ($item['type'] == 2 && !$tax_expenses)
+             continue;
+        $tax += round($item['cost'] * $tax_percent / 100, 2);  
+      }
+    }
+    $total = $subtotal + $tax; 
+    
+    $subtotal = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($subtotal, 2)));
+    if ($tax) $tax = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($tax, 2)));
+    $total = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($total, 2)));
+    
+    if ('.' != $user->decimal_mark) {
+      foreach ($invoice_items as &$item) {
+        $item['cost'] = str_replace('.', $user->decimal_mark, $item['cost']);
+      }
+      unset($item); // Unset the reference. If we don't, the foreach loop below modifies the array while printing.
+                    // See http://stackoverflow.com/questions/8220399/php-foreach-pass-by-reference-last-element-duplicating-bug
+    }
+    
+    // Define some styles to use in email.
+    $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
+    $style_tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
+    $style_tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
+    
+    // Start creating email body.
+    $body = '<html>';
+    $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
+    $body .= '<body>';
+    
+    // Output title.
+    $body .= '<p style="'.$style_title.'">'.$i18n->getKey('title.invoice').' '.htmlspecialchars($invoice['name']).'</p>';
+    
+    // Output comment.
+    if($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>';
+    
+    // Output invoice info.
+    $body .= '<table>';
+    $body .= '<tr><td><b>'.$i18n->getKey('label.date').':</b> '.$invoice['date'].'</td></tr>';
+    $body .= '<tr><td><b>'.$i18n->getKey('label.client').':</b> '.htmlspecialchars($client['name']).'</td></tr>';
+    $body .= '<tr><td><b>'.$i18n->getKey('label.client_address').':</b> '.htmlspecialchars($client['address']).'</td></tr>';
+    $body .= '</table>';
+    
+    $body .= '<p></p>';
+    
+    // Output invoice items.
+    $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
+    $body .= '<tr>';
+    $body .= '<td style="'.$style_tableHeader.'">'.$i18n->getKey('label.date').'</td>';
+    $body .= '<td style="'.$style_tableHeader.'">'.$i18n->getKey('form.invoice.person').'</td>';
+    if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+      $body .= '<td style="'.$style_tableHeader.'">'.$i18n->getKey('label.project').'</td>';
+    if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+      $body .= '<td style="'.$style_tableHeader.'">'.$i18n->getKey('label.task').'</td>';
+    $body .= '<td style="'.$style_tableHeader.'">'.$i18n->getKey('label.note').'</td>';
+    $body .= '<td style="'.$style_tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
+    $body .= '<td style="'.$style_tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
+    $body .= '</tr>';
+    foreach ($invoice_items as $item) {
+      $body .= '<tr>';
+      $body .= '<td>'.$item['date'].'</td>';
+      $body .= '<td>'.htmlspecialchars($item['user_name']).'</td>';
+      if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+        $body .= '<td>'.htmlspecialchars($item['project_name']).'</td>';
+      if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+        $body .= '<td>'.htmlspecialchars($item['task_name']).'</td>';
+      $body .= '<td>'.htmlspecialchars($item['note']).'</td>';
+      $body .= '<td align="right">'.$item['duration'].'</td>';
+      $body .= '<td align="right">'.$item['cost'].'</td>';
+      $body .= '</tr>';
+    }
+    // Output summary.
+    $colspan = 4;
+    if (MODE_PROJECTS == $user->tracking_mode)
+      $colspan++;
+    else if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode)
+      $colspan += 2;
+    $body .= '<tr><td>&nbsp;</td></tr>';
+    if ($tax) {
+      $body .= '<tr><td colspan="'.$colspan.'" align="right"><b>'.$i18n->getKey('label.subtotal').':</b></td><td nowrap align="right">'.$subtotal.'</td></tr>';
+      $body .= '<tr><td colspan="'.$colspan.'" align="right"><b>'.$i18n->getKey('label.tax').':</b></td><td nowrap align="right">'.$tax.'</td></tr>';
+    }
+    $body .= '<tr><td colspan="'.$colspan.'" align="right"><b>'.$i18n->getKey('label.total').':</b></td><td nowrap align="right">'.$total.'</td></tr>';
+    $body .= '</table>';
+  
+    // Output footer.
+    $body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
+    // Finish creating email body.
+    $body .= '</body></html>';
+
+    return $body;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttNotificationHelper.class.php b/WEB-INF/lib/ttNotificationHelper.class.php
new file mode 100644 (file)
index 0000000..8fd185c
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+import('ttUserHelper');
+
+// Class ttNotificationHelper is used to help with notification related tasks.
+class ttNotificationHelper {
+       
+  // get - gets notification details. 
+  static function get($id)
+  {
+    global $user;
+    $mdb2 = getConnection();
+
+    $sql = "select c.id, c.cron_spec, c.report_id, c.email, c.status, fr.name from tt_cron c
+      left join tt_fav_reports fr on (fr.id = c.report_id)
+      where c.id = $id and c.team_id = $user->team_id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val && $val['id'])
+        return $val;
+    }
+    return false;
+  }
+  
+  // delete - deletes a notification from tt_cron table. 
+  static function delete($id) {
+    global $user;
+           
+    $mdb2 = getConnection();
+    
+    $sql = "delete from tt_cron where id = $id and team_id = $user->team_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+       return true;
+  }
+  
+  // insert function inserts a new notification into database.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+
+    $team_id = (int) $fields['team_id'];
+    $cron_spec = $fields['cron_spec'];
+    $next = (int) $fields['next'];
+    $report_id = (int) $fields['report_id'];
+    $email = $fields['email'];
+    $status = $fields['status'];
+    
+    $sql = "insert into tt_cron (team_id, cron_spec, next, report_id, email, status)
+      values ($team_id, ".$mdb2->quote($cron_spec).", $next, $report_id, ".$mdb2->quote($email).", ".$mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+    return true;
+  } 
+  
+  // update function - updates a notification in database.
+  static function update($fields)
+  {
+    $mdb2 = getConnection();
+    
+    $notification_id = (int) $fields['id'];
+    $team_id = (int) $fields['team_id'];
+    $cron_spec = $fields['cron_spec'];
+    $next = (int) $fields['next'];
+    $report_id = (int) $fields['report_id'];
+    $email = $fields['email'];
+    $status = $fields['status'];
+    
+    $sql = "update tt_cron set cron_spec = ".$mdb2->quote($cron_spec).", next = $next, report_id = $report_id, email = ".$mdb2->quote($email).", status = ".$mdb2->quote($status).
+      " where id = $notification_id and team_id = $team_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttProjectHelper.class.php b/WEB-INF/lib/ttProjectHelper.class.php
new file mode 100644 (file)
index 0000000..1ec21fe
--- /dev/null
@@ -0,0 +1,341 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+import('ttUserHelper');
+
+// Class ttProjectHelper is used to help with project related tasks.
+class ttProjectHelper {
+       
+  // getAssignedProjects - returns an array of assigned projects.
+  static function getAssignedProjects($user_id)
+  {
+       global $user;
+       
+    $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned projects.
+    $sql = "select p.id, p.name, p.tasks, upb.rate from tt_projects p
+      inner join tt_user_project_binds upb on (upb.user_id = $user_id and upb.project_id = p.id and upb.status = 1)
+      where p.team_id = $user->team_id and p.status = 1 order by p.name";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+
+  // getRates - returns an array of project rates for user, including deassigned and deactivated projects.
+  static function getRates($user_id)
+  {
+    global $user;
+       
+    $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select p.id, upb.rate from tt_projects p
+      inner join tt_user_project_binds upb on (upb.user_id = $user_id and upb.project_id = p.id)
+      where team_id = $user->team_id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getProjects - returns an array of active and inactive projects in a team.
+  static function getProjects()
+  {
+       global $user;
+               
+       $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select id, name, tasks from tt_projects
+      where team_id = $user->team_id and (status = 0 or status = 1) order by name";    
+        
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+
+  // getProjectsForClient - returns an array of active and inactive projects in a team for a client.
+  static function getProjectsForClient()
+  {
+       global $user;
+               
+       $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select p.id, p.name, p.tasks from tt_projects p
+         inner join tt_client_project_binds cpb on (cpb.client_id = $user->client_id and cpb.project_id = p.id)
+         where p.team_id = $user->team_id and (p.status = 0 or p.status = 1)
+      order by p.name";        
+        
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  
+  // get - gets details of the project identified by its id. 
+  static function get($id)
+  {
+    global $user;
+    $mdb2 = getConnection();
+
+    $sql = "select id, name, description, status, tasks from tt_projects where id = $id and team_id = $user->team_id and (status = 0 or status = 1)";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val && $val['id'])
+        return $val;
+    }
+    return false;
+  }
+  
+  // The getProjectByName looks up a project by name.
+  static function getProjectByName($name) {
+       
+    $mdb2 = getConnection();
+    global $user;
+
+    $sql = "select id from tt_projects where team_id = $user->team_id and name = ".
+      $mdb2->quote($name)." and (status = 1 or status = 0)";
+       $res = $mdb2->query($sql);
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val && $val['id'])
+        return $val;
+    }
+    return false;
+  }
+  
+  
+  // delete - deletes things associated with a project and marks the project as deleted. 
+  static function delete($id) {
+    $mdb2 = getConnection();
+    
+    // Delete user binds to this project.
+    $sql = "delete from tt_user_project_binds where project_id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+    
+       // Delete task binds to this project.
+    $sql = "delete from tt_project_task_binds where project_id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+    
+    // Remove associated tasks.
+    $sql = "update tt_projects set tasks = NULL where id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Mark project as deleted.
+    $sql = "update tt_projects set status = NULL where id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+       return true;
+  }
+  
+  // insert function inserts a new project into database.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+
+    $team_id = (int) $fields['team_id'];
+
+    $name = $fields['name'];
+    $description = $fields['description'];
+    $users = $fields['users'];
+    $tasks = $fields['tasks'];
+    $comma_separated = implode(',', $tasks); // This is a comma-separated list of associated task ids.
+    $status = $fields['status'];
+    
+    $sql = "insert into tt_projects (team_id, name, description, tasks, status)
+      values ($team_id, ".$mdb2->quote($name).", ".$mdb2->quote($description).", ".$mdb2->quote($comma_separated).", ".$mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+    
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+
+    // Bind the project to users.
+    $active_users = ttTeamHelper::getActiveUsers(array('getAllFields'=>true));
+    foreach ($active_users as $u) {
+      if(in_array($u['id'], $users)) {
+        $sql = "insert into tt_user_project_binds (project_id, user_id, status, rate) values(
+          $last_id, ".$u['id'].", 1, ".$u['rate'].")";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+    }
+
+    // Bind the project to tasks in tt_project_task_binds table.
+    $all_tasks = ttTeamHelper::getAllTasks($team_id);
+    foreach ($all_tasks as $task) {
+      if(in_array($task['id'], $tasks)) {
+        $sql = "insert into tt_project_task_binds (project_id, task_id) values($last_id, ".$task['id'].")";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+    }
+
+    return $last_id;
+  } 
+
+  // update function - updates the project in database.
+  static function update($fields)
+  {
+    $mdb2 = getConnection();
+    
+    $project_id = $fields['id']; // Project we are updating.
+    $name = $fields['name']; // Project name.
+    $description = $fields['description']; // Project description.
+    $users_to_bind = $fields['users']; // Users to bind with project.
+    $tasks_to_bind = $fields['tasks']; // Tasks to bind with project.
+    $status = $fields['status']; // Project status.
+    
+    // Update records in tt_user_project_binds table.
+    $sql = "select user_id, status from tt_user_project_binds where project_id = $project_id";
+    $all_users = array();
+    $users_to_update = array();
+    $res2 = $mdb2->query($sql);
+    while ($row = $res2->fetchRow()) {
+      if(!in_array($row['user_id'], $users_to_bind)) { 
+       // Delete tt_user_project_binds record (safely).
+       ttUserHelper::deleteBind($row['user_id'], $project_id);
+      } else if (!$row['status']) {
+       // If we are here, status of the bind is not active. Memorize such users to update their bind status.
+               $users_to_update[] = $row['user_id'];  // Users we need to update in tt_user_project_binds.
+      }
+      $all_users[] = $row['user_id']; // All users from tt_user_project_binds for project.
+    }
+    // Insert records.
+    $users_to_add = array_diff($users_to_bind, $all_users); // Users missing from tt_user_project_binds, that we need to insert.
+    if(count($users_to_add) > 0) {
+      $sql = "select id, rate from tt_users where id in (".join(', ', $users_to_add).")";
+      $res = $mdb2->query($sql);
+      while ($row = $res->fetchRow()) {
+        $user_rate[$row['id']] = $row['rate'];
+      }
+      foreach ($users_to_add as $id) {
+        $sql = "insert into tt_user_project_binds (user_id, project_id, rate, status) values($id, $project_id, ".$user_rate[$id].", 1)";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+    }
+    // Update status (to active) in existing tt_user_project_binds records.
+    if ($users_to_update) {
+      $sql = "update tt_user_project_binds set status = 1 where project_id = $project_id and user_id in (".join(', ', $users_to_update).")";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+    // End of updating tt_user_project_binds table.
+
+    // Update records in tt_project_task_binds table.
+    // Obtain existing task binds.
+    $existing_task_binds = array();
+    $sql = "select task_id from tt_project_task_binds where project_id = $project_id";
+    $res = $mdb2->query($sql);
+    while ($val = $res->fetchRow()) {
+      $existing_task_binds[] = $val['task_id'];
+    }
+    // Determine which task binds to delete and which ones to add.
+    $task_binds_to_delete = array_diff($existing_task_binds, $tasks_to_bind);
+    $task_binds_to_add = array_diff($tasks_to_bind, $existing_task_binds);
+    foreach ($task_binds_to_delete as $task_id) {
+      $sql = "delete from tt_project_task_binds where project_id = $project_id and task_id = $task_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }    
+    foreach ($task_binds_to_add as $task_id) {
+      $sql = "insert into tt_project_task_binds (project_id, task_id) values($project_id, $task_id)";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+    // End of updating tt_project_task_binds table.
+    
+    // Update project name, description, tasks and status in tt_projects table.
+    $comma_separated = implode(",", $tasks_to_bind); // This is a comma-separated list of associated task ids.
+    $sql = "update tt_projects set name = ".$mdb2->quote($name).", description = ".$mdb2->quote($description).", tasks = ".$mdb2->quote($comma_separated).", status = $status where id = $project_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  /*
+  // getAssignedUsers - returns an array of user ids assigned to a project.
+  static function getAssignedUsers($project_id)
+  {
+       global $user;
+       
+    $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned users.
+    $sql = "select id, name from tt_users u
+      inner join tt_user_project_binds ub on (ub.user_id = u.id and ub.project_id = $project_id and ub.status = 1)";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }*/
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttReportHelper.class.php b/WEB-INF/lib/ttReportHelper.class.php
new file mode 100644 (file)
index 0000000..a51cbec
--- /dev/null
@@ -0,0 +1,1592 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttClientHelper');
+import('DateAndTime');
+import('Period');
+import('ttTimeHelper');
+
+require_once(dirname(__FILE__).'/../../plugins/CustomFields.class.php');
+
+// Class ttReportHelper is used for help with reports.
+class ttReportHelper {
+
+  // getWhere prepares a WHERE clause for a report query.
+  static function getWhere($bean) {
+    global $user;
+
+       // Prepare dropdown parts.
+    $dropdown_parts = '';
+    if ($bean->getAttribute('client'))
+      $dropdown_parts .= ' and l.client_id = '.$bean->getAttribute('client');
+    else if ($user->isClient() && $user->client_id)
+      $dropdown_parts .= ' and l.client_id = '.$user->client_id;
+    if ($bean->getAttribute('option')) $dropdown_parts .= ' and l.id in(select log_id from tt_custom_field_log where status = 1 and option_id = '.$bean->getAttribute('option').')';
+    if ($bean->getAttribute('project')) $dropdown_parts .= ' and l.project_id = '.$bean->getAttribute('project');
+    if ($bean->getAttribute('task')) $dropdown_parts .= ' and l.task_id = '.$bean->getAttribute('task');
+    if ($bean->getAttribute('include_records')=='1') $dropdown_parts .= ' and l.billable = 1';
+    if ($bean->getAttribute('include_records')=='2') $dropdown_parts .= ' and l.billable = 0';
+    if ($bean->getAttribute('invoice')=='1') $dropdown_parts .= ' and l.invoice_id is not NULL';
+    if ($bean->getAttribute('invoice')=='2') $dropdown_parts .= ' and l.invoice_id is NULL';
+        
+    // Prepare user list part.
+    $userlist = -1;
+    if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')))
+      $userlist = join(',', $bean->getAttribute('users'));
+    // Prepare sql query part for user list.
+    $user_list_part = null;
+    if ($user->canManageTeam() || $user->isClient())
+      $user_list_part = " and l.user_id in ($userlist)";
+    else
+      $user_list_part = " and l.user_id = ".$user->id;
+    
+    // Prepare sql query part for where.
+    if ($bean->getAttribute('period'))
+      $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
+        new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
+    }
+    $where = " where l.status = 1 and l.date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and l.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
+      " $user_list_part $dropdown_parts";
+    return $where;
+  }
+  
+  // getFavWhere prepares a WHERE clause for a favorite report query.
+  static function getFavWhere($report) {
+    global $user;
+
+       // Prepare dropdown parts.
+    $dropdown_parts = '';
+    if ($report['client_id'])
+      $dropdown_parts .= ' and l.client_id = '.$report['client_id'];
+    else if ($user->isClient() && $user->client_id)
+      $dropdown_parts .= ' and l.client_id = '.$user->client_id;
+    if ($report['cf_1_option_id']) $dropdown_parts .= ' and l.id in(select log_id from tt_custom_field_log where status = 1 and option_id = '.$report['cf_1_option_id'].')';
+    if ($report['project_id']) $dropdown_parts .= ' and l.project_id = '.$report['project_id'];
+    if ($report['task_id']) $dropdown_parts .= ' and l.task_id = '.$report['task_id'];
+    if ($report['billable']=='1') $dropdown_parts .= ' and l.billable = 1';
+    if ($report['billable']=='2') $dropdown_parts .= ' and l.billable = 0';
+    if ($report['invoice']=='1') $dropdown_parts .= ' and l.invoice_id is not NULL';
+    if ($report['invoice']=='2') $dropdown_parts .= ' and l.invoice_id is NULL';
+        
+    // Prepare user list part.
+    $userlist = -1;
+    if (($user->canManageTeam() || $user->isClient())) {
+      if ($report['users'])
+        $userlist = $report['users'];
+      else {
+       $active_users = ttTeamHelper::getActiveUsers();
+        foreach ($active_users as $single_user)
+          $users[] = $single_user['id'];
+        $userlist = join(',', $users);
+      }
+    }
+    // Prepare sql query part for user list.
+    $user_list_part = null;
+    if ($user->canManageTeam() || $user->isClient())
+      $user_list_part = " and l.user_id in ($userlist)";
+    else
+      $user_list_part = " and l.user_id = ".$user->id;
+    
+    // Prepare sql query part for where.
+    if ($report['period'])
+      $period = new Period($report['period'], new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $report['period_start']),
+        new DateAndTime($user->date_format, $report['period_end']));
+    }
+    $where = " where l.status = 1 and l.date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and l.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
+      " $user_list_part $dropdown_parts";
+    return $where;
+  }
+  
+  // getExpenseWhere prepares WHERE clause for expenses query in a report.
+  static function getExpenseWhere($bean) {
+    global $user;
+
+       // Prepare dropdown parts.
+    $dropdown_parts = '';
+    if ($bean->getAttribute('client'))
+      $dropdown_parts .= ' and ei.client_id = '.$bean->getAttribute('client');
+    else if ($user->isClient() && $user->client_id)
+      $dropdown_parts .= ' and ei.client_id = '.$user->client_id;
+    if ($bean->getAttribute('project')) $dropdown_parts .= ' and ei.project_id = '.$bean->getAttribute('project');
+    if ($bean->getAttribute('invoice')=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL';
+    if ($bean->getAttribute('invoice')=='2') $dropdown_parts .= ' and ei.invoice_id is NULL';
+        
+    // Prepare user list part.
+    $userlist = -1;
+    if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')))
+      $userlist = join(',', $bean->getAttribute('users'));
+    // Prepare sql query part for user list.
+    $user_list_part = null;
+    if ($user->canManageTeam() || $user->isClient())
+      $user_list_part = " and ei.user_id in ($userlist)";
+    else
+      $user_list_part = " and ei.user_id = ".$user->id;
+    
+    // Prepare sql query part for where.
+    if ($bean->getAttribute('period'))
+      $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
+        new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
+    }
+    $where = " where ei.status = 1 and ei.date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
+      " $user_list_part $dropdown_parts";
+    return $where;
+  }
+
+  // getFavExpenseWhere prepares a WHERE clause for expenses query in a favorite report.
+  static function getFavExpenseWhere($report) {
+    global $user;
+
+       // Prepare dropdown parts.
+    $dropdown_parts = '';
+    if ($report['client_id'])
+      $dropdown_parts .= ' and ei.client_id = '.$report['client_id'];
+    else if ($user->isClient() && $user->client_id)
+      $dropdown_parts .= ' and ei.client_id = '.$user->client_id;
+    if ($report['project_id']) $dropdown_parts .= ' and ei.project_id = '.$report['project_id'];
+    if ($report['invoice']=='1') $dropdown_parts .= ' and ei.invoice_id is not NULL';
+    if ($report['invoice']=='2') $dropdown_parts .= ' and ei.invoice_id is NULL';
+        
+    // Prepare user list part.
+    $userlist = -1;
+    if (($user->canManageTeam() || $user->isClient())) {
+      if ($report['users'])
+        $userlist = $report['users'];
+      else {
+       $active_users = ttTeamHelper::getActiveUsers();
+        foreach ($active_users as $single_user)
+          $users[] = $single_user['id'];
+        $userlist = join(',', $users);
+      }
+    }
+    // Prepare sql query part for user list.
+    $user_list_part = null;
+    if ($user->canManageTeam() || $user->isClient())
+      $user_list_part = " and ei.user_id in ($userlist)";
+    else
+      $user_list_part = " and ei.user_id = ".$user->id;
+    
+    // Prepare sql query part for where.
+    if ($report['period'])
+      $period = new Period($report['period'], new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $report['period_start']),
+        new DateAndTime($user->date_format, $report['period_end']));
+    }
+    $where = " where ei.status = 1 and ei.date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and ei.date <= '".$period->getEndDate(DB_DATEFORMAT)."'".
+      " $user_list_part $dropdown_parts";
+    return $where;
+  }
+    
+  // getItems retrieves all items associated with a report.
+  // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
+  // Expense items use the "note" field for item name.
+  static function getItems($bean) {
+    global $user;
+    $mdb2 = getConnection();
+    
+    $group_by_option = $bean->getAttribute('group_by');
+    $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($bean->getAttribute('chstart') || $bean->getAttribute('chfinish'));
+    
+    // Prepare a query for time items in tt_log table.
+    $fields = array(); // An array of fields for database query.
+    array_push($fields, 'l.id as id');
+    array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
+    array_push($fields, 'l.date as date');
+    if($user->canManageTeam() || $user->isClient())
+      array_push($fields, 'u.name as user');
+    // Add client name if it is selected.
+    if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
+      array_push($fields, 'c.name as client');
+    // Add project name if it is selected.
+    if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
+      array_push($fields, 'p.name as project');
+    // Add task name if it is selected.
+    if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
+      array_push($fields, 't.name as task');
+    // Add custom field.
+    $include_cf_1 = $bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option;
+    if ($include_cf_1) {
+      $custom_fields = new CustomFields($user->team_id);
+      $cf_1_type = $custom_fields->fields[0]['type'];
+      if ($cf_1_type == CustomFields::TYPE_TEXT) {
+        array_push($fields, 'cfl.value as cf_1');      
+      } else if ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
+           array_push($fields, 'cfo.value as cf_1');   
+      }
+    }
+    // Add start time.
+    if ($bean->getAttribute('chstart')) {
+      array_push($fields, "l.start as unformatted_start");
+      array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
+    }
+    // Add finish time.
+    if ($bean->getAttribute('chfinish'))
+      array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
+    // Add duration.
+    if ($bean->getAttribute('chduration'))
+      array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
+    // Add note.
+    if ($bean->getAttribute('chnote'))
+      array_push($fields, 'l.comment as note');
+    // Handle cost.
+    $includeCost = $bean->getAttribute('chcost');
+    if ($includeCost) {
+      if (MODE_TIME == $user->tracking_mode)
+        array_push($fields, "cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost");   // Use default user rate.
+      else
+        array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user.
+      array_push($fields, "null as expense"); 
+    }
+    // Add invoice name if it is selected.
+    if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
+      array_push($fields, 'i.name as invoice');
+
+    // Prepare sql query part for left joins.
+    $left_joins = null;
+    if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
+      $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
+    if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
+      $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
+    if ($user->canManageTeam() || $user->isClient() || in_array('ex', explode(',', $user->plugins)))
+       $left_joins .= " left join tt_users u on (u.id = l.user_id)";
+    if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
+      $left_joins .= " left join tt_projects p on (p.id = l.project_id)";      
+    if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
+      $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";            
+    if ($include_cf_1) {
+      if ($cf_1_type == CustomFields::TYPE_TEXT)
+        $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
+      else if ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
+        $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
+          " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
+      }
+    }
+    if ($includeCost && MODE_TIME != $user->tracking_mode)
+      $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
+    
+    $where = ttReportHelper::getWhere($bean);
+    
+    // Construct sql query for tt_log items.    
+    $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
+    // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
+    // with an exception of sorting part, that is added in the end.
+
+    // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
+    if ($bean->getAttribute('chcost') && in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
+    
+      $fields = array(); // An array of fields for database query.
+      array_push($fields, 'ei.id');
+      array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
+      array_push($fields, 'ei.date');
+      if($user->canManageTeam() || $user->isClient())
+        array_push($fields, 'u.name as user');
+      // Add client name if it is selected.
+      if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
+        array_push($fields, 'c.name as client');
+      // Add project name if it is selected.
+      if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
+        array_push($fields, 'p.name as project');
+      if ($bean->getAttribute('chtask') || 'task' == $group_by_option)
+        array_push($fields, 'null'); // null for task name. We need to match column count for union.
+      if ($bean->getAttribute('chcf_1') || 'cf_1' == $group_by_option)
+        array_push($fields, 'null'); // null for cf_1.
+      if ($bean->getAttribute('chstart')) {
+        array_push($fields, 'null'); // null for unformatted_start.
+        array_push($fields, 'null'); // null for start.
+      }
+      if ($bean->getAttribute('chfinish'))
+        array_push($fields, 'null'); // null for finish.
+      if ($bean->getAttribute('chduration'))
+        array_push($fields, 'null'); // null for duration.
+      // Use the note field to print item name.
+      if ($bean->getAttribute('chnote'))
+        array_push($fields, 'ei.name as note');
+      array_push($fields, 'ei.cost as cost');
+      array_push($fields, 'ei.cost as expense');
+      // Add invoice name if it is selected.
+      if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
+        array_push($fields, 'i.name as invoice');
+        
+      // Prepare sql query part for left joins.
+      $left_joins = null;
+      if ($user->canManageTeam() || $user->isClient())
+        $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
+      if ($bean->getAttribute('chclient') || 'client' == $group_by_option)
+        $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
+      if ($bean->getAttribute('chproject') || 'project' == $group_by_option)
+        $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";         
+      if (($user->canManageTeam() || $user->isClient()) && $bean->getAttribute('chinvoice'))
+        $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
+
+      $where = ttReportHelper::getExpenseWhere($bean);
+      
+      // Construct sql query for expense items.
+      $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
+      
+      // Construct a union.
+      $sql = "($sql) union all ($sql_for_expense_items)";
+    }
+    
+    // Determine sort part.
+    $sort_part = ' order by ';
+    if ('no_grouping' == $group_by_option || 'date' == $group_by_option)
+      $sort_part .= 'date';
+    else
+      $sort_part .= $group_by_option.', date';
+    if (($user->canManageTeam() || $user->isClient()) && is_array($bean->getAttribute('users')) && 'user' != $group_by_option)
+      $sort_part .= ', user, type';
+    if ($bean->getAttribute('chstart'))
+      $sort_part .= ', unformatted_start';
+    $sort_part .= ', id';
+    
+    $sql .= $sort_part;
+    // By now we are ready with sql.
+
+    // Obtain items for report.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+          if ($convertTo12Hour) {
+            if($val['start'] != '')
+              $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
+            if($val['finish'] != '')
+              $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
+          }
+             if (isset($val['cost'])) {
+            if ('.' != $user->decimal_mark)
+                 $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+             }
+          if (isset($val['expense'])) {
+            if ('.' != $user->decimal_mark)
+                 $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
+             }
+             if ('no_grouping' != $group_by_option) {
+            $val['grouped_by'] = $val[$group_by_option];
+            if ('date' == $group_by_option) {
+              // This is needed to get the date in user date format.
+              $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
+              $val['grouped_by'] = $o_date->toString($user->date_format);
+              unset($o_date);
+            }
+             }
+            
+          // This is needed to get the date in user date format.
+          $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
+          $val['date'] = $o_date->toString($user->date_format);
+          unset($o_date);
+
+          $row = $val;
+          $report_items[] = $row;
+        }
+      } else
+           die($res->getMessage());
+
+    return $report_items;
+  }
+  
+  // getFavItems retrieves all items associated with a favorite report.
+  // It combines tt_log and tt_expense_items in one array for presentation in one table using mysql union all.
+  // Expense items use the "note" field for item name.
+  static function getFavItems($report) {
+    global $user;
+    $mdb2 = getConnection();
+    
+    $group_by_option = $report['group_by'];
+    $convertTo12Hour = ('%I:%M %p' == $user->time_format) && ($report['show_start'] || $report['show_end']);
+    
+    // Prepare a query for time items in tt_log table.
+    $fields = array(); // An array of fields for database query.
+    array_push($fields, 'l.id as id');
+    array_push($fields, '1 as type'); // Type 1 is for tt_log entries.
+    array_push($fields, 'l.date as date');
+    if($user->canManageTeam() || $user->isClient())
+      array_push($fields, 'u.name as user');
+    // Add client name if it is selected.
+    if ($report['show_client'] || 'client' == $group_by_option)
+      array_push($fields, 'c.name as client');
+    // Add project name if it is selected.
+    if ($report['show_project'] || 'project' == $group_by_option)
+      array_push($fields, 'p.name as project');
+    // Add task name if it is selected.
+    if ($report['show_task'] || 'task' == $group_by_option)
+      array_push($fields, 't.name as task');
+    // Add custom field.
+    $include_cf_1 = $report['show_custom_field_1'] || 'cf_1' == $group_by_option;
+    if ($include_cf_1) {
+      $custom_fields = new CustomFields($user->team_id);
+      $cf_1_type = $custom_fields->fields[0]['type'];
+      if ($cf_1_type == CustomFields::TYPE_TEXT) {
+        array_push($fields, 'cfl.value as cf_1');      
+      } else if ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
+           array_push($fields, 'cfo.value as cf_1');   
+      }
+    }
+    // Add start time.
+    if ($report['show_start']) {
+      array_push($fields, "l.start as unformatted_start");
+      array_push($fields, "TIME_FORMAT(l.start, '%k:%i') as start");
+    }
+    // Add finish time.
+    if ($report['show_end'])
+      array_push($fields, "TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish");
+    // Add duration.
+    if ($report['show_duration'])
+      array_push($fields, "TIME_FORMAT(l.duration, '%k:%i') as duration");
+    // Add note.
+    if ($report['show_note'])
+      array_push($fields, 'l.comment as note');
+    // Handle cost.
+    $includeCost = $report['show_cost'];
+    if ($includeCost) {
+      if (MODE_TIME == $user->tracking_mode)
+        array_push($fields, "cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost");   // Use default user rate.
+      else
+        array_push($fields, "cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2)) as cost"); // Use project rate for user.
+      array_push($fields, "null as expense"); 
+    }
+    // Add invoice name if it is selected.
+    if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
+      array_push($fields, 'i.name as invoice');
+
+    // Prepare sql query part for left joins.
+    $left_joins = null;
+    if ($report['show_client'] || 'client' == $group_by_option)
+      $left_joins .= " left join tt_clients c on (c.id = l.client_id)";
+    if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
+      $left_joins .= " left join tt_invoices i on (i.id = l.invoice_id and i.status = 1)";
+    if ($user->canManageTeam() || $user->isClient() || in_array('ex', explode(',', $user->plugins)))
+       $left_joins .= " left join tt_users u on (u.id = l.user_id)";
+    if ($report['show_project'] || 'project' == $group_by_option)
+      $left_joins .= " left join tt_projects p on (p.id = l.project_id)";      
+    if ($report['show_task'] || 'task' == $group_by_option)
+      $left_joins .= " left join tt_tasks t on (t.id = l.task_id)";            
+    if ($include_cf_1) {
+      if ($cf_1_type == CustomFields::TYPE_TEXT)
+        $left_joins .= " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)";
+      else if ($cf_1_type == CustomFields::TYPE_DROPDOWN) {
+        $left_joins .=  " left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1)".
+          " left join tt_custom_field_options cfo on (cfl.option_id = cfo.id)";
+      }
+    }
+    if ($includeCost && MODE_TIME != $user->tracking_mode)
+      $left_joins .= " left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id)";
+    
+    $where = ttReportHelper::getFavWhere($report);
+    
+    // Construct sql query for tt_log items.    
+    $sql = "select ".join(', ', $fields)." from tt_log l $left_joins $where";
+    // If we don't have expense items (such as when the Expenses plugin is desabled), the above is all sql we need,
+    // with an exception of sorting part, that is added in the end.
+
+    // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
+    if ($report['show_cost'] && in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
+    
+      $fields = array(); // An array of fields for database query.
+      array_push($fields, 'ei.id');
+      array_push($fields, '2 as type'); // Type 2 is for tt_expense_items entries.
+      array_push($fields, 'ei.date');
+      if($user->canManageTeam() || $user->isClient())
+        array_push($fields, 'u.name as user');
+      // Add client name if it is selected.
+      if ($report['show_client'] || 'client' == $group_by_option)
+        array_push($fields, 'c.name as client');
+      // Add project name if it is selected.
+      if ($report['show_project'] || 'project' == $group_by_option)
+        array_push($fields, 'p.name as project');
+      if ($report['show_task'] || 'task' == $group_by_option)
+        array_push($fields, 'null'); // null for task name. We need to match column count for union.
+      if ($report['show_custom_field_1'] || 'cf_1' == $group_by_option)
+        array_push($fields, 'null'); // null for cf_1.
+      if ($report['show_start']) {
+        array_push($fields, 'null'); // null for unformatted_start.
+        array_push($fields, 'null'); // null for start.
+      }
+      if ($report['show_end'])
+        array_push($fields, 'null'); // null for finish.
+      if ($report['show_duration'])
+        array_push($fields, 'null'); // null for duration.
+      // Use the note field to print item name.
+      if ($report['show_note'])
+        array_push($fields, 'ei.name as note');
+      array_push($fields, 'ei.cost as cost');
+      array_push($fields, 'ei.cost as expense');
+      // Add invoice name if it is selected.
+      if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
+        array_push($fields, 'i.name as invoice');
+        
+      // Prepare sql query part for left joins.
+      $left_joins = null;
+      if ($user->canManageTeam() || $user->isClient())
+        $left_joins .= " left join tt_users u on (u.id = ei.user_id)";
+      if ($report['show_client'] || 'client' == $group_by_option)
+        $left_joins .= " left join tt_clients c on (c.id = ei.client_id)";
+      if ($report['show_project'] || 'project' == $group_by_option)
+        $left_joins .= " left join tt_projects p on (p.id = ei.project_id)";         
+      if (($user->canManageTeam() || $user->isClient()) && $report['show_invoice'])
+        $left_joins .= " left join tt_invoices i on (i.id = ei.invoice_id and i.status = 1)";
+
+      $where = ttReportHelper::getFavExpenseWhere($report);
+      
+      // Construct sql query for expense items.
+      $sql_for_expense_items = "select ".join(', ', $fields)." from tt_expense_items ei $left_joins $where";
+      
+      // Construct a union.
+      $sql = "($sql) union all ($sql_for_expense_items)";
+    }
+    
+    // Determine sort part.
+    $sort_part = ' order by ';
+    if ($group_by_option == null || 'no_grouping' == $group_by_option || 'date' == $group_by_option) // TODO: fix DB for NULL values in group_by field.
+      $sort_part .= 'date';
+    else
+      $sort_part .= $group_by_option.', date';
+    if (($user->canManageTeam() || $user->isClient()) /*&& is_array($bean->getAttribute('users'))*/ && 'user' != $group_by_option)
+      $sort_part .= ', user, type';
+    if ($report['show_start'])
+      $sort_part .= ', unformatted_start';
+    $sort_part .= ', id';
+    
+    $sql .= $sort_part;
+    // By now we are ready with sql.
+
+    // Obtain items for report.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+          if ($convertTo12Hour) {
+            if($val['start'] != '')
+              $val['start'] = ttTimeHelper::to12HourFormat($val['start']);
+            if($val['finish'] != '')
+              $val['finish'] = ttTimeHelper::to12HourFormat($val['finish']);
+          }
+             if (isset($val['cost'])) {
+            if ('.' != $user->decimal_mark)
+                 $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+             }
+          if (isset($val['expense'])) {
+            if ('.' != $user->decimal_mark)
+                 $val['expense'] = str_replace('.', $user->decimal_mark, $val['expense']);
+             }
+             if ('no_grouping' != $group_by_option) {
+            $val['grouped_by'] = $val[$group_by_option];
+            if ('date' == $group_by_option) {
+              // This is needed to get the date in user date format.
+              $o_date = new DateAndTime(DB_DATEFORMAT, $val['grouped_by']);
+              $val['grouped_by'] = $o_date->toString($user->date_format);
+              unset($o_date);
+            }
+             }
+            
+          // This is needed to get the date in user date format.
+          $o_date = new DateAndTime(DB_DATEFORMAT, $val['date']);
+          $val['date'] = $o_date->toString($user->date_format);
+          unset($o_date);
+
+          $row = $val;
+          $report_items[] = $row;
+        }
+      } else
+           die($res->getMessage());
+
+    return $report_items;
+  }
+  
+  // getSubtotals calculates report items subtotals when a report is grouped by.
+  // Without expenses, it's a simple select with group by.
+  // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
+  static function getSubtotals($bean) {
+       global $user;
+       
+    $group_by_option = $bean->getAttribute('group_by');
+    if ('no_grouping' == $group_by_option) return null;
+    
+    $mdb2 = getConnection();
+    
+    // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
+    
+    // Determine group by field and a required join.
+    switch ($group_by_option) {
+         case 'date':
+        $group_field = 'l.date';
+        $group_join = '';
+        break;
+      case 'user':
+        $group_field = 'u.name';
+        $group_join = 'left join tt_users u on (l.user_id = u.id) ';
+        break;
+      case 'client':
+        $group_field = 'c.name';
+        $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
+        break;
+      case 'project':
+           $group_field = 'p.name';
+        $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
+           break;
+      case 'task':
+           $group_field = 't.name';
+        $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
+           break;
+      case 'cf_1':
+           $group_field = 'cfo.value';
+           $custom_fields = new CustomFields($user->team_id);
+           if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+          $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.value = cfo.id) ';
+        else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+          $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.option_id = cfo.id) ';
+         break;
+    }
+
+    $where = ttReportHelper::getWhere($bean);
+    if ($bean->getAttribute('chcost')) {
+      if (MODE_TIME == $user->tracking_mode) {
+       if ($group_by_option != 'user')
+         $left_join = 'left join tt_users u on (l.user_id = u.id)';
+        $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
+          sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
+          null as expenses from tt_log l
+          $group_join $left_join $where group by $group_field";        
+      } else {
+        // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
+        $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
+          sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses from tt_log l 
+          $group_join
+          left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where group by $group_field";
+      }
+    } else {
+          $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l 
+         $group_join $where group by $group_field";
+    }
+    // By now we have sql for time items.
+    
+    // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
+    if ($bean->getAttribute('chcost') && in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
+       
+      // Determine group by field and a required join.
+      $group_join = null;
+      $group_field = 'null';
+      switch ($group_by_option) {
+           case 'date':
+          $group_field = 'ei.date';
+          $group_join = '';
+          break;
+        case 'user':
+          $group_field = 'u.name';
+          $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
+          break;
+        case 'client':
+          $group_field = 'c.name';
+          $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
+          break;
+        case 'project':
+             $group_field = 'p.name';
+          $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
+             break;
+      }
+
+      $where = ttReportHelper::getExpenseWhere($bean);
+      $sql_for_expenses = "select $group_field as group_field, null as time, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei 
+        $group_join $where";
+      // Add a "group by" clause if we are grouping.
+      if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
+         
+      // Create a combined query.
+      $sql = "select group_field, sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
+    }
+    
+    // Execute query.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+         while ($val = $res->fetchRow()) {
+               if ('date' == $group_by_option) {
+          // This is needed to get the date in user date format.
+          $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
+          $val['group_field'] = $o_date->toString($user->date_format);
+          unset($o_date);
+               }
+               $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
+        if ($bean->getAttribute('chcost')) {
+             if ('.' != $user->decimal_mark) {
+               $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+               $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
+             }
+         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
+        } else
+          $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
+      }
+    } else
+      die($res->getMessage());
+
+    return $subtotals;
+  }
+  
+  // getFavSubtotals calculates report items subtotals when a favorite report is grouped by.
+  // Without expenses, it's a simple select with group by.
+  // With expenses, it becomes a select with group by from a combined set of records obtained with "union all".
+  static function getFavSubtotals($report) {
+       global $user;
+       
+    $group_by_option = $report['group_by'];
+    if ('no_grouping' == $group_by_option) return null;
+    
+    $mdb2 = getConnection();
+    
+    // Start with sql to obtain subtotals for time items. This simple sql will be used when we have no expenses.
+    
+    // Determine group by field and a required join.
+    switch ($group_by_option) {
+         case 'date':
+        $group_field = 'l.date';
+        $group_join = '';
+        break;
+      case 'user':
+        $group_field = 'u.name';
+        $group_join = 'left join tt_users u on (l.user_id = u.id) ';
+        break;
+      case 'client':
+        $group_field = 'c.name';
+        $group_join = 'left join tt_clients c on (l.client_id = c.id) ';
+        break;
+      case 'project':
+           $group_field = 'p.name';
+        $group_join = 'left join tt_projects p on (l.project_id = p.id) ';
+           break;
+      case 'task':
+           $group_field = 't.name';
+        $group_join = 'left join tt_tasks t on (l.task_id = t.id) ';
+           break;
+      case 'cf_1':
+           $group_field = 'cfo.value';
+           $custom_fields = new CustomFields($user->team_id);
+           if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+          $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.value = cfo.id) ';
+        else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+          $group_join = 'left join tt_custom_field_log cfl on (l.id = cfl.log_id and cfl.status = 1) left join tt_custom_field_options cfo on (cfl.option_id = cfo.id) ';
+         break;
+    }
+
+    $where = ttReportHelper::getFavWhere($report);
+    if ($report['show_cost']) {
+      if (MODE_TIME == $user->tracking_mode) {
+       if ($group_by_option != 'user')
+         $left_join = 'left join tt_users u on (l.user_id = u.id)';
+        $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
+          sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10, 2))) as cost,
+          null as expenses from tt_log l
+          $group_join $left_join $where group by $group_field";        
+      } else {
+        // If we are including cost and tracking projects, our query (the same as above) needs to join the tt_user_project_binds table.
+        $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, 
+          sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses from tt_log l 
+          $group_join
+          left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where group by $group_field";
+      }
+    } else {
+          $sql = "select $group_field as group_field, sum(time_to_sec(l.duration)) as time, null as expenses from tt_log l 
+         $group_join $where group by $group_field";
+    }
+    // By now we have sql for time items.
+    
+    // However, when we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
+    if ($report['show_cost'] && in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
+       
+      // Determine group by field and a required join.
+      $group_join = null;
+      $group_field = 'null';
+      switch ($group_by_option) {
+           case 'date':
+          $group_field = 'ei.date';
+          $group_join = '';
+          break;
+        case 'user':
+          $group_field = 'u.name';
+          $group_join = 'left join tt_users u on (ei.user_id = u.id) ';
+          break;
+        case 'client':
+          $group_field = 'c.name';
+          $group_join = 'left join tt_clients c on (ei.client_id = c.id) ';
+          break;
+        case 'project':
+             $group_field = 'p.name';
+          $group_join = 'left join tt_projects p on (ei.project_id = p.id) ';
+             break;
+      }
+
+      $where = ttReportHelper::getFavExpenseWhere($report);
+      $sql_for_expenses = "select $group_field as group_field, null as time, sum(ei.cost) as cost, sum(ei.cost) as expenses from tt_expense_items ei 
+        $group_join $where";
+      // Add a "group by" clause if we are grouping.
+      if ('null' != $group_field) $sql_for_expenses .= " group by $group_field";
+         
+      // Create a combined query.
+      $sql = "select group_field, sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t group by group_field";
+    }
+    
+    // Execute query.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+         while ($val = $res->fetchRow()) {
+               if ('date' == $group_by_option) {
+          // This is needed to get the date in user date format.
+          $o_date = new DateAndTime(DB_DATEFORMAT, $val['group_field']);
+          $val['group_field'] = $o_date->toString($user->date_format);
+          unset($o_date);
+               }
+               $time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
+        if ($report['show_cost']) {
+             if ('.' != $user->decimal_mark) {
+               $val['cost'] = str_replace('.', $user->decimal_mark, $val['cost']);
+               $val['expenses'] = str_replace('.', $user->decimal_mark, $val['expenses']);
+             }
+         $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time,'cost'=>$val['cost'],'expenses'=>$val['expenses']);
+        } else
+          $subtotals[$val['group_field']] = array('name'=>$val['group_field'],'time'=>$time);
+      }
+    } else
+      die($res->getMessage());
+
+    return $subtotals;
+  }
+  
+  // getTotals calculates total hours and cost for all report items.
+  static function getTotals($bean)
+  {
+    global $user;
+    
+    $mdb2 = getConnection();
+    
+    $where = ttReportHelper::getWhere($bean);
+       
+    // Start with a query for time items.
+    if ($bean->getAttribute('chcost')) {
+      if (MODE_TIME == $user->tracking_mode) {
+        $sql = "select sum(time_to_sec(l.duration)) as time,
+          sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses 
+          from tt_log l
+          left join tt_users u on (l.user_id = u.id) $where";
+      } else {
+        $sql = "select sum(time_to_sec(l.duration)) as time,
+          sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses
+          from tt_log l
+          left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";          
+      }
+    } else
+      $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
+      
+    // If we have expenses, query becomes a bit more complex.
+    if ($bean->getAttribute('chcost') && in_array('ex', explode(',', $user->plugins))) {
+      $where = ttReportHelper::getExpenseWhere($bean);
+      $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
+      // Create a combined query.
+      $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
+       }
+
+       // Execute query.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
+      if ($bean->getAttribute('chcost')) {
+       $total_cost = $val['cost'];
+       if (!$total_cost) $total_cost = '0.00';
+       if ('.' != $user->decimal_mark)
+             $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
+             
+           $total_expenses = $val['expenses'];
+       if (!$total_expenses) $total_expenses = '0.00';
+       if ('.' != $user->decimal_mark)
+             $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
+      }
+    } else
+      die($res->getMessage());
+      
+    if ($bean->getAttribute('period'))
+      $period = new Period($bean->getAttribute('period'), new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $bean->getAttribute('start_date')),
+        new DateAndTime($user->date_format, $bean->getAttribute('end_date')));
+    }
+
+    $totals['start_date'] = $period->getBeginDate();
+       $totals['end_date'] = $period->getEndDate();
+       $totals['time'] = $total_time;
+       $totals['cost'] = $total_cost;
+       $totals['expenses'] = $total_expenses;
+
+    return $totals;
+  }
+  
+  // getFavTotals calculates total hours and cost for all favorite report items.
+  static function getFavTotals($report)
+  {
+    global $user;
+    
+    $mdb2 = getConnection();
+    
+    $where = ttReportHelper::getFavWhere($report);
+       
+    // Start with a query for time items.
+    if ($report['show_cost']) {
+      if (MODE_TIME == $user->tracking_mode) {
+        $sql = "select sum(time_to_sec(l.duration)) as time,
+          sum(cast(l.billable * coalesce(u.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses 
+          from tt_log l
+          left join tt_users u on (l.user_id = u.id) $where";
+      } else {
+        $sql = "select sum(time_to_sec(l.duration)) as time,
+          sum(cast(l.billable * coalesce(upb.rate, 0) * time_to_sec(l.duration)/3600 as decimal(10,2))) as cost,
+          null as expenses
+          from tt_log l
+          left join tt_user_project_binds upb on (l.user_id = upb.user_id and l.project_id = upb.project_id) $where";          
+      }
+    } else
+      $sql = "select sum(time_to_sec(l.duration)) as time, null as cost, null as expenses from tt_log l $where";
+      
+    // If we have expenses, query becomes a bit more complex.
+    if ($report['show_cost'] && in_array('ex', explode(',', $user->plugins))) {
+      $where = ttReportHelper::getFavExpenseWhere($report);
+      $sql_for_expenses = "select null as time, sum(cost) as cost, sum(cost) as expenses from tt_expense_items ei $where";
+      // Create a combined query.
+      $sql = "select sum(time) as time, sum(cost) as cost, sum(expenses) as expenses from (($sql) union all ($sql_for_expenses)) t";
+       }
+
+       // Execute query.
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      $total_time = $val['time'] ? sec_to_time_fmt_hm($val['time']) : null;
+      if ($report['show_cost']) {
+       $total_cost = $val['cost'];
+       if (!$total_cost) $total_cost = '0.00';
+       if ('.' != $user->decimal_mark)
+             $total_cost = str_replace('.', $user->decimal_mark, $total_cost);
+             
+           $total_expenses = $val['expenses'];
+       if (!$total_expenses) $total_expenses = '0.00';
+       if ('.' != $user->decimal_mark)
+             $total_expenses = str_replace('.', $user->decimal_mark, $total_expenses);
+      }
+    } else
+      die($res->getMessage());
+      
+    if ($report['period'])
+      $period = new Period($report['period'], new DateAndTime($user->date_format));
+    else {
+      $period = new Period();
+      $period->setPeriod(
+        new DateAndTime($user->date_format, $report['period_start']),
+        new DateAndTime($user->date_format, $report['period_end']));
+    }
+
+    $totals['start_date'] = $period->getBeginDate();
+       $totals['end_date'] = $period->getEndDate();
+       $totals['time'] = $total_time;
+       $totals['cost'] = $total_cost;
+       $totals['expenses'] = $total_expenses;
+
+    return $totals;
+  }
+  
+  // The assignToInvoice assigns a set of records to a specific invoice.
+  static function assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids)
+  {
+       $mdb2 = getConnection();
+       if ($time_log_ids) {
+      $sql = "update tt_log set invoice_id = ".$mdb2->quote($invoice_id).
+        " where id in(".join(', ', $time_log_ids).")";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+           die($affected->getMessage());
+       }
+    if ($expense_item_ids) {
+      $sql = "update tt_expense_items set invoice_id = ".$mdb2->quote($invoice_id).
+        " where id in(".join(', ', $expense_item_ids).")";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+           die($affected->getMessage());
+       }
+  }
+
+  // prepareReportBody - prepares an email body for report.
+  static function prepareReportBody($bean, $comment)
+  {
+       global $user;
+       global $i18n;
+
+    $items = ttReportHelper::getItems($bean);
+    $group_by = $bean->getAttribute('group_by');
+    if ($group_by && 'no_grouping' != $group_by)
+        $subtotals = ttReportHelper::getSubtotals($bean);
+    $totals = ttReportHelper::getTotals($bean);
+    
+    // Use custom fields plugin if it is enabled.
+    if (in_array('cf', explode(',', $user->plugins)))
+      $custom_fields = new CustomFields($user->team_id);
+
+    // Define some styles to use in email.
+    $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
+    $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
+    $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
+    $rowItem = 'background-color: #ccccce;';
+    $rowItemAlt = 'background-color: #f5f5f5;';
+    $rowSubtotal = 'background-color: #e0e0e0;';
+    $cellLeftAligned = 'text-align: left; vertical-align: top;';
+    $cellRightAligned = 'text-align: right; vertical-align: top;';
+    $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
+    $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
+    
+    // Start creating email body.
+    $body = '<html>';
+    $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
+    $body .= '<body>';
+    
+    // Output title.
+    $body .= '<p style="'.$style_title.'">'.$i18n->getKey('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
+    
+    // Output comment.
+    if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>';
+
+    if ($bean->getAttribute('chtotalsonly')) {
+      // Totals only report. Output subtotals.
+      
+      // Determine group_by header.
+      if ('cf_1' == $group_by)
+        $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
+      else {
+        $key = 'label.'.$group_by;
+        $group_by_header = $i18n->getKey($key);
+      }
+       
+      $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
+      $body .= '<tr>';
+      $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
+      if ($bean->getAttribute('chduration'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
+      if ($bean->getAttribute('chcost'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
+      $body .= '</tr>';
+      foreach($subtotals as $subtotal) {
+        $body .= '<tr style="'.$rowSubtotal.'">';
+        $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : '&nbsp;').'</td>';
+        if ($bean->getAttribute('chduration')) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
+          $body .= '</td>';
+        }
+        if ($bean->getAttribute('chcost')) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotal['cost'] : $subtotal['expenses'];
+          $body .= '</td>';
+        }
+        $body .= '</tr>';
+      }
+      
+      // Print totals.
+      $body .= '<tr><td>&nbsp;</td></tr>';
+      $body .= '<tr style="'.$rowSubtotal.'">';
+      $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
+      if ($bean->getAttribute('chduration')) {
+       $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+       if ($totals['time'] <> '0:00') $body .= $totals['time'];
+       $body .= '</td>';
+      }
+      if ($bean->getAttribute('chcost')) {
+       $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
+        $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
+       $body .= '</td>';
+      }
+      $body .= '</tr>';
+       
+      $body .= '</table>';
+    } else {
+      // Regular report.
+      
+      // Print table header.
+      $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
+      $body .= '<tr>';
+      $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.date').'</td>';
+      if ($user->canManageTeam() || $user->isClient())
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.user').'</td>';
+      if ($bean->getAttribute('chclient'))
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.client').'</td>';
+      if ($bean->getAttribute('chproject'))
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.project').'</td>';
+      if ($bean->getAttribute('chtask'))
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.task').'</td>';
+      if ($bean->getAttribute('chcf_1'))
+        $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
+      if ($bean->getAttribute('chstart'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.start').'</td>';
+      if ($bean->getAttribute('chfinish'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.finish').'</td>';
+      if ($bean->getAttribute('chduration'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
+      if ($bean->getAttribute('chnote'))
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.note').'</td>';
+      if ($bean->getAttribute('chcost'))
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
+      if ($bean->getAttribute('chinvoice'))
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.invoice').'</td>';
+      $body .= '</tr>';
+      
+      // Initialize variables to print subtotals.
+      if ($items && 'no_grouping' != $group_by) {
+        $print_subtotals = true;
+        $first_pass = true;
+        $prev_grouped_by = '';
+        $cur_grouped_by = '';
+      }
+      // Initialize variables to alternate color of rows for different dates.
+      $prev_date = '';
+      $cur_date = '';
+      $row_style = $rowItem;
+      
+      // Print report items.
+      if (is_array($items)) {
+        foreach ($items as $record) {
+          $cur_date = $record['date'];
+          // Print a subtotal row after a block of grouped items.
+          if ($print_subtotals) {
+            $cur_grouped_by = $record['grouped_by'];
+            if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
+              $body .= '<tr style="'.$rowSubtotal.'">';
+              $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
+              $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
+              if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
+              if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
+              if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
+              if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
+              if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
+              if ($bean->getAttribute('chstart')) $body .= '<td></td>';
+              if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
+              if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
+              if ($bean->getAttribute('chnote')) $body .= '<td></td>';
+              if ($bean->getAttribute('chcost')) {
+               $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+               $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
+               $body .= '</td>';
+              }
+              if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
+              $body .= '</tr>';
+              $body .= '<tr><td>&nbsp;</td></tr>';
+            }
+            $first_pass = false;
+          }
+          
+          // Print a regular row.
+          if ($cur_date != $prev_date)
+            $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
+          $body .= '<tr style="'.$row_style.'">';
+          $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
+          if ($user->canManageTeam() || $user->isClient())
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
+          if ($bean->getAttribute('chclient'))
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
+          if ($bean->getAttribute('chproject'))
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
+          if ($bean->getAttribute('chtask'))
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
+          if ($bean->getAttribute('chcf_1'))
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
+          if ($bean->getAttribute('chstart'))
+            $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
+          if ($bean->getAttribute('chfinish'))
+            $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
+          if ($bean->getAttribute('chduration'))
+            $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
+          if ($bean->getAttribute('chnote'))
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
+          if ($bean->getAttribute('chcost'))
+            $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
+          if ($bean->getAttribute('chinvoice'))
+            $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
+          $body .= '</tr>';
+          
+          $prev_date = $record['date'];
+          if ($print_subtotals)
+            $prev_grouped_by = $record['grouped_by'];
+        }
+      }
+      
+      // Print a terminating subtotal.
+      if ($print_subtotals) {
+        $body .= '<tr style="'.$rowSubtotal.'">';
+        $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
+        $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
+        if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
+        if ($bean->getAttribute('chclient')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
+        if ($bean->getAttribute('chproject')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
+        if ($bean->getAttribute('chtask')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
+        if ($bean->getAttribute('chcf_1')) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
+        if ($bean->getAttribute('chstart')) $body .= '<td></td>';
+        if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
+        if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
+        if ($bean->getAttribute('chnote')) $body .= '<td></td>';
+        if ($bean->getAttribute('chcost')) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
+          $body .= '</td>';
+        }
+        if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
+        $body .= '</tr>';
+      }
+      
+      // Print totals.
+      $body .= '<tr><td>&nbsp;</td></tr>';
+      $body .= '<tr style="'.$rowSubtotal.'">';
+      $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
+      if ($user->canManageTeam() || $user->isClient()) $body .= '<td></td>';
+      if ($bean->getAttribute('chclient')) $body .= '<td></td>';
+      if ($bean->getAttribute('chproject')) $body .= '<td></td>';
+      if ($bean->getAttribute('chtask')) $body .= '<td></td>';
+      if ($bean->getAttribute('chcf_1')) $body .= '<td></td>';
+      if ($bean->getAttribute('chstart')) $body .= '<td></td>';
+      if ($bean->getAttribute('chfinish')) $body .= '<td></td>';
+      if ($bean->getAttribute('chduration')) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
+      if ($bean->getAttribute('chnote')) $body .= '<td></td>';
+      if ($bean->getAttribute('chcost')) {
+       $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
+       $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
+       $body .= '</td>';
+      }
+      if ($bean->getAttribute('chinvoice')) $body .= '<td></td>';
+      $body .= '</tr>';
+      
+      $body .= '</table>';
+    }
+    
+    // Output footer.
+    $body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
+
+    // Finish creating email body.
+    $body .= '</body></html>';
+
+    return $body;
+  }
+  
+// prepareFavReportBody - prepares an email body for a favorite report.
+  static function prepareFavReportBody($report)
+  {
+       global $user;
+       global $i18n;
+       
+       $items = ttReportHelper::getFavItems($report);
+    $group_by = $report['group_by'];
+       if ($group_by && 'no_grouping' != $group_by)
+        $subtotals = ttReportHelper::getFavSubtotals($report);    
+    $totals = ttReportHelper::getFavTotals($report);           
+       
+    // Use custom fields plugin if it is enabled.
+    if (in_array('cf', explode(',', $user->plugins)))
+      $custom_fields = new CustomFields($user->team_id);
+
+    // Define some styles to use in email.
+    $style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
+    $tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
+    $tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
+    $rowItem = 'background-color: #ccccce;';
+    $rowItemAlt = 'background-color: #f5f5f5;';
+    $rowSubtotal = 'background-color: #e0e0e0;';
+    $cellLeftAligned = 'text-align: left; vertical-align: top;';
+    $cellRightAligned = 'text-align: right; vertical-align: top;';
+    $cellLeftAlignedSubtotal = 'font-weight: bold; text-align: left; vertical-align: top;';
+    $cellRightAlignedSubtotal = 'font-weight: bold; text-align: right; vertical-align: top;';
+    
+    // Start creating email body.
+    $body = '<html>';
+    $body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
+    $body .= '<body>';
+    
+    // Output title.
+    $body .= '<p style="'.$style_title.'">'.$i18n->getKey('form.mail.report_subject').': '.$totals['start_date'].' - '.$totals['end_date'].'</p>';
+
+    // Output comment.
+    // if ($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>'; // No comment for fav. reports.
+
+    if ($report['show_totals_only']) {
+      // Totals only report. Output subtotals.
+      
+      // Determine group_by header.
+      if ('cf_1' == $group_by)
+        $group_by_header = htmlspecialchars($custom_fields->fields[0]['label']);
+      else {
+        $key = 'label.'.$group_by;
+        $group_by_header = $i18n->getKey($key);
+      }
+       
+      $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
+      $body .= '<tr>';
+      $body .= '<td style="'.$tableHeader.'">'.$group_by_header.'</td>';
+      if ($report['show_duration'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
+      if ($report['show_cost'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
+      $body .= '</tr>';
+      foreach($subtotals as $subtotal) {
+        $body .= '<tr style="'.$rowSubtotal.'">';
+        $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($subtotal['name'] ? htmlspecialchars($subtotal['name']) : '&nbsp;').'</td>';
+        if ($report['show_duration']) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          if ($subtotal['time'] <> '0:00') $body .= $subtotal['time'];
+          $body .= '</td>';
+        }
+        if ($report['show_cost']) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotal['cost'] : $subtotal['expenses'];
+          $body .= '</td>';
+        }
+        $body .= '</tr>';
+      }
+      
+      // Print totals.
+      $body .= '<tr><td>&nbsp;</td></tr>';
+      $body .= '<tr style="'.$rowSubtotal.'">';
+      $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
+      if ($report['show_duration']) {
+       $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+       if ($totals['time'] <> '0:00') $body .= $totals['time'];
+       $body .= '</td>';
+      }
+      if ($report['show_cost']) {
+       $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
+        $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
+       $body .= '</td>';
+      }
+      $body .= '</tr>';
+       
+      $body .= '</table>';
+    } else {
+      // Regular report.
+      
+      // Print table header.
+      $body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
+      $body .= '<tr>';
+      $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.date').'</td>';
+      if ($user->canManageTeam() || $user->isClient())
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.user').'</td>';
+      if ($report['show_client'])
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.client').'</td>';
+      if ($report['show_project'])
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.project').'</td>';
+      if ($report['show_task'])
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.task').'</td>';
+      if ($report['show_custom_field_1'])
+        $body .= '<td style="'.$tableHeader.'">'.htmlspecialchars($custom_fields->fields[0]['label']).'</td>';
+      if ($report['show_start'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.start').'</td>';
+      if ($report['show_end'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.finish').'</td>';
+      if ($report['show_duration'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.duration').'</td>';
+      if ($report['show_note'])
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.note').'</td>';
+      if ($report['show_cost'])
+        $body .= '<td style="'.$tableHeaderCentered.'" width="5%">'.$i18n->getKey('label.cost').'</td>';
+      if ($report['show_invoice'])
+        $body .= '<td style="'.$tableHeader.'">'.$i18n->getKey('label.invoice').'</td>';
+      $body .= '</tr>';
+      
+      // Initialize variables to print subtotals.
+      if ($items && 'no_grouping' != $group_by) {
+        $print_subtotals = true;
+        $first_pass = true;
+        $prev_grouped_by = '';
+        $cur_grouped_by = '';
+      }
+      // Initialize variables to alternate color of rows for different dates.
+      $prev_date = '';
+      $cur_date = '';
+      $row_style = $rowItem;
+      
+      // Print report items.
+      if (is_array($items)) {
+        foreach ($items as $record) {
+          $cur_date = $record['date'];
+          // Print a subtotal row after a block of grouped items.
+          if ($print_subtotals) {
+            $cur_grouped_by = $record['grouped_by'];
+            if ($cur_grouped_by != $prev_grouped_by && !$first_pass) {
+              $body .= '<tr style="'.$rowSubtotal.'">';
+              $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
+              $subtotal_name = htmlspecialchars($subtotals[$prev_grouped_by]['name']);
+              if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
+              if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
+              if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
+              if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
+              if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
+              if ($report['show_start']) $body .= '<td></td>';
+              if ($report['show_end']) $body .= '<td></td>';
+              if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$prev_grouped_by]['time'].'</td>';
+              if ($report['show_note']) $body .= '<td></td>';
+              if ($report['show_cost']) {
+                $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+                $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$prev_grouped_by]['cost'] : $subtotals[$prev_grouped_by]['expenses'];
+                $body .= '</td>';
+              }
+              if ($report['show_invoice']) $body .= '<td></td>';
+              $body .= '</tr>';
+              $body .= '<tr><td>&nbsp;</td></tr>';     
+            }
+            $first_pass = false;
+         }
+       
+          // Print a regular row.
+          if ($cur_date != $prev_date)
+            $row_style = ($row_style == $rowItem) ? $rowItemAlt : $rowItem;
+          $body .= '<tr style="'.$row_style.'">';
+          $body .= '<td style="'.$cellLeftAligned.'">'.$record['date'].'</td>';
+          if ($user->canManageTeam() || $user->isClient())
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['user']).'</td>';
+          if ($report['show_client'])
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['client']).'</td>';
+          if ($report['show_project'])
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['project']).'</td>';
+          if ($report['show_task'])
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['task']).'</td>';
+          if ($report['show_custom_field_1'])
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['cf_1']).'</td>';
+          if ($report['show_start'])
+            $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['start'].'</td>';
+          if ($report['show_end'])
+            $body .= '<td nowrap style="'.$cellRightAligned.'">'.$record['finish'].'</td>';
+          if ($report['show_duration'])
+            $body .= '<td style="'.$cellRightAligned.'">'.$record['duration'].'</td>';
+          if ($report['show_note'])
+            $body .= '<td style="'.$cellLeftAligned.'">'.htmlspecialchars($record['note']).'</td>';
+          if ($report['show_cost'])
+            $body .= '<td style="'.$cellRightAligned.'">'.$record['cost'].'</td>';
+          if ($report['show_invoice'])
+            $body .= '<td style="'.$cellRightAligned.'">'.htmlspecialchars($record['invoice']).'</td>';
+          $body .= '</tr>';
+            
+          $prev_date = $record['date'];
+          if ($print_subtotals)
+            $prev_grouped_by = $record['grouped_by'];
+        }
+      }
+      
+      // Print a terminating subtotal.
+      if ($print_subtotals) {
+        $body .= '<tr style="'.$rowSubtotal.'">';
+        $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.subtotal').'</td>';
+        $subtotal_name = htmlspecialchars($subtotals[$cur_grouped_by]['name']);
+        if ($user->canManageTeam() || $user->isClient()) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'user' ? $subtotal_name : '').'</td>';
+        if ($report['show_client']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'client' ? $subtotal_name : '').'</td>';
+        if ($report['show_project']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'project' ? $subtotal_name : '').'</td>';
+        if ($report['show_task']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'task' ? $subtotal_name : '').'</td>';
+        if ($report['show_custom_field_1']) $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.($group_by == 'cf_1' ? $subtotal_name : '').'</td>';
+        if ($report['show_start']) $body .= '<td></td>';
+        if ($report['show_end']) $body .= '<td></td>';
+        if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$subtotals[$cur_grouped_by]['time'].'</td>';
+        if ($report['show_note']) $body .= '<td></td>';
+        if ($report['show_cost']) {
+          $body .= '<td style="'.$cellRightAlignedSubtotal.'">';
+          $body .= ($user->canManageTeam() || $user->isClient()) ? $subtotals[$cur_grouped_by]['cost'] : $subtotals[$cur_grouped_by]['expenses'];
+          $body .= '</td>';
+        }
+        if ($report['show_invoice']) $body .= '<td></td>';
+        $body .= '</tr>';
+      }
+      
+      // Print totals.
+      $body .= '<tr><td>&nbsp;</td></tr>';
+      $body .= '<tr style="'.$rowSubtotal.'">';
+      $body .= '<td style="'.$cellLeftAlignedSubtotal.'">'.$i18n->getKey('label.total').'</td>';
+      if ($user->canManageTeam() || $user->isClient()) $body .= '<td></td>';
+      if ($report['show_client']) $body .= '<td></td>';
+      if ($report['show_project']) $body .= '<td></td>';
+      if ($report['show_task']) $body .= '<td></td>';
+      if ($report['show_custom_field_1']) $body .= '<td></td>';
+      if ($report['show_start']) $body .= '<td></td>';
+      if ($report['show_end']) $body .= '<td></td>';
+      if ($report['show_duration']) $body .= '<td style="'.$cellRightAlignedSubtotal.'">'.$totals['time'].'</td>';
+      if ($report['show_note']) $body .= '<td></td>';
+      if ($report['show_cost']) {
+       $body .= '<td nowrap style="'.$cellRightAlignedSubtotal.'">'.htmlspecialchars($user->currency).' ';
+       $body .= ($user->canManageTeam() || $user->isClient()) ? $totals['cost'] : $totals['expenses'];
+       $body .= '</td>';
+      }
+      if ($report['show_invoice']) $body .= '<td></td>';
+      $body .= '</tr>';
+      
+      $body .= '</table>';
+    }
+    
+    // Output footer.
+    $body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
+
+    // Finish creating email body.
+    $body .= '</body></html>';
+
+    return $body;
+  }
+  
+  // sendFavReport - sends a favorite report to a specified email, called from cron.php
+  static function sendFavReport($report, $email) {
+    // We are called from cron.php, we have no $bean in session.
+    // cron.php set global $user and $i18n objects to match our favorite report user.
+    global $user;
+    global $i18n;
+    
+    // Prepare report body.
+    $body = ttReportHelper::prepareFavReportBody($report);
+                       
+    import('mail.Mailer');
+    $mailer = new Mailer();
+    $mailer->setCharSet(CHARSET);
+    $mailer->setContentType('text/html');
+    $mailer->setSender(SENDER);
+    $mailer->setReceiver($email);
+    $mailer->setSendType(MAIL_MODE);
+    if (!$mailer->send($report['name'], $body))
+      return false;
+      
+    return true;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttSysConfig.class.php b/WEB-INF/lib/ttSysConfig.class.php
new file mode 100644 (file)
index 0000000..231b979
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+define('SYSC_CHART_INTERVAL', 'chart_interval');
+define('SYSC_CHART_TYPE', 'chart_type');
+define('SYSC_LAST_REPORT_EMAIL', 'last_report_email');
+define('SYSC_LAST_REPORT_CC', 'last_report_cc');
+define('SYSC_LAST_INVOICE_EMAIL', 'last_invoice_email');
+define('SYSC_LAST_INVOICE_CC', 'last_invoice_cc');
+
+// Class ttSysConfig is used for storing and retrieving named values associated with users.
+class ttSysConfig {
+  var $u_id = null;
+  var $mdb2 = null;
+
+  // Constructor.
+  function ttSysConfig($u_id) {
+    $this->u_id = $u_id;
+    $this->mdb2 = getConnection();
+  }
+
+  // The getValue retrieves a value identified by name.
+  function getValue($name) {
+    $res = $this->mdb2->query("select param_value from tt_config where user_id = ".$this->u_id." and param_name=".$this->mdb2->quote($name));
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val['param_value'];
+    }
+    return false;
+  }
+
+  // The setValue sets a value identified by name.
+  function setValue($name, $value) {
+    $rcnt = 0;
+    $res = $this->mdb2->query("select count(*) as rcnt from tt_config where user_id = ".$this->u_id." and param_name = ".$this->mdb2->quote($name));
+    if ($val = $res->fetchRow()) $rcnt = $val['rcnt'];
+    
+    if ($rcnt > 0) {
+      $affected = $this->mdb2->exec("update tt_config set param_value = ".$this->mdb2->quote($value)." where user_id = ".$this->u_id." and param_name=".$this->mdb2->quote($name));
+    } else {
+      $affected = $this->mdb2->exec("insert into tt_config set param_value = ".$this->mdb2->quote($value).", param_name = ".$this->mdb2->quote($name).", user_id = ".$this->u_id);
+    }
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttTaskHelper.class.php b/WEB-INF/lib/ttTaskHelper.class.php
new file mode 100644 (file)
index 0000000..b9198e4
--- /dev/null
@@ -0,0 +1,293 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Class ttTaskHelper is used to help with task related operations.
+class ttTaskHelper {
+
+  // getTask - gets details of the task identified by its id. 
+  static function getTask($id)
+  {
+    global $user;
+    $mdb2 = getConnection();
+
+    $sql = "select id, name, description, status from tt_tasks
+      where id = $id and team_id = $user->team_id and (status = 0 or status = 1)";
+    $res = $mdb2->query($sql);
+
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['id'] != '') {
+        return $val;
+      } else
+        return false;
+    }
+    return false;
+  }
+
+  // getAssignedProjects - returns an array of projects associatied with a task.
+  static function getAssignedProjects($task_id)
+  {
+       global $user;
+       
+    $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned projects.
+    $sql = "select p.id, p.name from tt_projects p
+      inner join tt_project_task_binds ptb on (ptb.project_id = p.id and ptb.task_id = $task_id)
+      where p.team_id = $user->team_id and p.status = 1 order by p.name";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getTaskByName looks up a task by name.
+  static function getTaskByName($task_name) {
+       
+    $mdb2 = getConnection();
+    global $user;
+
+    $sql = "select id from tt_tasks where team_id = $user->team_id and name = ".
+      $mdb2->quote($task_name)." and (status = 1 or status = 0)";
+       $res = $mdb2->query($sql);
+
+       if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+         if ($val['id']) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  // delete - deletes things associated with a task and marks the task as deleted. 
+  static function delete($task_id) {
+    global $user;
+           
+    $mdb2 = getConnection();
+
+    // Delete project binds to this task from tt_project_task_binds table.
+    $sql = "delete from tt_project_task_binds where task_id = $task_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+        
+    // Delete project binds to this task from the tasks field in tt_projects table.
+    // Get projects where tasks is not NULL.
+    $sql = "select id, tasks from tt_projects where team_id = $user->team_id and tasks is not NULL";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error'))
+      return false;
+    while ($val = $res->fetchRow()) {
+      $project_id = $val['id'];
+      $tasks = explode(',', $val['tasks']);
+      
+      if (in_array($task_id, $tasks)) {
+        // Remove task from array.
+        unset($tasks[array_search($task_id, $tasks)]);
+        $comma_separated = implode(',', $tasks); // This is a new comma-separated list of associated task ids.
+      
+        // Re-bind the project to tasks.
+        $sql = "update tt_projects set tasks = ".$mdb2->quote($comma_separated)." where id = $project_id";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+      }
+    }
+
+    // Mark the task as deleted.
+    $sql = "update tt_tasks set status = NULL where id = $task_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+ // insert function inserts a new task into database.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+
+    $team_id = (int) $fields['team_id'];
+    $name = $fields['name'];
+    $description = $fields['description'];
+    $projects = $fields['projects'];
+    $status = $fields['status'];
+        
+    $sql = "insert into tt_tasks (team_id, name, description, status)
+      values ($team_id, ".$mdb2->quote($name).", ".$mdb2->quote($description).", ".$mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    $last_id = 0;
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+    
+    if (is_array($projects)) {
+      foreach ($projects as $p_id) {
+        // Insert task binds into tt_project_task_binds table.
+        $sql = "insert into tt_project_task_binds (project_id, task_id) values($p_id, $last_id)";
+        $affected = $mdb2->exec($sql);
+               if (is_a($affected, 'PEAR_Error'))
+                 return false;
+
+               // Add task bind to the tasks field of the tt_projects table.
+        $sql = "select tasks from tt_projects where id = $p_id";
+        $res = $mdb2->query($sql);
+        if (is_a($res, 'PEAR_Error'))
+          return false;
+
+        $val = $res->fetchRow();
+        $task_ids = $val['tasks'];
+        if ($task_ids) {
+                 $task_ids .= ",$last_id";
+                 $task_ids = ttTaskHelper::sort($task_ids);
+               } else
+                 $task_ids = $last_id;
+
+        $sql = "update tt_projects set tasks = ".$mdb2->quote($task_ids)." where id = $p_id";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+         }
+    }
+    return $last_id;
+  }
+ // update function updates a task in database.
+  static function update($fields)
+  {
+    global $user;
+           
+    $mdb2 = getConnection();
+
+    $task_id = (int)$fields['task_id'];
+    $name = $fields['name'];
+    $description = $fields['description'];
+    $status = $fields['status'];
+    $projects = $fields['projects'];
+
+    $sql = "update tt_tasks set name = ".$mdb2->quote($name).", description = ".$mdb2->quote($description).
+      ", status = $status where id = $task_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      die($affected->getMessage());
+        
+    // Insert task binds into tt_project_task_binds table.
+    $sql = "delete from tt_project_task_binds where task_id = $task_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      die($affected->getMessage());
+    if (count($projects) > 0)
+      foreach ($projects as $p_id) {
+        $sql = "insert into tt_project_task_binds (project_id, task_id) values($p_id, $task_id)";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          die($affected->getMessage());
+      }
+      
+    // Handle task binds in the tasks field of the tt_projects table.
+    // We need to either delete or insert task id in all affected projects.
+    
+    // Get all not deleted projects for team.
+    $sql = "select id, tasks from tt_projects where team_id = $user->team_id and status is not NULL";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error'))
+      die($res->getMessage());
+    
+    // Iterate through projects.
+    while ($val = $res->fetchRow()) {
+      $project_id = $val['id'];
+      $task_ids = $val['tasks'];
+      $task_arr = explode(',', $task_ids);
+      
+      if (is_array($projects) && in_array($project_id, $projects)) {
+       // Task needs to be available for this project.
+               if (!in_array($task_id, $task_arr)) {
+         if ($task_ids) {
+            $task_ids .= ",$task_id";
+            $task_ids = ttTaskHelper::sort($task_ids);
+                 } else
+                       $task_ids = $task_id;
+
+                $sql = "update tt_projects set tasks = ".$mdb2->quote($task_ids)." where id = $project_id";
+                $affected = $mdb2->exec($sql);
+                if (is_a($affected, 'PEAR_Error'))
+           die($affected->getMessage());
+               }
+      } else {
+       // Task needs to be removed from this project.
+        if (in_array($task_id, $task_arr)) {
+          // Remove task from array.
+          unset($task_arr[array_search($task_id, $task_arr)]);
+          $comma_separated = implode(",", $task_arr); // This is a comma-separated list of associated task ids.
+      
+          // Re-bind the project to tasks.
+          $sql = "update tt_projects set tasks = ".$mdb2->quote($comma_separated)." where id = $project_id";
+          $affected = $mdb2->exec($sql);
+          if (is_a($affected, 'PEAR_Error'))
+            die($affected->getMessage());
+        }
+      }
+    }
+    return true;
+  }
+
+  // sort function sorts task ids passed as comma-separated list by their name.
+  static function sort($comma_separated) {
+       // We can't sort an empty string.
+       if (!$comma_separated)
+         return $comma_separated;
+                 
+    $mdb2 = getConnection();
+      
+       $sql = "select id, name from tt_tasks where id in ($comma_separated)";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error'))
+      die ($res->getMessage());
+    
+    $task_arr = array();
+    while ($val = $res->fetchRow()) {
+      $task_arr[] = array('id'=>$val['id'],'name'=>$val['name']);
+    }
+    $task_arr = mu_sort($task_arr, 'name');
+    $task_ids = array();
+    for($i = 0; $i < count($task_arr); $i++) {
+         $task_ids[] = $task_arr[$i]['id'];
+    }
+       $result = implode(',', $task_ids);
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttTeamHelper.class.php b/WEB-INF/lib/ttTeamHelper.class.php
new file mode 100644 (file)
index 0000000..493884a
--- /dev/null
@@ -0,0 +1,957 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttUserHelper');
+import('DateAndTime');
+
+// Class ttTeamHelper - contains helper functions that operate with teams.
+class ttTeamHelper {
+       
+  // The getUserCount function returns number of people in team.
+  static function getUserCount($team_id) {
+       $mdb2 = getConnection();
+    
+    $sql = "select count(id) as cnt from tt_users where team_id = $team_id and status = 1";
+    $res = $mdb2->query($sql);
+
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val['cnt'];
+    }
+    return false;
+  }
+  
+  // The getUsersForClient obtains all active and inactive users in a team that are relevant to a client.
+  static function getUsersForClient() {
+    global $user;
+    $mdb2 = getConnection();
+    
+    $sql = "select u.id, u.name from tt_user_project_binds upb
+      inner join tt_client_project_binds cpb on (upb.project_id = cpb.project_id and cpb.client_id = $user->client_id)
+      inner join tt_users u on (u.id = upb.user_id)
+      where (u.status = 1 or u.status = 0)
+      group by u.id
+      order by u.name";
+    $res = $mdb2->query($sql);
+    $user_list = array();
+    if (is_a($res, 'PEAR_Error'))
+      return false;
+    while ($val = $res->fetchRow()) {
+      $user_list[] = $val;
+    }
+    return $user_list;
+  }  
+
+  // The getActiveUsers obtains all active users in a given team.
+  static function getActiveUsers($options = null) {
+       global $user;
+    $mdb2 = getConnection();
+    
+    if (isset($options['getAllFields']))
+      $sql = "select * from tt_users where team_id = $user->team_id and status = 1 order by name";
+    else
+      $sql = "select id, name from tt_users where team_id = $user->team_id and status = 1 order by name";
+    $res = $mdb2->query($sql);
+    $user_list = array();
+    if (is_a($res, 'PEAR_Error'))
+      return false;
+    while ($val = $res->fetchRow()) {
+      $user_list[] = $val;
+    }
+
+    if (isset($options['putSelfFirst'])) {
+      // Put own entry at the front.
+      $cnt = count($user_list);
+      for($i = 0; $i < $cnt; $i++) {
+        if ($user_list[$i]['id'] == $user->id) {
+          $self = $user_list[$i]; // Found self.
+          array_unshift($user_list, $self); // Put own entry at the front.
+          array_splice($user_list, $i+1, 1); // Remove duplicate.
+        }
+      }
+    }      
+    return $user_list;
+  }
+  
+  
+  // The getUsers obtains all active and inactive (but not deleted) users in a given team.
+  static function getUsers() {
+       global $user;
+    $mdb2 = getConnection();
+    
+    $sql = "select id, name from tt_users where team_id = $user->team_id and (status = 1 or status = 0) order by name";
+    $res = $mdb2->query($sql);
+    $user_list = array();
+    if (is_a($res, 'PEAR_Error'))
+      return false;
+    while ($val = $res->fetchRow()) {
+      $user_list[] = $val;
+    }
+
+    return $user_list;
+  }
+  
+
+  // The getInactiveUsers obtains all inactive users in a given team.
+  static function getInactiveUsers($team_id, $all_fields = false) {
+    $mdb2 = getConnection();
+    
+    if ($all_fields)
+      $sql = "select * from tt_users where team_id = $team_id and status = 0 order by name";
+    else
+      $sql = "select id, name from tt_users where team_id = $team_id and status = 0 order by name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // The getAllUsers obtains all users in a given team.
+  static function getAllUsers($team_id, $all_fields = false) {
+    $mdb2 = getConnection();
+    
+    if ($all_fields)
+      $sql = "select * from tt_users where team_id = $team_id order by name";
+    else
+      $sql = "select id, name from tt_users where team_id = $team_id order by name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // getActiveProjects - returns an array of active projects for team.
+  static function getActiveProjects($team_id)
+  {
+       $result = array();
+    $mdb2 = getConnection();
+
+    $sql = "select id, name, description, tasks from tt_projects
+      where team_id = $team_id and status = 1 order by name";          
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getInactiveProjects - returns an array of inactive projects for team.
+  static function getInactiveProjects($team_id)
+  {
+       $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select id, name, description, tasks from tt_projects
+      where team_id = $team_id and status = 0 order by name";          
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getAllProjects obtains all projects in a given team.
+  static function getAllProjects($team_id, $all_fields = false) {
+    $mdb2 = getConnection();
+    
+    if ($all_fields)
+      $sql = "select * from tt_projects where team_id = $team_id order by status, name";
+    else
+      $sql = "select id, name from tt_projects where team_id = $team_id order by status, name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+
+  // getActiveTasks - returns an array of active tasks for team.
+  static function getActiveTasks($team_id)
+  {
+       $result = array();
+    $mdb2 = getConnection();
+
+    $sql = "select id, name, description from tt_tasks where team_id = $team_id and status = 1 order by name";         
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getInactiveTasks - returns an array of inactive tasks for team.
+  static function getInactiveTasks($team_id)
+  {
+       $result = array();
+    $mdb2 = getConnection();
+    
+    $sql = "select id, name, description from tt_tasks
+      where team_id = $team_id and status = 0 order by name";          
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+    
+  // The getAllTasks obtains all tasks in a given team.
+  static function getAllTasks($team_id, $all_fields = false) {
+    $mdb2 = getConnection();
+    
+    if ($all_fields)
+      $sql = "select * from tt_tasks where team_id = $team_id order by status, name";
+    else
+      $sql = "select id, name from tt_tasks where team_id = $team_id order by status, name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // The getActiveClients returns an array of active clients for team.
+  static function getActiveClients($team_id, $all_fields = false)
+  {
+    $result = array();
+       $mdb2 = getConnection();
+    
+       if ($all_fields)
+      $sql = "select * from tt_clients where team_id = $team_id and status = 1 order by name";
+    else
+      $sql = "select id, name from tt_clients where team_id = $team_id and status = 1 order by name";
+       $res = $mdb2->query($sql);
+       $result = array();
+       if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getInactiveClients returns an array of inactive clients for team.
+  static function getInactiveClients($team_id, $all_fields = false)
+  {
+    $result = array();
+       $mdb2 = getConnection();
+    
+       if ($all_fields)
+      $sql = "select * from tt_clients where team_id = $team_id and status = 0 order by name";
+    else
+      $sql = "select id, name from tt_clients where team_id = $team_id and status = 0 order by name";
+       $res = $mdb2->query($sql);
+    $result = array();
+       if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getAllClients obtains all clients in a given team.
+  static function getAllClients($team_id, $all_fields = false) {
+    $mdb2 = getConnection();
+    
+    if ($all_fields)
+      $sql = "select * from tt_clients where team_id = $team_id order by status, name";
+    else
+      $sql = "select id, name from tt_clients where team_id = $team_id order by status, name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+
+  // The getActiveInvoices returns an array of active invoices for team.
+  static function getActiveInvoices($localizeDates = true)
+  {
+       global $user;
+       
+    $result = array();
+       $mdb2 = getConnection();
+       
+       if (ROLE_CLIENT == $user->role && $user->client_id)
+         $client_part = " and i.client_id = $user->client_id";
+       
+       $sql = "select i.id, i.name, i.date, i.client_id, i.status, c.name as client_name from tt_invoices i 
+         left join tt_clients c on (c.id = i.client_id)
+         where i.status = 1 and i.team_id = $user->team_id $client_part order by i.name";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      $dt = new DateAndTime(DB_DATEFORMAT);
+      while ($val = $res->fetchRow()) {
+       if ($localizeDates) {
+                 $dt->parseVal($val['date']);
+          $val['date'] = $dt->toString($user->date_format);
+       }
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getAllInvoices returns an array of all invoices for team.
+  static function getAllInvoices()
+  {
+       global $user;
+       
+    $result = array();
+       $mdb2 = getConnection();
+       
+       $sql = "select * from tt_invoices where team_id = $user->team_id";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      $dt = new DateAndTime(DB_DATEFORMAT);
+      while ($val = $res->fetchRow()) {
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // The getRecentInvoices returns an array of recent invoices (max 3) for a client.
+  static function getRecentInvoices($team_id, $client_id)
+  {
+       global $user;
+       
+    $result = array();
+       $mdb2 = getConnection();
+    
+       $sql = "select i.id, i.name from tt_invoices i 
+         left join tt_clients c on (c.id = i.client_id)
+         where i.team_id = $team_id and i.status = 1 and c.id = $client_id
+         order by i.id desc limit 3";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      $dt = new DateAndTime(DB_DATEFORMAT);
+      while ($val = $res->fetchRow()) {
+       $result[] = $val;
+      }
+    }
+    return $result;
+  }
+  
+  // getUserToProjectBinds - obtains all user to project binds for a team.
+  static function getUserToProjectBinds($team_id) {
+    $mdb2 = getConnection();
+      
+    $result = array();
+    $sql = "select * from tt_user_project_binds where user_id in (select id from tt_users where team_id = $team_id) order by user_id, status, project_id";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // The getAllCustomFields obtains all custom fields in a given team.
+  static function getAllCustomFields($team_id) {
+    $mdb2 = getConnection();
+
+    $sql = "select * from tt_custom_fields where team_id = $team_id order by status";
+
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+
+  // The getAllCustomFieldOptions obtains all custom field options in a given team.
+  static function getAllCustomFieldOptions($team_id) {
+    $mdb2 = getConnection();
+
+    $sql = "select * from tt_custom_field_options where field_id in (select id from tt_custom_fields where team_id = $team_id) order by id";
+
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // The getCustomFieldLog obtains all custom field log entries for a given team.
+  static function getCustomFieldLog($team_id) {
+    $mdb2 = getConnection();
+
+    $sql = "select * from tt_custom_field_log where field_id in (select id from tt_custom_fields where team_id = $team_id) order by id";
+
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+    
+  // getFavReports - obtains all favorite reports for all users in team.
+  static function getFavReports($team_id) {
+    $mdb2 = getConnection();
+
+    $result = array();
+    $sql = "select * from tt_fav_reports where user_id in (select id from tt_users where team_id = $team_id)";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // getExpenseItems - obtains all expense items for all users in team.
+  static function getExpenseItems($team_id) {
+    $mdb2 = getConnection();
+
+    $result = array();
+    $sql = "select * from tt_expense_items where user_id in (select id from tt_users where team_id = $team_id)";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // getNotifications - obtains notification descriptions for team.
+  static function getNotifications($team_id) {
+    $mdb2 = getConnection();
+
+    $result = array();
+    $sql = "select c.id, c.cron_spec, c.email, fr.name from tt_cron c
+      left join tt_fav_reports fr on (fr.id = c.report_id)
+      where c.team_id = $team_id and c.status is not null"; 
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+
+  // The getTeams function returns an array of all active teams on the server.
+  static function getTeams() {
+    $result = array();
+    $mdb2 = getConnection();
+    
+    $sql =  "select id, name, lang, timestamp from tt_teams where status = 1 order by id desc";
+    $res = $mdb2->query($sql);
+    $result = array();
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $val['date'] = substr($val['timestamp'], 0, 10); // Strip the time.
+        $result[] = $val;
+      }
+      return $result;
+    }
+    return false;
+  }
+  
+  // The markDeleted function marks the team and everything in it as deleted.
+  static function markDeleted($team_id) {
+       
+       // Iterate through team users and mark them as deleted.
+       $users = ttTeamHelper::getAllUsers($team_id);
+       foreach ($users as $one_user) {
+         if (!ttUserHelper::markDeleted($one_user['id']))
+               return false;
+       }
+       
+    // Mark tasks deleted.
+    if (!ttTeamHelper::markTasksDeleted($team_id))
+      return false;
+      
+       $mdb2 = getConnection();
+       
+       // Mark projects deleted.
+    $sql = "update tt_projects set status = NULL where team_id = $team_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+   
+       // Mark clients deleted.
+    $sql = "update tt_clients set status = NULL where team_id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+       // Mark custom fields deleted.
+    $sql = "update tt_custom_fields set status = NULL where team_id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+       // Mark team deleted.
+       $sql = "update tt_teams set status = NULL where id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+       return true;
+  }
+
+  // The getTeamDetails function returns team details.
+  static function getTeamDetails($team_id) {
+    $result = array();
+    $mdb2 = getConnection();
+    
+    $role_manager = ROLE_MANAGER;
+    $sql = "select t.name as team_name, u.id as manager_id, u.name as manager_name, u.login as manager_login, u.email as manager_email
+      from tt_teams t
+      inner join tt_users u on (u.team_id = t.id and u.role = $role_manager)
+      where t.id = $team_id"; 
+    
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val;
+    }
+
+    return false;
+  }
+  
+  // The insert function creates a new team. 
+  static function insert($fields) {
+
+    $mdb2 = getConnection();
+    
+    if ($fields['lock_interval'] !== null) {
+      $locktime_f = ', locktime';
+      $locktime_v = ", " . (int)$fields['lock_interval'];
+    } else {
+      $locktime_f = '';
+      $locktime_v = '';
+    }
+    
+    $lang = $fields['lang'];
+    if (!$lang) {
+      global $i18n;
+      $lang = $i18n->lang;
+    }
+    
+    $decimal_mark = $fields['decimal_mark'];
+    if ($decimal_mark !== null) {
+      $decimal_mark_f = ', decimal_mark';
+      $decimal_mark_v = ', ' . $mdb2->quote($decimal_mark);            
+    } else {
+      $decimal_mark_f = '';
+      $decimal_mark_v = '';            
+    }
+
+    $date_format = $fields['date_format'];
+    if ($date_format !== null) {
+      $date_format_f = ', date_format';
+      $date_format_v = ', ' . $mdb2->quote($date_format);      
+    } else if (defined('DATE_FORMAT_DEFAULT')) {
+      $date_format_f = ', date_format';
+      $date_format_v = ', ' . $mdb2->quote(DATE_FORMAT_DEFAULT);
+    } else {
+      $date_format_f = '';
+      $date_format_v = '';     
+    }
+
+    $time_format = $fields['time_format'];
+    if ($time_format !== null) {
+      $time_format_f = ', time_format';
+      $time_format_v = ', ' . $mdb2->quote($time_format);
+    } else if (defined('TIME_FORMAT_DEFAULT')) {
+      $time_format_f = ', time_format';
+      $time_format_v = ', ' . $mdb2->quote(TIME_FORMAT_DEFAULT);
+    } else {
+      $time_format_f = '';
+      $time_format_v = '';     
+    }
+    
+    $week_start = $fields['week_start'];
+    if ($week_start !== null) {
+      $week_start_f = ', week_start';
+      $week_start_v = ', ' . (int)$week_start;
+    } else if (defined('WEEK_START_DEFAULT')) {
+      $week_start_f = ', week_start';
+      $week_start_v = ', ' . (int)WEEK_START_DEFAULT;          
+    } else {
+      $week_start_f = '';
+      $week_start_v = '';      
+    }
+    
+    $plugins = $fields['plugins'];
+    if ($plugins !== null) {
+      $plugins_f = ', plugins';
+      $plugins_v = ', ' . $mdb2->quote($plugins);
+    } else {
+      $plugins_f = '';
+      $plugins_v = '';         
+    }
+
+    $tracking_mode = $fields['tracking_mode'];
+    if ($tracking_mode !== null) {
+      $tracking_mode_f = ', tracking_mode';
+      $tracking_mode_v = ', ' . (int)$tracking_mode;
+    } else {
+      $tracking_mode_f = '';
+      $tracking_mode_v = '';           
+    }
+    
+    $record_type = $fields['record_type'];
+    if ($record_type !== null) {
+      $record_type_f = ', record_type';
+      $record_type_v = ', ' . (int)$record_type;
+    } else {
+      $record_type_f = '';
+      $record_type_v = '';     
+    }
+    
+    $sql = "insert into tt_teams (name, address, currency $locktime_f, lang $decimal_mark_f $date_format_f $time_format_f $week_start_f $plugins_f $tracking_mode_f $record_type_f)
+      values(".
+      $mdb2->quote(trim($fields['name'])).
+      ", ".$mdb2->quote(trim($fields['address'])).
+      ", ".$mdb2->quote(trim($fields['currency']))." $locktime_v, ".$mdb2->quote($lang).
+      "$decimal_mark_v $date_format_v $time_format_v $week_start_v $plugins_v $tracking_mode_v $record_type_v)";
+    $affected = $mdb2->exec($sql);
+
+    if (!is_a($affected, 'PEAR_Error')) {
+      $team_id = $mdb2->lastInsertID('tt_teams', 'id');
+      return $team_id;
+    }
+
+    return false;
+  }
+
+  // The update function updates team information.
+  static function update($team_id, $fields)    
+  {
+       // We'll require team name to be always set.
+       if (!isset($fields['name'])) return false;
+
+       $mdb2 = getConnection();
+       $name_part = 'name = '.$mdb2->quote($fields['name']);
+    $currency_part = '';
+    $addr_part = '';
+    $locktime_part = '';
+    $lang_part = '';
+    $decimal_mark_part = '';
+    $date_format_part = '';
+    $time_format_part = '';
+    $week_start_part = '';
+    $tracking_mode_part = '';
+    $record_type_part = '';
+    $plugins_part = '';
+    
+    if (isset($fields['address'])) $addr_part = ', address = '.$mdb2->quote($fields['address']);
+    if (isset($fields['currency'])) $currency_part = ', currency = '.$mdb2->quote($fields['currency']);
+    if (isset($fields['locktime'])) $locktime_part = ', locktime = '.intval($fields['locktime']);
+    if (isset($fields['lang'])) $lang_part = ', lang = '.$mdb2->quote($fields['lang']);
+    if (isset($fields['decimal_mark'])) $decimal_mark_part = ', decimal_mark = '.$mdb2->quote($fields['decimal_mark']);
+    if (isset($fields['date_format'])) $date_format_part = ', date_format = '.$mdb2->quote($fields['date_format']);
+    if (isset($fields['time_format'])) $time_format_part = ', time_format = '.$mdb2->quote($fields['time_format']);
+    if (isset($fields['week_start'])) $week_start_part = ', week_start = '.intval($fields['week_start']);
+    if (isset($fields['tracking_mode'])) $tracking_mode_part = ', tracking_mode = '.intval($fields['tracking_mode']);
+    if (isset($fields['record_type'])) $record_type_part = ', record_type = '.intval($fields['record_type']);
+    if (isset($fields['plugins'])) $plugins_part = ', plugins = '.$mdb2->quote($fields['plugins']);
+
+    $sql = "update tt_teams set $name_part $addr_part $currency_part $locktime_part $lang_part $decimal_mark_part
+      $date_format_part $time_format_part $week_start_part $tracking_mode_part $record_type_part $plugins_part where id = $team_id";
+    $affected = $mdb2->exec($sql);
+    
+    if (is_a($affected, 'PEAR_Error')) {
+      return false;
+    }
+    
+    return true;
+  }
+  
+  // The getInactiveTeams is a maintenance function that returns an array of inactive team ids (max 100).
+  static function getInactiveTeams() {
+    $inactive_teams = array();
+    $mdb2 = getConnection();
+
+    // Get all team ids for teams created or modified more than 1 year ago.
+    $ts = date('Y-m-d', strtotime('-1 year'));
+    $sql =  "select id from tt_teams where timestamp < '$ts' order by id";
+    $res = $mdb2->query($sql);
+
+    $count = 0;
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $team_id = $val['id'];
+       if (ttTeamHelper::isTeamActive($team_id) == false) {
+          $count ++;
+         $inactive_teams[] = $team_id;
+         // Limit the array size for perfomance by allowing this operation on small chunks only.
+         if ($count >= 25) break;
+       }
+      }
+      return $inactive_teams;
+    }
+    return false;
+  }
+  
+  // The isTeamActive determines if a team is using Time Tracker or abandoned it.
+  static function isTeamActive($team_id) {
+       $users = array();
+  
+       $mdb2 = getConnection();
+    $sql = "select id from tt_users where team_id = $team_id";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) die($res->getMessage());
+    while ($val = $res->fetchRow()) {
+      $users[] = $val['id'];
+    }
+    $user_list = implode(',', $users); // This is a comma-separated list of user ids.
+    if (!$user_list)
+      return false; // No users in team.
+
+    $count = 0;
+       $ts = date('Y-m-d', strtotime('-2 years')); 
+    $sql = "select count(*) as cnt from tt_log where user_id in ($user_list) and timestamp > '$ts'";  
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if ($val = $res->fetchRow()) {
+        $count = $val['cnt'];
+      }
+    }
+
+    if ($count == 0)
+      return false;  // No time entries for the last 2 years.
+
+    if ($count <= 5) {
+      // We will consider a team inactive if it has 5 or less time entries made more than 1 year ago.
+      $count_last_year = 0;
+      $ts = date('Y-m-d', strtotime('-1 year')); 
+      $sql = "select count(*) as cnt from tt_log where user_id in ($user_list) and timestamp > '$ts'";  
+      $res = $mdb2->query($sql);
+      if (!is_a($res, 'PEAR_Error')) {
+        if ($val = $res->fetchRow()) {
+          $count_last_year = $val['cnt'];
+        }
+        if ($count_last_year == 0)
+          return false;  // No time entries for the last year and only a few entries before that...
+      } 
+    }
+    return true;
+  }
+  
+  // The delete function permanently deletes all data for a team.
+  static function delete($team_id) {
+       $mdb2 = getConnection();
+       
+       // Delete users.
+    $sql = "select id from tt_users where team_id = $team_id";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) return false;
+    while ($val = $res->fetchRow()) {
+      $user_id = $val['id'];
+      if (!ttUserHelper::delete($user_id))
+        return false;
+    }
+
+    // Delete tasks.
+    if (!ttTeamHelper::deleteTasks($team_id))
+      return false;
+      
+    // Delete client to project binds.
+    $sql = "delete from tt_client_project_binds where client_id in (select id from tt_clients where team_id = $team_id)";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Delete projects.
+       $sql = "delete from tt_projects where team_id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+       // Delete clients.
+       $sql = "delete from tt_clients where team_id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+         
+       // Delete invoices.
+       $sql = "delete from tt_invoices where team_id = $team_id";
+       $affected = $mdb2->exec($sql);
+       if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+       // Delete custom fields.
+       if (!ttTeamHelper::deleteCustomFields($team_id))
+         return false;
+    
+    // Delete team.
+    $sql = "delete from tt_teams where id = $team_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    return true;
+  }
+
+  // The markTasksDeleted deletes task binds and marks the tasks as deleted for a team.
+  static function markTasksDeleted($team_id) {
+       $mdb2 = getConnection();
+       $sql = "select id from tt_tasks where team_id = $team_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error')) return false;
+       while ($val = $res->fetchRow()) {
+         
+         // Delete task binds.
+         $task_id = $val['id'];
+         $sql = "delete from tt_project_task_binds where task_id = $task_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+         
+         // Mark task as deleted.
+         $sql = "update tt_tasks set status = NULL where id = $task_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;         
+       }
+       
+       return true;
+  }
+  
+  // The deleteTasks deletes all tasks and task binds for an inactive team.
+  static function deleteTasks($team_id) {
+       $mdb2 = getConnection();
+       $sql = "select id from tt_tasks where team_id = $team_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error')) return false;
+       while ($val = $res->fetchRow()) {
+         
+         // Delete task binds.
+         $task_id = $val['id'];
+         $sql = "delete from tt_project_task_binds where task_id = $task_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+         
+         // Delete task.
+         $sql = "delete from tt_tasks where id = $task_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;         
+       }
+       
+       return true;
+  }
+  
+  // The deleteCustomFields cleans up tt_custom_field_log, tt_custom_field_options and tt_custom_fields tables for an inactive team.
+  static function deleteCustomFields($team_id) {
+       $mdb2 = getConnection();
+       $sql = "select id from tt_custom_fields where team_id = $team_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error')) return false;
+       while ($val = $res->fetchRow()) {
+         $field_id = $val['id'];
+         
+         // Clean up tt_custom_field_log.
+         $sql = "delete from tt_custom_field_log where field_id = $field_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+         
+         // Clean up tt_custom_field_options.
+         $sql = "delete from tt_custom_field_options where field_id = $field_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;
+         
+         // Delete custom field.
+         $sql = "delete from tt_custom_fields where id = $field_id";
+         $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error'))
+           return false;         
+       }
+       
+       return true;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttTimeHelper.class.php b/WEB-INF/lib/ttTimeHelper.class.php
new file mode 100644 (file)
index 0000000..6b76f9f
--- /dev/null
@@ -0,0 +1,639 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('DateAndTime');
+
+// The ttTimeHelper is a class to help with time-related values.
+class ttTimeHelper {
+       
+  /* // isWeekend determines if $date falls on weekend.
+  static function isWeekend($date) {
+       $weekDay = date('w', strtotime($date));
+    return ($weekDay == WEEKEND_START_DAY || $weekDay == (WEEKEND_START_DAY + 1) % 7);
+  }*/
+
+  // isValidTime validates a value as a time string.
+  static function isValidTime($value) {
+    if (strlen($value)==0 || !isset($value)) return false;
+    
+    // 24 hour patterns.
+    if ($value == '24:00' || $value == '2400') return true;
+    
+    if (preg_match('/^([0-1]{0,1}[0-9]|[2][0-3]):?[0-5][0-9]$/', $value )) { // 0:00 - 23:59, 000 - 2359
+      return true;
+    }
+    if (preg_match('/^([0-1]{0,1}[0-9]|[2][0-4])$/', $value )) { // 0 - 24
+      return true;
+    }    
+    
+    // 12 hour patterns
+    if (preg_match('/^[1-9]\s?(am|AM|pm|PM)$/', $value)) { // 1 - 9 am
+      return true;
+    }
+    if (preg_match('/^(0[1-9]|1[0-2])\s?(am|AM|pm|PM)$/', $value)) { // 01 - 12 am
+      return true;
+    }
+    if (preg_match('/^[1-9]:?[0-5][0-9]\s?(am|AM|pm|PM)$/', $value)) { // 1:00 - 9:59 am, 100 - 959 am
+      return true;
+    }    
+    if (preg_match('/^(0[1-9]|1[0-2]):?[0-5][0-9]\s?(am|AM|pm|PM)$/', $value)) { // 01:00 - 12:59 am, 0100 - 1259 am
+      return true;     
+    }
+
+    return false;
+  }
+  
+  // isValidDuration validates a value as a time duration string (in hours and minutes).
+  static function isValidDuration($value) {
+    if (strlen($value)==0 || !isset($value)) return false;
+    
+    if ($value == '24:00' || $value == '2400') return true;
+
+    if (preg_match('/^([0-1]{0,1}[0-9]|2[0-3]):?[0-5][0-9]$/', $value )) { // 0:00 - 23:59, 000 - 2359
+      if ('00:00' == ttTimeHelper::normalizeDuration($value))
+        return false;
+      return true;
+    }
+    if (preg_match('/^([0-1]{0,1}[0-9]|2[0-4])h?$/', $value )) { // 0, 1 ... 24
+      if ('00:00' == ttTimeHelper::normalizeDuration($value))
+        return false;
+      return true;
+    }
+    if (preg_match('/^([0-1]{0,1}[0-9]|2[0-3])?[.][0-9]{1,4}h?$/', $value )) { // Decimal values like 0.5, 1.25h, ... .. 23.9999h
+      if ('00:00' == ttTimeHelper::normalizeDuration($value))
+        return false;
+      return true;
+    }
+    return false;
+  }
+  
+  // normalizeDuration - converts a valid time duration string to format 00:00.
+  static function normalizeDuration($value) {
+    $time_value = $value;
+    
+    // If we have a decimal format - convert to time format 00:00.
+    if((strpos($time_value, '.') !== false) || (strpos($time_value, 'h') !== false)) {
+      $val = floatval($time_value);
+      $mins = round($val * 60);
+      $hours = (string)((int)($mins / 60));
+      $mins = (string)($mins % 60);
+      if (strlen($hours) == 1)
+        $hours = '0'.$hours;
+      if (strlen($mins) == 1)
+        $mins = '0' . $mins;
+      return $hours.':'.$mins;
+    }
+          
+    $time_a = explode(':', $time_value);
+    $res = '';
+
+    // 0-99
+    if ((strlen($time_value) >= 1) && (strlen($time_value) <= 2) && !isset($time_a[1])) {
+      $hours = $time_a[0];
+      if (strlen($hours) == 1)
+        $hours = '0'.$hours;
+       return $hours.':00';
+    }
+
+    // 000-2359 (2400)
+    if ((strlen($time_value) >= 3) && (strlen($time_value) <= 4) && !isset($time_a[1])) {
+      if (strlen($time_value)==3) $time_value = '0'.$time_value;
+      $hours = substr($time_value,0,2);
+      if (strlen($hours) == 1)
+        $hours = '0'.$hours;
+      return $hours.':'.substr($time_value,2,2);
+    }
+
+    // 0:00-23:59 (24:00)
+    if ((strlen($time_value) >= 4) && (strlen($time_value) <= 5) && isset($time_a[1])) {
+      $hours = $time_a[0];
+      if (strlen($hours) == 1)
+        $hours = '0'.$hours;
+      return $hours.':'.$time_a[1];
+    }
+
+    return $res;
+  }
+  
+  // toMinutes - converts a time string in format 00:00 to a number of minutes.
+  static function toMinutes($value) {
+    $time_a = explode(':', $value);
+    return (int)@$time_a[1] + ((int)@$time_a[0]) * 60;
+  }
+  
+  // toDuration - calculates duration between start and finish times in 00:00 format.
+  static function toDuration($start, $finish) {
+    $duration_minutes = ttTimeHelper::toMinutes($finish) - ttTimeHelper::toMinutes($start);
+    if ($duration_minutes <= 0) return false;
+    
+    $hours = (string)((int)($duration_minutes / 60));
+    $mins = (string)($duration_minutes % 60);
+    if (strlen($hours) == 1)
+      $hours = '0'.$hours;
+    if (strlen($mins) == 1)
+      $mins = '0' . $mins;
+    return $hours.':'.$mins;
+  }
+  
+  // The to12HourFormat function converts a 24-hour time value (such as 15:23) to 12 hour format (03:23 PM).
+  static function to12HourFormat($value) {
+       if ('24:00' == $value) return '12:00 AM';
+       
+    $time_a = explode(':', $value);
+    if ($time_a[0] > 12)
+      $res = (string)((int)$time_a[0] - 12).':'.$time_a[1].' PM';
+    else if ($time_a[0] == 12)
+      $res = $value.' PM';
+    else if ($time_a[0] == 0)
+      $res = '12:'.$time_a[1].' AM';
+    else
+      $res = $value.' AM';
+    return $res;
+  }
+  
+  // The to24HourFormat function attempts to convert a string value (human readable notation of time of day)
+  // to a 24-hour time format HH:MM.
+  static function to24HourFormat($value) {
+       $res = null;
+       
+       // Algorithm: use regular expressions to find a matching pattern, starting with most popular patterns first.
+       $tmp_val = trim($value);
+
+       // 24 hour patterns.
+       if (preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $tmp_val)) { // 00:00 - 23:59
+         // We already have a 24-hour format. Just return it. 
+         $res = $tmp_val; 
+         return $res;
+       }
+       if (preg_match('/^[0-9]:[0-5][0-9]$/', $tmp_val)) { // 0:00 - 9:59
+         // This is a 24-hour format without a leading zero. Add 0 and return.
+         $res = '0'.$tmp_val; 
+         return $res;
+       }
+    if (preg_match('/^[0-9]$/', $tmp_val)) { // 0 - 9
+         // Single digit. Assuming hour number.
+         $res = '0'.$tmp_val.':00'; 
+         return $res;
+       }
+    if (preg_match('/^([01][0-9]|2[0-4])$/', $tmp_val)) { // 00 - 24
+         // Two digit hour number.
+         $res = $tmp_val.':00'; 
+         return $res;
+       }
+    if (preg_match('/^[0-9][0-5][0-9]$/', $tmp_val)) { // 000 - 959
+         // Missing colon. We'll assume the first digit is the hour, the rest is minutes.
+         $tmp_arr = str_split($tmp_val);
+         $res = '0'.$tmp_arr[0].':'.$tmp_arr[1].$tmp_arr[2]; 
+         return $res;
+       }       
+    if (preg_match('/^([01][0-9]|2[0-3])[0-5][0-9]$/', $tmp_val)) { // 0000 - 2359
+         // Missing colon. We'll assume the first 2 digits are the hour, the rest is minutes.
+         $tmp_arr = str_split($tmp_val);
+         $res = $tmp_arr[0].$tmp_arr[1].':'.$tmp_arr[2].$tmp_arr[3]; 
+         return $res;
+       }
+       // Special handling for midnight.
+    if ($tmp_val == '24:00' || $tmp_val == '2400')
+      return '24:00';  
+       
+    // 12 hour AM patterns.
+    if (preg_match('/.(am|AM)$/', $tmp_val)) {
+       
+      // The $value ends in am or AM. Strip it.
+      $tmp_val = rtrim(substr($tmp_val, 0, -2));
+      
+      // Special case to handle 12, 12:MM, and 12MM AM.
+      if (preg_match('/^12:?([0-5][0-9])?$/', $tmp_val))
+        $tmp_val = '00'.substr($tmp_val, 2);
+        
+      // We are ready to convert AM time.
+      if (preg_match('/^(0[0-9]|1[0-1]):[0-5][0-9]$/', $tmp_val)) { // 00:00 - 11:59
+           // We already have a 24-hour format. Just return it. 
+           $res = $tmp_val; 
+        return $res;
+         }
+         if (preg_match('/^[1-9]:[0-5][0-9]$/', $tmp_val)) { // 1:00 - 9:59
+           // This is a 24-hour format without a leading zero. Add 0 and return.
+           $res = '0'.$tmp_val; 
+           return $res;
+         }
+      if (preg_match('/^[1-9]$/', $tmp_val)) { // 1 - 9
+       // Single digit. Assuming hour number.
+        $res = '0'.$tmp_val.':00'; 
+           return $res;
+         }
+         if (preg_match('/^(0[0-9]|1[0-1])$/', $tmp_val)) { // 00 - 11
+           // Two digit hour number.
+           $res = $tmp_val.':00'; 
+           return $res;
+         }
+      if (preg_match('/^[1-9][0-5][0-9]$/', $tmp_val)) { // 100 - 959
+        // Missing colon. Assume the first digit is the hour, the rest is minutes.
+           $tmp_arr = str_split($tmp_val);
+           $res = '0'.$tmp_arr[0].':'.$tmp_arr[1].$tmp_arr[2]; 
+           return $res;
+         }     
+      if (preg_match('/^(0[0-9]|1[0-1])[0-5][0-9]$/', $tmp_val)) { // 0000 - 1159
+        // Missing colon. We'll assume the first 2 digits are the hour, the rest is minutes.
+           $tmp_arr = str_split($tmp_val);
+           $res = $tmp_arr[0].$tmp_arr[1].':'.$tmp_arr[2].$tmp_arr[3]; 
+           return $res;
+         }  
+    } // AM cases handling.
+
+    // 12 hour PM patterns.
+    if (preg_match('/.(pm|PM)$/', $tmp_val)) {
+       
+      // The $value ends in pm or PM. Strip it.
+      $tmp_val = rtrim(substr($tmp_val, 0, -2));
+        
+      if (preg_match('/^[1-9]$/', $tmp_val)) { // 1 - 9
+        // Single digit. Assuming hour number.
+        $hour = (string)(12 + (int)$tmp_val);
+        $res = $hour.':00';
+        return $res;
+      }
+      if (preg_match('/^((0[1-9])|(1[0-2]))$/', $tmp_val)) { // 01 - 12
+        // Double digit hour.
+        if ('12' != $tmp_val)
+          $tmp_val = (string)(12 + (int)$tmp_val);
+        $res = $tmp_val.':00';
+        return $res;
+      }        
+      if (preg_match('/^[1-9][0-5][0-9]$/', $tmp_val)) { // 100 - 959
+        // Missing colon. We'll assume the first digit is the hour, the rest is minutes.
+           $tmp_arr = str_split($tmp_val);
+           $hour = (string)(12 + (int)$tmp_arr[0]);
+           $res = $hour.':'.$tmp_arr[1].$tmp_arr[2]; 
+           return $res;
+         }
+         if (preg_match('/^(0[1-9]|1[0-2])[0-5][0-9]$/', $tmp_val)) { // 0100 - 1259
+        // Missing colon. We'll assume the first 2 digits are the hour, the rest is minutes.
+        $hour = substr($tmp_val, 0, -2);
+        $min = substr($tmp_val, 2);
+        if ('12' != $hour)
+          $hour = (string)(12 + (int)$hour); 
+        $res = $hour.':'.$min; 
+           return $res;
+         }  
+      if (preg_match('/^[1-9]:[0-5][0-9]$/', $tmp_val)) { // 1:00 - 9:59
+           $hour = substr($tmp_val, 0, -3);
+        $min = substr($tmp_val, 2);
+        $hour = (string)(12 + (int)$hour);
+        $res = $hour.':'.$min;         
+           return $res;
+         }
+      if (preg_match('/^(0[1-9]|1[0-2]):[0-5][0-9]$/', $tmp_val)) { // 01:00 - 12:59
+           $hour = substr($tmp_val, 0, -3);
+        $min = substr($tmp_val, 3);
+        if ('12' != $hour)
+          $hour = (string)(12 + (int)$hour);
+           $res = $hour.':'.$min;
+        return $res;
+         }    
+    } // PM cases handling.
+
+    return $res;
+  }
+  
+  // isValidInterval - checks if finish time is greater than start time.
+  static function isValidInterval($start, $finish) {
+    $start = ttTimeHelper::to24HourFormat($start);
+    $finish = ttTimeHelper::to24HourFormat($finish);
+    if ('00:00' == $finish) $finish = '24:00';
+    
+    $minutesStart = ttTimeHelper::toMinutes($start);
+    $minutesFinish = ttTimeHelper::toMinutes($finish);
+    if ($minutesFinish > $minutesStart)
+      return true;
+
+    return false;
+  }
+  
+  // insert - inserts a time record into log table. Does not deal with custom fields.
+  static function insert($fields)
+  {
+    $mdb2 = getConnection();
+
+    $timestamp = isset($fields['timestamp']) ? $fields['timestamp'] : '';
+    $user_id = $fields['user_id'];
+    $date = $fields['date'];
+    $start = $fields['start'];
+    $finish = $fields['finish'];
+    $duration = $fields['duration'];    
+    $client = $fields['client'];
+    $project = $fields['project'];
+    $task = $fields['task'];
+    $invoice = $fields['invoice'];
+    $note = $fields['note'];
+    $billable = $fields['billable'];
+    if (array_key_exists('status', $fields)) { // Key exists and may be NULL during migration of data.
+      $status_f = ', status';
+      $status_v = ', '.$mdb2->quote($fields['status']);
+    }
+
+    $start = ttTimeHelper::to24HourFormat($start);
+    if ($finish) {
+      $finish = ttTimeHelper::to24HourFormat($finish);
+      if ('00:00' == $finish) $finish = '24:00';
+    }
+    $duration = ttTimeHelper::normalizeDuration($duration);
+
+    if (!$timestamp) {
+      $timestamp = date('YmdHis');//yyyymmddhhmmss
+    }
+        
+    if (!$billable) $billable = 0;
+      
+    if ($duration) {
+      $sql = "insert into tt_log (timestamp, user_id, date, duration, client_id, project_id, task_id, invoice_id, comment, billable $status_f) ".
+        "values ('$timestamp', $user_id, ".$mdb2->quote($date).", '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable $status_v)";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    } else {
+      $duration = ttTimeHelper::toDuration($start, $finish);
+      if ($duration === false) $duration = 0;
+      if (!$duration && ttTimeHelper::getUncompleted($user_id)) return false;
+
+      $sql = "insert into tt_log (timestamp, user_id, date, start, duration, client_id, project_id, task_id, invoice_id, comment, billable $status_f) ".
+        "values ('$timestamp', $user_id, ".$mdb2->quote($date).", '$start', '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable $status_v)";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+
+    $id = $mdb2->lastInsertID('tt_log', 'id');
+    return $id;
+  }
+  
+  // update - updates a record in log table. Does not update its custom fields.
+  static function update($fields)
+  {
+    $mdb2 = getConnection();
+
+    $id = $fields['id'];
+    $date = $fields['date'];
+    $user_id = $fields['user_id'];
+    $client = $fields['client'];
+    $project = $fields['project'];
+    $task = $fields['task'];
+    $start = $fields['start'];
+    $finish = $fields['finish'];
+    $duration = $fields['duration'];
+    $note = $fields['note'];
+    $billable = $fields['billable'];
+
+    $start = ttTimeHelper::to24HourFormat($start);
+    $finish = ttTimeHelper::to24HourFormat($finish);
+    if ('00:00' == $finish) $finish = '24:00';
+    $duration = ttTimeHelper::normalizeDuration($duration);
+
+    if (!$billable) $billable = 0;
+    if ($start) $duration = '';
+
+    if ($duration) {
+      $sql = "UPDATE tt_log set start = NULL, duration = '$duration', client_id = ".$mdb2->quote($client).", project_id = ".$mdb2->quote($project).", task_id = ".$mdb2->quote($task).", ".
+        "comment = ".$mdb2->quote($note).", billable = $billable, date = '$date' WHERE id = $id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    } else {
+      $duration = ttTimeHelper::toDuration($start, $finish);
+      if ($duration === false)
+        $duration = 0;
+      $uncompleted = ttTimeHelper::getUncompleted($user_id);
+      if (!$duration && $uncompleted && ($uncompleted['id'] != $id))
+        return false;
+
+      $sql = "UPDATE tt_log SET start = '$start', duration = '$duration', client_id = ".$mdb2->quote($client).", project_id = ".$mdb2->quote($project).", task_id = ".$mdb2->quote($task).", ".
+        "comment = ".$mdb2->quote($note).", billable = $billable, date = '$date' WHERE id = $id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+    return true;
+  }
+  
+  // delete - deletes a record from tt_log table and its associated custom field values.
+  static function delete($id, $user_id) {
+    $mdb2 = getConnection();
+
+    $sql = "update tt_log set status = NULL where id = $id and user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    $sql = "update tt_custom_field_log set status = NULL where log_id = $id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+       
+    return true;
+  }
+  
+  // getTimeForDay - gets total time for a user for a specific date.
+  static function getTimeForDay($user_id, $date) {
+    $mdb2 = getConnection();
+
+    $sql = "select sum(time_to_sec(duration)) as sm from tt_log where user_id = $user_id and date = '$date' and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return sec_to_time_fmt_hm($val['sm']);
+    }
+    return false;
+  }
+  
+  // getTimeForWeek - gets total time for a user for a given week.
+  static function getTimeForWeek($user_id, $date) {
+    import('Period');
+    $mdb2 = getConnection();
+
+    $period = new Period(INTERVAL_THIS_WEEK, $date);
+    $sql = "select sum(time_to_sec(duration)) as sm from tt_log where user_id = $user_id and date >= '".$period->getBeginDate(DB_DATEFORMAT)."' and date <= '".$period->getEndDate(DB_DATEFORMAT)."' and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return sec_to_time_fmt_hm($val['sm']);
+    }
+    return 0;
+  }
+  
+  // getUncompleted - retrieves an uncompleted record for user, if one exists.
+  static function getUncompleted($user_id) {
+    $mdb2 = getConnection();
+
+    $sql = "select id from tt_log  
+      where user_id = $user_id and start is not null and time_to_sec(duration) = 0 and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if (!$res->numRows()) {
+        return false;
+      }
+      if ($val = $res->fetchRow()) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  // overlaps - determines if a record overlaps with an already existing record.
+  //
+  // Parameters:
+  //   $user_id - user id for whom to determine overlap
+  //   $date - date
+  //   $start - new record start time
+  //   $finish - new record finish time, may be null
+  //   $record_id - optional record id we may be editing, excluded from overlap set
+  static function overlaps($user_id, $date, $start, $finish, $record_id = null) {
+    $mdb2 = getConnection();
+    
+    $start = ttTimeHelper::to24HourFormat($start);
+    if ($finish) {
+      $finish = ttTimeHelper::to24HourFormat($finish);
+      if ('00:00' == $finish) $finish = '24:00';
+    }
+    // Handle these 3 overlap situations:
+    // - start time in existing record
+    // - end time in existing record
+    // - record fully encloses existing record
+    $sql = "select id from tt_log  
+      where user_id = $user_id and date = ".$mdb2->quote($date)."
+      and start is not null and duration is not null and status = 1 and (
+      (cast(".$mdb2->quote($start)." as time) >= start and cast(".$mdb2->quote($start)." as time) < addtime(start, duration))";
+    if ($finish) {
+      $sql .= " or (cast(".$mdb2->quote($finish)." as time) <= addtime(start, duration) and cast(".$mdb2->quote($finish)." as time) > start)
+      or (cast(".$mdb2->quote($start)." as time) < start and cast(".$mdb2->quote($finish)." as time) > addtime(start, duration))";
+    }
+    $sql .= ")";
+    if ($record_id) {
+      $sql .= " and id <> $record_id";
+    }
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if (!$res->numRows()) {
+        return false;
+      }
+      if ($val = $res->fetchRow()) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  // getRecord - retrieves a time record identified by its id.
+  static function getRecord($id, $user_id) {
+       global $user;
+       $sql_time_format = "'%k:%i'"; //  24 hour format.
+       if ('%I:%M %p' == $user->time_format)
+         $sql_time_format = "'%h:%i %p'"; // 12 hour format for MySQL TIME_FORMAT function.
+       
+    $mdb2 = getConnection();
+
+    $sql = "select l.id as id, l.timestamp as timestamp, TIME_FORMAT(l.start, $sql_time_format) as start,
+      TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), $sql_time_format) as finish,
+      TIME_FORMAT(l.duration, '%k:%i') as duration,
+      p.name as project_name, t.name as task_name, l.comment, l.client_id, l.project_id, l.task_id, l.invoice_id, l.billable, l.date
+      from tt_log l
+      left join tt_projects p on (p.id = l.project_id)
+      left join tt_tasks t on (t.id = l.task_id)
+      where l.id = $id and l.user_id = $user_id and l.status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if (!$res->numRows()) {
+        return false;
+      }
+      if ($val = $res->fetchRow()) {
+        return $val;
+      }
+    }
+    return false;
+  }
+  
+  // getAllRecords - returns all time records for a certain user.
+  static function getAllRecords($user_id) {
+    $result = array();
+
+    $mdb2 = getConnection();
+
+    $sql = "select l.id, l.timestamp, l.user_id, l.date, TIME_FORMAT(l.start, '%k:%i') as start,
+      TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish,
+      TIME_FORMAT(l.duration, '%k:%i') as duration,
+      l.client_id, l.project_id, l.task_id, l.invoice_id, l.comment, l.billable, l.status
+      from tt_log l where l.user_id = $user_id order by l.id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    } else return false;
+
+    return $result;
+  }
+  
+  // getRecords - returns time records for a user for a given date.
+  static function getRecords($user_id, $date) {
+       global $user;
+       $sql_time_format = "'%k:%i'"; //  24 hour format.
+       if ('%I:%M %p' == $user->time_format)
+         $sql_time_format = "'%h:%i %p'"; // 12 hour format for MySQL TIME_FORMAT function.    
+               
+    $result = array();
+    $mdb2 = getConnection();
+
+    $client_field = null;
+    if (in_array('cl', explode(',', $user->plugins)))
+      $client_field = ", c.name as client";
+    
+    $left_joins = " left join tt_projects p on (l.project_id = p.id)".
+      " left join tt_tasks t on (l.task_id = t.id)";
+    if (in_array('cl', explode(',', $user->plugins)))
+      $left_joins .= " left join tt_clients c on (l.client_id = c.id)";
+
+    $sql = "select l.id as id, TIME_FORMAT(l.start, $sql_time_format) as start,
+      TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), $sql_time_format) as finish,
+      TIME_FORMAT(l.duration, '%k:%i') as duration, p.name as project, t.name as task, l.comment, l.billable, l.invoice_id $client_field
+      from tt_log l
+      $left_joins
+      where l.date = '$date' and l.user_id = $user_id and l.status = 1
+      order by l.start, l.id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        if($val['duration']=='0:00')
+          $val['finish'] = '';
+        $result[] = $val;
+      }
+    } else return false;
+
+    return $result;
+  }
+
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttUser.class.php b/WEB-INF/lib/ttUser.class.php
new file mode 100644 (file)
index 0000000..2f71ad3
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+class ttUser {
+  var $login = null;        // User login.
+  var $name = null;         // User name.
+  var $id = null;           // User id.
+  var $team_id = null;      // Team id.
+  var $role = null;                    // User role (user, client, comanager, manager, admin).
+  var $client_id = null;       // Client id for client user role.
+  var $behalf_id = null;    // User id, on behalf of whom we are working.
+  var $behalf_name = null;  // User name, on behalf of whom we are working.
+  var $email = null;        // User email.
+  var $lang = null;         // Language.
+  var $decimal_mark = null; // Decimal separator.
+  var $date_format = null;  // Date format.
+  var $time_format = null;  // Time format.
+  var $week_start = 0;      // Week start day.
+  var $tracking_mode = 0;   // Tracking mode.
+  var $record_type = 0;     // Record type (duration vs start and finish, or both).
+  var $currency = null;     // Currency.
+  var $plugins = null;      // Comma-separated list of enabled plugins.
+  var $team = null;            // Team name.
+  var $custom_logo = 0;     // Whether to use a custom logo for team.
+  var $address = null;         // Address for invoices.
+  var $lock_interval = 0;      // Lock interval in days for time records.
+  var $rights = 0;          // A mask of user rights.
+  
+  // Constructor.
+  function ttUser($login, $id = null) {
+       if (!$login && !$id) {
+      // nothing to initialize
+         return;
+       }
+       
+    $mdb2 = getConnection();
+    
+    $sql = "SELECT u.id, u.login, u.name, u.team_id, u.role, u.client_id, u.email, t.name as team_name, 
+      t.address, t.currency, t.locktime, t.lang, t.decimal_mark, t.date_format, t.time_format, t.week_start, t.tracking_mode, t.record_type, t.plugins, t.custom_logo
+      FROM tt_users u LEFT JOIN tt_teams t ON (u.team_id = t.id) WHERE ";
+       if ($id)
+         $sql .= "u.id = $id";
+    else
+         $sql .= "u.login = ".$mdb2->quote($login);
+       $sql .= " AND u.status = 1";
+                       
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) {
+      return;
+      //die($res->getMessage());
+    }
+
+    $val = $res->fetchRow();
+    if ($val['id'] > 0) {
+      $this->login = $val['login'];
+      $this->name = $val['name'];
+      $this->id = $val['id'];
+      $this->team_id = $val['team_id'];
+      $this->role = $val['role'];
+      $this->client_id = $val['client_id'];
+      $this->email = $val['email'];
+      $this->lang = $val['lang'];
+      $this->decimal_mark = $val['decimal_mark'];
+      $this->date_format = $val['date_format'];
+      $this->time_format = $val['time_format'];
+      $this->week_start = $val['week_start'];
+      $this->tracking_mode = $val['tracking_mode'];
+      $this->record_type = $val['record_type'];
+      $this->team = $val['team_name'];
+      $this->address = $val['address'];
+      $this->currency = $val['currency'];
+      $this->plugins = $val['plugins'];
+      $this->custom_logo = $val['custom_logo'];
+      $this->lock_interval = $val['locktime'];
+      
+      // Set "on behalf" id and name.
+      if (isset($_SESSION['behalf_id'])) {
+          $this->behalf_id = $_SESSION['behalf_id'];
+          $this->behalf_name = $_SESSION['behalf_name'];
+      }
+      
+      // Set user rights.
+      if ($this->role == ROLE_USER) {
+       $this->rights = right_data_entry|right_view_charts|right_view_reports;
+      } else if ($this->role == ROLE_CLIENT) {
+       $this->rights = right_view_reports|right_view_invoices; // TODO: how about right_view_charts, too?
+      } else if ($this->role == ROLE_COMANAGER) {
+       $this->rights = right_data_entry|right_view_charts|right_view_reports|right_view_invoices|right_manage_team;
+      } else if ($this->role == ROLE_MANAGER) {
+       $this->rights = right_data_entry|right_view_charts|right_view_reports|right_view_invoices|right_manage_team|right_assign_roles|right_export_team;
+      } else if ($this->role == ROLE_SITE_ADMIN) {
+       $this->rights = right_administer_site;
+      }
+    }
+  }
+  
+  // The getActiveUser returns user id on behalf of whom current user is operating.
+  function getActiveUser() {
+    return ($this->behalf_id ? $this->behalf_id : $this->id);
+  }
+  
+  // isAdmin - determines whether current user is admin (has right_administer_site).
+  function isAdmin() {
+    return (right_administer_site & $this->role);
+  }
+
+  // isManager - determines whether current user is team manager.
+  function isManager() {
+    return (ROLE_MANAGER == $this->role);
+  }
+
+  // isCoManager - determines whether current user is team comanager.
+  function isCoManager() {
+    return (ROLE_COMANAGER == $this->role);
+  }
+  
+  // isClient - determines whether current user is a client.
+  function isClient() {
+    return (ROLE_CLIENT == $this->role);
+  }
+  
+  // canManageTeam - determines whether current user is manager or co-manager.
+  function canManageTeam() {
+    return (right_manage_team & $this->role);
+  }
+  
+  // getAssignedProjects - returns an array of assigned projects.
+  function getAssignedProjects()
+  {
+    $result = array();
+    $mdb2 = getConnection();
+    
+    // Do a query with inner join to get assigned projects.
+    $sql = "select p.id, p.name, p.description, p.tasks, upb.rate from tt_projects p
+      inner join tt_user_project_binds upb on (upb.user_id = ".$this->getActiveUser()." and upb.project_id = p.id and upb.status = 1)
+      where p.team_id = $this->team_id and p.status = 1 order by p.name";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $result[] = $val;
+      }
+    }
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/lib/ttUserHelper.class.php b/WEB-INF/lib/ttUserHelper.class.php
new file mode 100644 (file)
index 0000000..a1c89c0
--- /dev/null
@@ -0,0 +1,457 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+import('ttTeamHelper');
+
+// Class ttUserHelper contains helper functions for operations with users.
+class ttUserHelper {
+       
+  // The getUserDetails function returns user details.
+  static function getUserDetails($user_id) {
+    $result = array();
+    $mdb2 = getConnection();
+
+    $sql =  "select * from tt_users where id = $user_id";
+    $res = $mdb2->query($sql);
+    
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val;
+    }
+    return false;
+  }
+       
+  // The getUserName function returns user name.
+  static function getUserName($user_id) {
+       $mdb2 = getConnection();
+    
+    $sql = "select name from tt_users where id = $user_id and (status = 1 or status = 0)";
+    $res = $mdb2->query($sql);
+
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val['name'];
+    }
+    return false;
+  }
+
+  // The getUserByLogin function obtains data for a user, who is identified by login.
+  static function getUserByLogin($login) {
+    $mdb2 = getConnection();
+
+    $sql = "select id, name from tt_users where login = ".$mdb2->quote($login)." and (status = 1 or status = 0)";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      if ($val = $res->fetchRow()) {
+        return $val;
+      }
+    }
+    return false;
+  }
+
+  // The getUserByEmail function is a helper function that tries to obtain user details identified by email.
+  // This function works only when one such active user exists.
+  static function getUserByEmail($email) {
+    $mdb2 = getConnection();
+
+    $sql = "select login, count(*) as cnt from tt_users where email = ".$mdb2->quote($email)." and status = 1 group by email";
+    $res = $mdb2->query($sql);
+    
+    if (is_a($res, 'PEAR_Error'))    
+      return false;
+      
+    $val = $res->fetchRow();
+    if (1 <> $val['cnt']) {
+      // We either have no users or multiple users with a given email.
+      return false;
+    }
+    return $val['login'];
+  }
+    
+  // The getUserIdByTmpRef obtains user id from a temporary reference (used for password resets).
+  static function getUserIdByTmpRef($ref) {
+    $mdb2 = getConnection();
+    
+    $sql = "select user_id from tt_tmp_refs where ref = ".$mdb2->quote($ref);
+    $res = $mdb2->query($sql);
+
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      return $val['user_id'];
+    }
+    return false;
+  }
+
+  // insert - inserts a user into database.
+  static function insert($fields, $hash = true) {
+       $mdb2 = getConnection();
+
+    $password = $mdb2->quote($fields['password']);
+    if($hash)
+      $password = 'md5('.$password.')';
+    $email = isset($fields['email']) ? $fields['email'] : '';
+    $team_id = (int) $fields['team_id'];
+    $role = (int) $fields['role'];
+    $rate = str_replace(',', '.', isset($fields['rate']) ? $fields['rate'] : 0);
+    if($rate == '')
+      $rate = 0;
+    if (array_key_exists('status', $fields)) { // Key exists and may be NULL during migration of deleted acounts.
+      $status_f = ', status';
+      $status_v = ', '.$mdb2->quote($fields['status']);
+    }
+
+    $sql = "insert into tt_users (name, login, password, team_id, role, client_id, rate, email $status_f) values (".
+      $mdb2->quote($fields['name']).", ".$mdb2->quote($fields['login']).
+      ", $password, $team_id, $role, ".$mdb2->quote($fields['client_id']).", $rate, ".$mdb2->quote($email)." $status_v)";
+    $affected = $mdb2->exec($sql);
+    
+    // Now deal with project assignment.
+    if (!is_a($affected, 'PEAR_Error')) {
+      $sql = "SELECT LAST_INSERT_ID() AS last_id";
+      $res = $mdb2->query($sql);
+      $val = $res->fetchRow();
+      $last_id = $val['last_id'];
+
+      $projects = isset($fields['projects']) ? $fields['projects'] : array();
+      if (count($projects) > 0) {
+        // We have at least one project assigned. Insert corresponding entries in tt_user_project_binds table.
+        foreach($projects as $p) {
+          if(!isset($p['rate']))
+            $p['rate'] = 0;
+          else
+            $p['rate'] = str_replace(',', '.', $p['rate']);
+
+          $sql = "insert into tt_user_project_binds (project_id, user_id, rate, status) values(".$p['id'].",".$last_id.",".$p['rate'].", 1)";
+          $affected = $mdb2->exec($sql);
+        }
+      }
+      return $last_id;
+    }
+    return false;
+  }
+  
+  // update - updates a user in database.
+  static function update($user_id, $fields) {
+       global $user;
+    $mdb2 = getConnection();
+    
+    // Check parameters.
+    if (!$user_id || !isset($fields['login']))
+      return false;
+
+    // Prepare query parts.
+    if (isset($fields['password']))
+      $pass_part = ', password = md5('.$mdb2->quote($fields['password']).')';
+    if (right_assign_roles & $user->rights) {
+      if (isset($fields['role'])) {
+        $role = (int) $fields['role'];
+        $role_part = ", role = $role";
+      }
+      if (array_key_exists('client_id', $fields)) // Could be NULL.
+        $client_part = ", client_id = ".$mdb2->quote($fields['client_id']);
+    }
+      
+    if (array_key_exists('rate', $fields)) {
+      $rate = str_replace(',', '.', isset($fields['rate']) ? $fields['rate'] : 0);
+      if($rate == '') $rate = 0;
+      $rate_part = ", rate = ".$mdb2->quote($rate); 
+    }
+    
+    if (isset($fields['status'])) {
+      $status = (int) $fields['status']; 
+      $status_part = ", status = $status";
+    }
+    
+    $sql = "update tt_users set login = ".$mdb2->quote($fields['login']).
+      "$pass_part, name = ".$mdb2->quote($fields['name']).
+      "$role_part $client_part $rate_part $status_part, email = ".$mdb2->quote($fields['email']).
+      " where id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error')) return false;
+    
+    if (array_key_exists('projects', $fields)) {
+      // Deal with project assignments.
+      // Note: we cannot simply delete old project binds and insert new ones because it screws up reporting
+      // (when looking for cost while entries for de-assigned projects exist).
+      // Therefore, we must iterate through all projects and only delete the binds when no time entries are present,
+      // otherwise de-activate the bind (set its status to inactive). This will keep the bind
+      // and its rate in database for reporting.
+
+      $all_projects = ttTeamHelper::getAllProjects($user->team_id);
+      $assigned_projects = isset($fields['projects']) ? $fields['projects'] : array();
+      
+      foreach($all_projects as $p) {
+        // Determine if a project is assigned.
+        $assigned = false;
+        $project_id = $p['id'];
+        $rate = '0.00';
+        if (count($assigned_projects) > 0) {
+          foreach ($assigned_projects as $ap) {
+            if ($project_id == $ap['id']) {
+              $assigned = true;
+              if ($ap['rate']) {
+                $rate = $ap['rate'];
+                $rate = str_replace(",",".",$rate);
+              }
+              break;
+            }
+          }
+        }
+
+        if (!$assigned) {
+          ttUserHelper::deleteBind($user_id, $project_id);
+        } else {
+          // Here we need to either update or insert new tt_user_project_binds record.
+          // Determine if a record exists.
+          $sql = "select id from tt_user_project_binds where user_id = $user_id and project_id = $project_id";
+          $res = $mdb2->query($sql);
+          if (is_a($res, 'PEAR_Error')) die ($res->getMessage());
+          if ($val = $res->fetchRow()) {
+            // Record exists. Update it.
+            $sql = "update tt_user_project_binds set status = 1, rate = $rate where id = ".$val['id'];
+            $affected = $mdb2->exec($sql);
+            if (is_a($affected, 'PEAR_Error')) die ($affected->getMessage());
+          } else {
+            // Record does not exist. Insert it.
+            ttUserHelper::insertBind($user_id, $project_id, $rate, 1);
+          }
+        }
+      }
+    }
+    return true;
+  }
+  // markDeleted - marks user and its associated things as deleted.
+  static function markDeleted($user_id) {
+       $mdb2 = getConnection();
+    global $user;
+       
+       // Preliminary checks. Only managers, co-managers, and admin can do this.
+       if (!$user->canManageTeam() && !$user->isAdmin())
+      return false;
+
+    // Tho logic is different depending on who is doint the operation.
+    // Co-manage and admin - mark user deleted.
+    // Manager - mark user deleted. If manager is the only account in team, mark team items deleted.
+
+    // admin part.
+    if ($user->isAdmin()) {
+      // Mark user binds as deleted.
+      $sql = "update tt_user_project_binds set status = NULL where user_id = $user_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+     
+      // Mark user as deleted.
+      $sql = "update tt_users set status = NULL where id = $user_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+
+    } else if ($user->isCoManager()) {
+      // Mark user binds as deleted.
+      $sql = "update tt_user_project_binds set status = NULL where user_id = $user_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+
+      // Mark user as deleted.
+      $sql = "update tt_users set status = NULL where id = $user_id and team_id = ".$user->team_id;
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+
+    } else if ($user->isManager()) {
+      $user_count = ttTeamHelper::getUserCount($user->team_id);        
+
+      // Marking deleted a manager with active users is not allowed.
+         if (($user_id == $user->id) && ($user_count > 1))
+        return false;          
+
+      if (1 == $user_count) {
+       // Mark tasks deleted.
+        if (!ttTeamHelper::markTasksDeleted($user->team_id))
+          return false;
+          
+        // Mark projects deleted.
+        $sql = "update tt_projects set status = NULL where team_id = $user->team_id";
+        $affected = $mdb2->exec($sql);
+        if (is_a($affected, 'PEAR_Error'))
+          return false;
+   
+           // Mark clients deleted.
+        $sql = "update tt_clients set status = NULL where team_id = $user->team_id";
+           $affected = $mdb2->exec($sql);
+           if (is_a($affected, 'PEAR_Error'))
+             return false;
+
+        // Mark custom fields deleted.
+        $sql = "update tt_custom_fields set status = NULL where team_id = $user->team_id";
+           $affected = $mdb2->exec($sql);
+           if (is_a($affected, 'PEAR_Error'))
+             return false;
+        // Mark team deleted.
+           $sql = "update tt_teams set status = NULL where id = $user->team_id";
+           $affected = $mdb2->exec($sql);
+           if (is_a($affected, 'PEAR_Error'))
+             return false;
+
+      }   
+    
+      // Mark user binds as deleted.
+      $sql = "update tt_user_project_binds set status = NULL where user_id = $user_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+     
+      // Mark user as deleted.
+      $sql = "update tt_users set status = NULL where id = $user_id and team_id = ".$user->team_id;
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+        return false;
+    }
+      
+    return true;
+  }
+  
+  // The delete function permanently deletes a user and all associated data.
+  static function delete($user_id) {
+    $mdb2 = getConnection();
+
+    // Delete custom field log entries for user, if we have them.
+       $sql = "delete from tt_custom_field_log where log_id in
+        (select id from tt_log where user_id = $user_id)";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Delete log entries for user.
+    $sql = "delete from tt_log where user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    // Delete expense items for user.
+    $sql = "delete from tt_expense_items where user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Delete user binds.
+    $sql = "delete from tt_user_project_binds where user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Clean up tt_config table.
+    $sql = "delete from tt_config where user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false; 
+
+    // Clean up tt_fav_reports table.
+       $sql = "delete from tt_fav_reports where user_id = $user_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    // Delete user.
+    $sql = "delete from tt_users where id = $user_id";
+    $affected = $mdb2->exec($sql);    
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+      
+    return true;
+  }
+    
+  // The saveTmpRef saves a temporary reference for user that is used to reset user password.
+  static function saveTmpRef($ref, $user_id) {
+    $mdb2 = getConnection();
+    
+    $sql = "delete from tt_tmp_refs where timestamp + 86400 < now()";
+    $affected = $mdb2->exec($sql);
+
+    $sql = "insert into tt_tmp_refs (ref, user_id) values(".$mdb2->quote($ref).", $user_id)";
+    $affected = $mdb2->exec($sql);
+  }
+  
+  // The setPassword function updates password for user.
+  static function setPassword($user_id, $password) {
+    $mdb2 = getConnection();
+      
+    $sql = "update tt_users set password = md5(".$mdb2->quote($password).") where id = $user_id";
+    $affected = $mdb2->exec($sql);
+    
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // insertBind - inserts a user to project bind into tt_user_project_binds table.
+  static function insertBind($user_id, $project_id, $rate, $status) {
+    $mdb2 = getConnection();
+    
+    $sql = "insert into tt_user_project_binds (user_id, project_id, rate, status)
+      values($user_id, $project_id, ".$mdb2->quote($rate).", $status)";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // deleteBind - deactivates user to project bind when time entries exist,
+  // otherwise deletes it entirely. 
+  static function deleteBind($user_id, $project_id) {
+    $mdb2 = getConnection();
+    
+    $sql = "select count(*) as cnt from tt_log where 
+      user_id = $user_id and project_id = $project_id and status = 1";
+    $res = $mdb2->query($sql);
+    if (is_a($res, 'PEAR_Error')) die ($res->getMessage());
+    
+    $count = 0;
+    $val = $res->fetchRow();
+    $count = $val['cnt'];
+    
+    if ($count > 0) {
+      // Deactivate user bind.
+      $sql = "select id from tt_user_project_binds where user_id = $user_id and project_id = $project_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error')) die ($res->getMessage());
+       if ($val = $res->fetchRow()) {
+         $sql = "update tt_user_project_binds set status = 0 where id = ".$val['id'];
+                $affected = $mdb2->exec($sql);
+         if (is_a($affected, 'PEAR_Error')) die ($res->getMessage());
+       }
+    } else {
+      // Delete user bind.
+      $sql = "delete from tt_user_project_binds where user_id = $user_id and project_id = $project_id";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error')) die ($res->getMessage());
+    } 
+    return true;
+  }
+}
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/ca.lang.php b/WEB-INF/resources/ca.lang.php
new file mode 100644 (file)
index 0000000..70657b1
--- /dev/null
@@ -0,0 +1,386 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Català';
+$i18n_months = array('Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre');
+$i18n_weekdays = array('Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte');
+$i18n_weekdays_short = array('Dg', 'Dl', 'Dm', 'Dc', 'Dj', 'Dv', 'Ds');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/16', '02/20', '03/29', '07/04', '09/04', '10/09', '11/11', '11/23', '12/25');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'Iniciar sessió',
+'menu.logout' => 'Finalitzar sessió',
+'menu.feedback' => 'Retroalimentació',
+'menu.help' => 'Ajuda',
+// Note to translators: menu.create_team needs to be translated more accurately.
+'menu.create_team' => 'Crear un nou compte de manejador',
+'menu.edit_profile' => 'Editar perfil',
+'menu.time' => 'El meu temps',
+'menu.reports' => 'Informes',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'Projectes',
+'menu.activities' => 'Activitats',
+'menu.people' => 'Persones',
+'menu.teams' => 'Equips',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'Clients',
+// Note to translators: menu.options needs to be translated.
+// 'menu.options' => 'options',
+'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'Error de la Base de Dades',
+'error.field' => 'Dada "{0}" incorrecta',
+'error.empty' => 'L\\\'Arxiu "{0}" està buit',
+'error.not_equal' => 'L\\\'Arxiu "{0}" no és igual al arxiu "{1}"',
+'error.interval' => 'Interval incorrecte',
+'error.project' => 'Sel·leccionar Projecte',
+'error.activity' => 'Sel·leccionar Actividat',
+'error.auth' => 'Usuari o parula de pas incorrecta',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'Ja existeix un projecte amb aquest nom',
+'error.activity_exists' => 'Ja existeix una activitat amb aquest nom',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: error.no_login needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'No existeix cap usuari amb aquest e-mail',
+'error.upload' => 'Error pujant l\\\'arxiu',
+// Note to translators: the 5 strings below are missing from the translation and must be added.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'Iniciar sessió',
+'button.now' => 'Ara',
+// 'button.set' => 'Establir',
+'button.save' => 'Guardar',
+'button.delete' => 'Eliminar',
+'button.cancel' => 'Cancel·lar',
+'button.submit' => 'Enviar',
+'button.add_user' => 'Agregar usuari ',
+'button.add_project' => 'Agregar projecte',
+'button.add_activity' => 'Agregar activitat',
+'button.add_client' => 'Agregar client',
+'button.add' => 'Agregar',
+'button.generate' => 'Generar',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'Anar',
+'button.send' => 'Enviar',
+'button.send_by_email' => 'Enviar per correu',
+'button.save_as_new' => 'Guardar com a nou',
+'button.create_team' => 'Crear grup',
+'button.export' => 'Exportar grup',
+'button.import' => 'Importar grup',
+'button.apply' => 'Aplicar',
+
+// labels for controls on various forms
+// TODO: translate label.team_name.
+// 'label.team_name' => 'team name',
+'label.currency' => 'Moneda',
+// TODO: translate these 2 strings below.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.name' => 'Nom',
+
+'label.password' => 'Paraula de pas',
+'label.confirm_password' => 'Confirmar paraula de pas',
+'label.email' => 'e-mail',
+
+"form.filter.project" => 'Projecte',
+"form.filter.filter" => 'Report favorit',
+"form.filter.filter_new" => 'Guardar com a favorit',
+
+// login form attributes
+"form.login.title" => 'Sessió iniciada',
+"form.login.login" => 'e-mail',
+
+// password reminder form attributes
+"form.fpass.title" => 'Restablir paraula de pas',
+"form.fpass.login" => 'e-mail',
+"form.fpass.send_pass_str" => 's\\\'ha enviat la petició de restablir paraula de pas',
+"form.fpass.send_pass_subj" => 'Sol·licitud de restabliment de la paraula de pas de Anuko Time Tracker',
+// Note to translators: this string needs to be translated.
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "Per restablir la paraula de pas, si us plau escrigui-la i faci clic en guardar",
+
+// administrator form
+"form.admin.title" => 'Administrador',
+"form.admin.duty_text" => 'Crear un nou grup, creant un nou compte del manejador de l\\\'equip.<br>També pot importar dades de grups, d\\\'un arxiu xml d\\\'un altre servidor Anuko Time Tracker.(No està permès col·lisions de e-mail).',
+
+"form.admin.change_pass" => 'Canviar la paraula de pas de l\\\'administrador de compte',
+"form.admin.profile.title" => 'Grups',
+"form.admin.profile.noprofiles" => 'La seva base de dades està buida. Iniciï sessió com a administrador i creï un nou grup.',
+"form.admin.profile.comment" => 'Eliminar grup',
+"form.admin.profile.th.id" => 'Identificació',
+"form.admin.profile.th.name" => 'Nom',
+"form.admin.profile.th.edit" => 'Modificar',
+"form.admin.profile.th.del" => 'Eliminar',
+"form.admin.profile.th.active" => 'Actiu',
+"form.admin.lock.period" => 'interval de tancament en dies',
+
+// my time form attributes
+"form.mytime.title" => 'El meu temps',
+"form.mytime.edit_title" => 'Modificant l\\\'historial de temps',
+"form.mytime.del_str" => 'Eliminant l\\\'historial de temps',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'Data',
+"form.mytime.project" => 'Projecte',
+"form.mytime.activity" => 'Activitat',
+"form.mytime.start" => 'Inici',
+"form.mytime.finish" => 'Fi',
+"form.mytime.duration" => 'Durada',
+"form.mytime.note" => 'Nota',
+"form.mytime.behalf" => 'Treball del dia per a',
+"form.mytime.daily" => 'Treball diari',
+"form.mytime.total" => 'Hores totals: ',
+"form.mytime.th.project" => 'Projecte',
+"form.mytime.th.activity" => 'Activitat',
+"form.mytime.th.start" => 'Inici',
+"form.mytime.th.finish" => 'Fi',
+"form.mytime.th.duration" => 'Durada',
+"form.mytime.th.note" => 'Nota',
+"form.mytime.th.edit" => 'Modificar',
+"form.mytime.th.delete" => 'Eliminar',
+"form.mytime.del_yes" => 'L\\\'historial de temps s\\\'ha eliminat amb èxit',
+"form.mytime.no_finished_rec" => 'Aquest historial s\\\'ha guardat únicament amb l\\\'hora d\\\'inici. Aixó no és un error. Finalitzi sessió si ho necessita.',
+"form.mytime.billable" => 'facturable',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'Crear un nou compte de manejador',
+"form.profile.edit_title" => 'Modificant perfil',
+"form.profile.name" => 'Nom',
+// Note to translators: a few strings in this section a missing. Please check against the English file.
+
+// people form attributes
+"form.people.ppl_str" => 'Persones',
+"form.people.createu_str" => 'Creant nou usuari',
+"form.people.edit_str" => 'Modificant usuari',
+"form.people.del_str" => 'Eliminant usuari',
+"form.people.th.name" => 'Nom',
+"form.people.th.email" => 'e-mail',
+"form.people.th.role" => 'Rol',
+"form.people.th.edit" => 'Modificar',
+"form.people.th.del" => 'Eliminar',
+"form.people.th.status" => 'Estat',
+"form.people.th.project" => 'Projecte',
+"form.people.th.rate" => 'Taxa',
+"form.people.manager" => 'Manejador',
+"form.people.comanager" => 'Auxiliar del manejador',
+"form.people.empl" => 'Usuari',
+"form.people.name" => 'Nom',
+
+"form.people.rate" => 'Taxa per defecte en hores',
+"form.people.comanager" => 'Auxiliar del manejador',
+"form.people.projects" => 'Projectes',
+
+// projects form attributes
+"form.project.proj_title" => 'Projectes',
+"form.project.edit_str" => 'Modificant projecte',
+"form.project.add_str" => 'Agregant nou projecte',
+"form.project.del_str" => 'Eliminant projecte',
+"form.project.th.name" => 'Nom',
+"form.project.th.edit" => 'Modificar',
+"form.project.th.del" => 'Eliminar',
+"form.project.name" => 'Nom',
+
+// activities form attributes
+"form.activity.act_title" => 'Activitats',
+"form.activity.add_title" => 'Agregant nova activitat',
+"form.activity.edit_str" => 'Modificant activitat',
+"form.activity.del_str" => 'Eliminant activitat',
+"form.activity.name" => 'Nom',
+"form.activity.project" => 'Projecte',
+"form.activity.th.name" => 'Nom',
+"form.activity.th.project" => 'Projecte',
+"form.activity.th.edit" => 'Editar',
+"form.activity.th.del" => 'Eliminar',
+
+// report attributes
+"form.report.title" => 'Reports',
+"form.report.from" => 'Data d\\\'inici',
+"form.report.to" => 'Data de fi',
+"form.report.groupby_user" => 'Usuari',
+"form.report.groupby_project" => 'Projecte',
+"form.report.groupby_activity" => 'Activitat',
+"form.report.duration" => 'Durada',
+"form.report.start" => 'Inici',
+"form.report.activity" => 'Activitat',
+"form.report.show_idle" => 'Mostrar ausent',
+"form.report.finish" => 'Fi',
+"form.report.note" => 'Nota',
+"form.report.project" => 'Projecte',
+"form.report.totals_only" => 'Només totals',
+"form.report.total" => 'Hores Totals',
+"form.report.th.empllist" => 'Usuari',
+"form.report.th.date" => 'Data',
+"form.report.th.project" => 'Projecte',
+"form.report.th.activity" => 'Activitat',
+"form.report.th.start" => 'Inici',
+"form.report.th.finish" => 'Fi',
+"form.report.th.duration" => 'Durada',
+"form.report.th.note" => 'Nota',
+
+// charts form attributes
+// Note to translators: form.charts.title needs to be translated.
+// 'form.charts.title' => 'charts',
+
+// mail form attributes
+"form.mail.from" => 'De',
+"form.mail.to" => 'Per a',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'Assumpte',
+"form.mail.comment" => 'Comentari',
+"form.mail.above" => 'Enviar aquest report por e-mail',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>Missatge enviat</b>',
+
+// invoice attributes
+"form.invoice.title" => 'Factura',
+"form.invoice.caption" => 'Factura',
+"form.invoice.above" => 'Informació addicional per factura',
+"form.invoice.select_cust" => 'Seleccioni el client',
+"form.invoice.fillform" => 'Empleni els camps',
+"form.invoice.date" => 'Data',
+"form.invoice.number" => 'Número de factura',
+"form.invoice.tax" => 'Impost',
+"form.invoice.daily_subtotals" => 'Subtotals diaris',
+"form.invoice.yourcoo" => 'El seu nom <br> i direcció',
+"form.invoice.custcoo" => 'Nom del client <br> i direcció',
+"form.invoice.comment" => 'Comentari ',
+"form.invoice.th.username" => 'Persona',
+"form.invoice.th.time" => 'Hores',
+"form.invoice.th.rate" => 'Taxa',
+"form.invoice.th.summ" => 'Quantitat',
+"form.invoice.subtotal" => 'Subtotal',
+"form.invoice.customer" => 'Client',
+"form.invoice.mailinv_above" => 'Enviar aquesta factura per e-mail',
+"form.invoice.sending_str" => '<b>Factura enviada</b>',
+
+"form.migration.zip" => 'Comprimir',
+"form.migration.file" => 'Sel·leccioni l\\\'arxiu',
+"form.migration.import.title" => 'Importar dades',
+"form.migration.import.success" => 'Importació finalitzada amb èxit',
+"form.migration.import.text" => 'Importar dades del grup des d\\\'un arxiu xml',
+"form.migration.export.title" => 'Exportar dades',
+"form.migration.export.success" => 'Exportació finalitzada amb èxit',
+"form.migration.export.text" => 'Vosté pot exportar totes les dades del grup dins d\\\'un archivo xml. Això pot ser útil si necessita migrar dades al seu propi servidor.',
+
+"form.client.title" => 'Clients',
+"form.client.add_title" => 'Agregar client',
+"form.client.edit_title" => 'Modificar client',
+"form.client.del_title" => 'Eliminar client',
+"form.client.th.name" => 'Nom',
+"form.client.th.edit" => 'Modificar',
+"form.client.th.del" => 'Eliminar',
+"form.client.name" => 'Nom',
+"form.client.tax" => 'Impost',
+"form.client.daily_subtotals" => 'Subtotals diaris',
+"form.client.yourcoo" => 'El seu nou <br> i direcció a la factura',
+"form.client.custcoo" => 'Direcció',
+"form.client.comment" => 'Comentari ',
+
+// miscellaneous strings
+"forward.forgot_password" => '¿Ha oblidat la seva paraula de pas?',
+"forward.edit" => 'Modificar',
+"forward.delete" => 'Eliminar',
+"forward.tocsvfile" => 'Exportar dades a un arxiu .csv',
+"forward.geninvoice" => 'Generar factura',
+"forward.change" => 'Configurar clients',
+
+// strings inside contols on forms
+"controls.select.project" => '--- Sel·leccionar projecte ---',
+"controls.select.activity" => '--- Sel·leccionar activitat ---',
+"controls.select.client" => '--- Sel·leccionar client ---',
+"controls.project_bind" => '--- Tots ---',
+"controls.all" => '--- Tots ---',
+"controls.notbind" => '--- No ---',
+"controls.per_tm" => 'Aquest mes',
+"controls.per_lm" => 'El mes passat',
+"controls.per_tw" => 'Aquestat setmana',
+"controls.per_lw" => 'La setmana passada',
+"controls.per_td" => 'Aquest dia',
+"controls.per_lw" => 'La setmana passada',
+"controls.sel_period" => '--- Seleccionar període de temps ---',
+"controls.sel_groupby" => '--- No agrupar ---',
+"controls.inc_billable" => 'facturable',
+"controls.inc_nbillable" => 'no facturable',
+
+// labels
+"label.chart.title1" => 'activitats per usuari',
+"label.chart.period" => 'gràfica por període',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>A nom de %s</b>',
+"label.pminfo" => ' (Manejador)',
+"label.pcminfo" => ' (Auxiliar del manejador)',
+"label.painfo" => ' (Administrador)',
+"label.time_noentry" => 'Sense entrada',
+"label.today" => 'Data Actual',
+"label.req_fields" => '* camps requerits',
+"label.sel_project" => 'Seleccionar projecte',
+"label.sel_activity" => 'Seleccionar activitat',
+"label.sel_tp" => 'Seleccionar període de temps',
+"label.set_tp" => 'o establir dates',
+"label.fields" => 'Mostrar camps',
+"label.group_title" => 'Agrupar per',
+"label.include_title" => 'include records',
+"label.inv_str" => 'Factura',
+"label.set_empl" => 'Seleccionar usuaris',
+"label.sel_all" => 'Seleccionar tots',
+"label.sel_none" => 'Treure totes las seleccions',
+"label.or" => 'o',
+"label.disable" => 'Deshabilitar',
+"label.enable" => 'Habilitar',
+"label.filter" => 'Filtrar',
+"label.timeweek" => 'total setmanal',
+// Note to translators: strings below are missing from the translation and need to be added.
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/cs.lang.php b/WEB-INF/resources/cs.lang.php
new file mode 100644 (file)
index 0000000..96d7b7d
--- /dev/null
@@ -0,0 +1,410 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Česky';
+$i18n_months = array('leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec');
+$i18n_weekdays = array('neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota');
+$i18n_weekdays_short = array('ne', 'po', 'út', 'st', 'čt', 'pá', 'so');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/13', '05/01', '05/08', '07/05', '07/06', '09/28', '10/28', '11/17', '12/24', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'přihlásit',
+'menu.logout' => 'odhlásit',
+'menu.feedback' => 'váš názor',
+'menu.help' => 'pomoc',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'vytvořit nový účet vedoucího',
+'menu.edit_profile' => 'upravit profil',
+'menu.time' => 'záznam práce',
+'menu.reports' => 'sestavy',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projekty',
+'menu.activities' => 'činnosti',
+'menu.people' => 'pracovníci',
+'menu.teams' => 'týmy',
+'menu.export' => 'export',
+'menu.clients' => 'zákazníci',
+// Note to translators: menu.options needs to be translated.
+// 'menu.options' => 'options',
+'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'chyba databáze',
+'error.field' => 'nesprávná "{0}" data',
+'error.empty' => 'pole "{0}" je prázdné',
+'error.not_equal' => 'pole "{0}" neodpovídá poli "{1}"',
+'error.interval' => 'nevhodný interval',
+'error.project' => 'výběr projektu',
+'error.activity' => 'výběr činnosti',
+'error.auth' => 'nesprávné jméno nebo heslo',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'projekt tohoto jména již existuje',
+'error.activity_exists' => 'činnost tohoto jména již existuje',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: error.no_login needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'uživatel s tímto e-mailem neexistuje',
+'error.upload' => 'chyba přenosu souboru',
+// Note to translators: these 5 strings are missing from the translation and must be added.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'přihlásit',
+'button.now' => 'teď',
+// 'button.set' => 'nastavit',
+'button.save' => 'uložit',
+'button.delete' => 'smazat',
+'button.cancel' => 'zrušit',
+'button.submit' => 'uložit',
+'button.add_user' => 'přidat uživatele',
+'button.add_project' => 'přidat projekt',
+'button.add_activity' => 'přidat činnost',
+'button.add_client' => 'přidat zákazníka',
+'button.add' => 'přidat',
+'button.generate' => 'vytvořit',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'přejít',
+'button.send' => 'poslat',
+'button.send_by_email' => 'poslat e-mailem',
+'button.save_as_new' => 'uložit jako nový',
+'button.create_team' => 'vytvořit tým',
+'button.export' => 'exportovat tým',
+'button.import' => 'importovat tým',
+'button.apply' => 'provést',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'měna',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.name' => 'jméno',
+
+'label.password' => 'heslo',
+'label.confirm_password' => 'potvrdit heslo',
+// 'label.email' => 'email',
+'label.total' => 'celkem',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'oblíbená sestava',
+"form.filter.filter_new" => 'uložit jako oblíbenou sestavu',
+"form.filter.filter_confirm_delete" => 'opravdu chceš vymazat tuto položku z oblíbených?',
+
+// login form attributes
+"form.login.title" => 'přihlásit',
+"form.login.login" => 'přihlásit',
+
+// password reminder form attributes
+"form.fpass.title" => 'resetovat heslo',
+"form.fpass.login" => 'přihlásit',
+"form.fpass.send_pass_str" => 'zaslán požadavek k vymazání hesla',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker požadavek na vymazání hesla',
+// Note to translators: this string needs to be translated.
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "pro změnu hesla jej napište a zvolte uložit",
+
+// administrator form
+"form.admin.title" => 'administrator',
+"form.admin.duty_text" => 'vytvořit nový tým prostřednictvím účtu týmového manažera.<br>můžete také importovat týmová data z xml souboru z jiného time tracker serveru (nejsou povoleny shody e-mailových adres!).',
+
+"form.admin.change_pass" => 'změna hesla účtu administrator',
+"form.admin.profile.title" => 'týmy',
+"form.admin.profile.noprofiles" => 'vaše databáze je prázdná. přihlašte se jako admin a vytvořte nový tým.',
+"form.admin.profile.comment" => 'smazat tým',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'jméno',
+"form.admin.profile.th.edit" => 'upravit',
+"form.admin.profile.th.del" => 'smazat',
+"form.admin.profile.th.active" => 'aktovní',
+"form.admin.lock.period" => 'období uzamčení ve dnech',
+// Note to translators: the strings below are missing in the translation and must be added
+// "form.admin.options" => 'options',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'můj deník',
+"form.mytime.edit_title" => 'upravit časový záznam',
+"form.mytime.del_str" => 'smazat časový záznam',
+// Note to translators: "form.mytime.time_form" => ' (hh:mm)', // the string must be translated
+"form.mytime.date" => 'datum',
+"form.mytime.project" => 'projekt',
+"form.mytime.activity" => 'činnost',
+"form.mytime.start" => 'začátek',
+"form.mytime.finish" => 'konec',
+"form.mytime.duration" => 'trvání',
+"form.mytime.note" => 'poznámka',
+"form.mytime.behalf" => 'denní práce pracovníka',
+"form.mytime.daily" => 'denní práce',
+"form.mytime.total" => 'součet hodin: ',
+"form.mytime.th.project" => 'projekt',
+"form.mytime.th.activity" => 'činnost',
+"form.mytime.th.start" => 'začátek',
+"form.mytime.th.finish" => 'konec',
+"form.mytime.th.duration" => 'trvání',
+"form.mytime.th.note" => 'poznámka',
+"form.mytime.th.edit" => 'upravit',
+"form.mytime.th.delete" => 'odstranit',
+"form.mytime.del_yes" => 'časový záznam úspěšně odstraněn',
+"form.mytime.no_finished_rec" => 'záznam byl uložen pouze s časem zahájení. není to chyba. můžete se odhlásit, potřebujete-li.',
+"form.mytime.billable" => 'k fakturaci',
+"form.mytime.warn_tozero_rec" => 'tento záznam musí být smazán, neboť období je uzamčeno',
+// Note to translators: the string below is missing in the translation and must be added
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'vytvořit nový manažerský účet',
+"form.profile.edit_title" => 'upravit profil',
+"form.profile.name" => 'jméno',
+"form.profile.login" => 'přihlásit',
+
+"form.profile.showchart" => 'zobrazuj grafy',
+"form.profile.lang" => 'jazyk',
+// Note to translators: the strings below are missing in the translation and must be added
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'pracovnící',
+"form.people.createu_str" => 'vytváření nového uživatele',
+"form.people.edit_str" => 'nastavení uživatele',
+"form.people.del_str" => 'smazat uživatele',
+"form.people.th.name" => 'jméno',
+"form.people.th.login" => 'přihlásit',
+"form.people.th.role" => 'role',
+"form.people.th.edit" => 'upravit',
+"form.people.th.del" => 'smazat',
+"form.people.th.status" => 'status',
+"form.people.th.project" => 'projekt',
+"form.people.th.rate" => 'sazba',
+"form.people.manager" => 'manažer',
+"form.people.comanager" => 'spolumanažer',
+"form.people.empl" => 'uživatel',
+"form.people.name" => 'jméno',
+"form.people.login" => 'přihlásit',
+
+"form.people.rate" => 'hodinová sazba',
+"form.people.comanager" => 'spolumanažer',
+"form.people.projects" => 'projekty',
+
+// projects form attributes
+"form.project.proj_title" => 'projekty',
+"form.project.edit_str" => 'upravit projekt',
+"form.project.add_str" => 'pridat nový projekt',
+"form.project.del_str" => 'smazat projekt',
+"form.project.th.name" => 'jméno',
+"form.project.th.edit" => 'upravit',
+"form.project.th.del" => 'smazat',
+"form.project.name" => 'Název',
+
+// activities form attributes
+"form.activity.act_title" => 'činnosti',
+"form.activity.add_title" => 'přidat činnost',
+"form.activity.edit_str" => 'upravit činnost',
+"form.activity.del_str" => 'smazat činnost',
+"form.activity.name" => 'název činnosti',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'jméno',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'upravit',
+"form.activity.th.del" => 'smazat',
+
+// report attributes
+"form.report.title" => 'sestavy',
+"form.report.from" => 'počáteční datum',
+"form.report.to" => 'koncové datum',
+"form.report.groupby_user" => 'uživatel',
+"form.report.groupby_project" => 'projekt',
+"form.report.groupby_activity" => 'činnost',
+"form.report.duration" => 'trvání',
+"form.report.start" => 'počátek',
+"form.report.activity" => 'činnost',
+"form.report.show_idle" => 'ukázat nečinné',
+"form.report.finish" => 'konec',
+"form.report.note" => 'poznámka',
+"form.report.project" => 'projekt',
+"form.report.totals_only" => 'pouze součty',
+"form.report.total" => 'součty hodin',
+"form.report.th.empllist" => 'uzivatel',
+"form.report.th.date" => 'datum',
+"form.report.th.project" => 'projekt',
+"form.report.th.activity" => 'činnost',
+"form.report.th.start" => 'počátek',
+"form.report.th.finish" => 'konec',
+"form.report.th.duration" => 'trvání',
+"form.report.th.note" => 'poznámka',
+
+// mail form attributes
+"form.mail.from" => 'od',
+"form.mail.to" => 'komu',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'předmět',
+"form.mail.comment" => 'komentář',
+"form.mail.above" => 'poslat sestavu e-mailem',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>zpráva odeslána</b>',
+
+// invoice attributes
+"form.invoice.title" => 'faktura',
+"form.invoice.caption" => 'faktura',
+"form.invoice.above" => 'fakturační informace',
+"form.invoice.select_cust" => 'výběr firmy',
+"form.invoice.fillform" => 'vyplňte pole',
+"form.invoice.date" => 'datum',
+"form.invoice.number" => 'faktura číslo',
+"form.invoice.tax" => 'DPH',
+"form.invoice.daily_subtotals" => 'denní součty',
+"form.invoice.yourcoo" => 'vaše jméno<br> adresa',
+"form.invoice.custcoo" => 'zákazník<br> adresa',
+"form.invoice.comment" => 'komentář ',
+"form.invoice.th.username" => 'osoba',
+"form.invoice.th.time" => 'hodin',
+"form.invoice.th.rate" => 'sazba',
+"form.invoice.th.summ" => 'množství',
+"form.invoice.subtotal" => 'subtotal',
+"form.invoice.customer" => 'zákazník',
+"form.invoice.mailinv_above" => 'poslat fakturu e-mailem',
+"form.invoice.sending_str" => '<b>faktura odeslána</b>',
+
+"form.migration.zip" => 'komprese',
+"form.migration.file" => 'výběr souboru',
+"form.migration.import.title" => 'importovat data',
+"form.migration.import.success" => 'import byl úspěšně dokončen',
+"form.migration.import.text" => 'importovat týmová data z xml souboru',
+"form.migration.export.title" => 'exportovat data',
+"form.migration.export.success" => 'export byl úspěšně dokončen',
+"form.migration.export.text" => 'můžete exportova týmová data do xml souboru. může se to hodit pro přesun na jiný server.',
+// Note to translators: the string below is missing in the translation and must be added
+// "form.migration.compression.none" => 'none',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'zákazníci',
+"form.client.add_title" => 'přidat zákazníka',
+"form.client.edit_title" => 'upravit zákazníka',
+"form.client.del_title" => 'smazat zákazníka',
+"form.client.th.name" => 'jméno',
+"form.client.th.edit" => 'upravit',
+"form.client.th.del" => 'smazat',
+"form.client.name" => 'jméno',
+"form.client.tax" => 'DPH',
+"form.client.daily_subtotals" => 'denní součty',
+"form.client.yourcoo" => 'vaše jméno<br> a adresa pro fakturaci',
+"form.client.custcoo" => 'adresa',
+"form.client.comment" => 'poznámka ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'zapomenuté heslo?',
+"forward.edit" => 'upravit',
+"forward.delete" => 'smazat',
+"forward.tocsvfile" => 'exportovat data do .csv souboru',
+"forward.toxmlfile" => 'exportovat data do .xml souboru',
+"forward.geninvoice" => 'vytvořit fakturu',
+"forward.change" => 'upravit zákazníky',
+
+// strings inside contols on forms
+"controls.select.project" => '--- výběr projektu ---',
+"controls.select.activity" => '--- výběr činnosti ---',
+"controls.select.client" => '--- výběr zákazníka ---',
+"controls.project_bind" => '--- všechny ---',
+"controls.all" => '--- vše ---',
+"controls.notbind" => '--- nic ---',
+"controls.per_tm" => 'tento měsíc',
+"controls.per_lm" => 'minulý měsíc',
+"controls.per_tw" => 'tento týden',
+"controls.per_lw" => 'minulý týden',
+"controls.per_td" => 'dnes',
+"controls.per_at" => 'od počátku',
+"controls.per_ty" => 'letos',
+"controls.sel_period" => '--- výběr období ---',
+"controls.sel_groupby" => '--- vše dohromady ---',
+"controls.inc_billable" => 'k fakturaci',
+"controls.inc_nbillable" => 'mimo fakturaci',
+// Note to translators: the string below must be translated
+// "controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'činnosti uživatele',
+"label.chart.title2" => 'projekty uživatele',
+"label.chart.period" => 'přehled za období',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+// Note to translators: the string below must be translated
+// "label.pbehalf_info" => '%s %s <b>on behalf of %s</b>',
+"label.pminfo" => ' (manažer)',
+"label.pcminfo" => ' (co-manažer)',
+"label.painfo" => ' (administrator)',
+"label.time_noentry" => 'žádné záznamy',
+"label.today" => 'dnes',
+"label.req_fields" => '* nutno vyplnit',
+"label.sel_project" => 'výběr projektu',
+"label.sel_activity" => 'výběr činnosti',
+"label.sel_tp" => 'výberte období',
+"label.set_tp" => 'nebo určete dny',
+"label.fields" => 'zobrazit pole',
+"label.group_title" => 'seskupit podle',
+"label.include_title" => 'včetně záznamů',
+"label.inv_str" => 'faktura',
+"label.set_empl" => 'výběr uživatelů',
+"label.sel_all" => 'vybrat všechno',
+"label.sel_none" => 'zrušit výběr',
+"label.or" => 'nebo',
+"label.disable" => 'zakázat',
+"label.enable" => 'povolit',
+"label.filter" => 'filtr',
+"label.timeweek" => 'celkem za týden',
+"label.hrs" => 'hodin',
+// Note to translators: the 3 strings below are missing in the translation and must be added
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+
+// login hello text
+// Note to translators: the string below is missing in the translation and must be added
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/da.lang.php b/WEB-INF/resources/da.lang.php
new file mode 100644 (file)
index 0000000..c6ddba4
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Dansk';
+$i18n_months = array('januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december');
+$i18n_weekdays = array('søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag');
+$i18n_weekdays_short = array('sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/09', '04/10', '04/12', '04/13', '05/08', '05/21', '05/31', '06/01', '06/05', '12/24', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'login',
+'menu.logout' => 'logout',
+'menu.feedback' => 'send din mening',
+'menu.help' => 'hjælp',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'lav en ny manager konto',
+'menu.edit_profile' => 'rediger profil',
+'menu.time' => 'min tid',
+'menu.reports' => 'rapporter',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projekter',
+'menu.activities' => 'aktiviteter',
+'menu.people' => 'brugere',
+// Note to translators: is menu.teams translated?
+'menu.teams' => 'teams',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'kunder',
+'menu.options' => 'indstillinger',
+// A note to translators: the string below needs to be translated.
+// 'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'database fejl',
+'error.field' => 'forkert "{0}" data',
+'error.empty' => 'felt "{0}" er tom',
+'error.not_equal' => 'felt "{0}" er ikke lig med "{1}"',
+'error.interval' => 'forkert interval',
+'error.project' => 'vælg projekt',
+'error.activity' => 'vælg aktivitet',
+'error.auth' => 'forkert login eller password',
+// Note to translators: 'error.user_exists' => 'der eksitrerer en bruger med denne e-mail adresse', // e-mail must be changed to login.
+'error.project_exists' => 'der eksiterer allerede et projekt med det navn',
+'error.activity_exists' => 'aktivitet med det navn eksisterer allerede',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+'error.no_login' => 'ingen bruger med denne login',
+'error.upload' => 'fil upload problem',
+// Note to translators: the 5 strings below are missing in the translation and must be added
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'login',
+'button.now' => 'nu',
+// 'button.set' => 'sæt',
+'button.save' => 'gem',
+'button.delete' => 'slet',
+'button.cancel' => 'fortryd',
+'button.submit' => 'gem',
+'button.add_user' => 'tilføj bruger',
+'button.add_project' => 'tilføj project',
+'button.add_activity' => 'tilføj aktivitet',
+'button.add_client' => 'tilføj kunde',
+'button.add' => 'tilføj',
+'button.generate' => 'dan',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'gе til',
+// Note to translators: the strings below must be translated
+// 'button.send' => 'send',
+// 'button.send_by_email' => 'send som e-mail',
+'button.save_as_new' => 'gem som ny',
+// TODO: check translation of button.create_team
+// 'button.create_team' => 'lav en team',
+'button.export' => 'exporter team',
+'button.import' => 'importer team',
+'button.apply' => 'gem',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'møntfod',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.name' => 'navn',
+
+'label.password' => 'adgangskode',
+'label.confirm_password' => 'gentag adgangskode',
+'label.email' => 'email',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'favorit rapport',
+"form.filter.filter_new" => 'gem som favorit',
+// Note to translators: the string below is missing in the translation and must be added
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'login',
+"form.login.login" => 'login',
+
+// password reminder form attributes
+"form.fpass.title" => 'nulstil adgangskode',
+"form.fpass.login" => 'login',
+"form.fpass.send_pass_str" => 'ønske om ny adgangskode sendt',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker adgangskode nulstil ',
+// Note to translators: this string needs to be translated.
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "for at nulstille din adgangskode, tast det og klik gem",
+
+// administrator form
+"form.admin.title" => 'administrator',
+// Note to translators: "form.admin.duty_text" => 'Lav et nyt team, ved at lave en team manager konto.<br>Du kan ogsе importerer fra en xml fil fra en anden Anuko Time Tracker server (no login collisions are allowed).', // the phrase in brackets must be translated
+
+"form.admin.change_pass" => 'skift adgagnskode pе administrator konto',
+"form.admin.profile.title" => 'teams',
+"form.admin.profile.noprofiles" => 'din database er tom, login som administrator og lav et nyt team',
+"form.admin.profile.comment" => 'slet team',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'navn',
+"form.admin.profile.th.edit" => 'rediger',
+"form.admin.profile.th.del" => 'slet',
+"form.admin.profile.th.active" => 'aktive',
+// Note to translators: the strings below are missing in the translation and must be added
+// "form.admin.lock.period" => 'lock interval in days',
+"form.admin.options" => 'indstillinger',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'min tid',
+"form.mytime.edit_title" => 'rediger tids post',
+"form.mytime.del_str" => 'slet tids post',
+// Note to translators: "form.mytime.time_form" => ' (hh:mm)', // the string must be translated
+"form.mytime.date" => 'dato',
+"form.mytime.project" => 'projekt',
+"form.mytime.activity" => 'aktivitet',
+"form.mytime.start" => 'start',
+"form.mytime.finish" => 'slut',
+"form.mytime.duration" => 'varighed',
+"form.mytime.note" => 'notat',
+"form.mytime.behalf" => 'dagligt arbejde for',
+"form.mytime.daily" => 'dagligt arbejde',
+"form.mytime.total" => 'timer i alt: ',
+"form.mytime.th.project" => 'projekt',
+"form.mytime.th.activity" => 'aktivitet',
+"form.mytime.th.start" => 'start',
+"form.mytime.th.finish" => 'slut',
+"form.mytime.th.duration" => 'varighed',
+"form.mytime.th.note" => 'notat',
+"form.mytime.th.edit" => 'rediger',
+"form.mytime.th.delete" => 'slet',
+"form.mytime.del_yes" => 'tids post slettet',
+"form.mytime.no_finished_rec" => 'denne post er gemt med kun en start tid. Det er ikke nødvendigvis en fejl. Du kan nu logge af.',
+// Note to translators: the 3 strings below are missing in the translation and need to be added
+// "form.mytime.billable" => 'billable',
+// "form.mytime.warn_tozero_rec" => 'this time record must be deleted because this time period is locked',
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'Dan ny manager konot',
+"form.profile.edit_title" => 'rediger profil',
+"form.profile.name" => 'navn',
+"form.profile.login" => 'login', 
+
+// Note to translators: the strings below are missing in the translation and need to be added
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'Brugere',
+"form.people.createu_str" => 'Dan ny bruger',
+"form.people.edit_str" => 'reidger bruger',
+"form.people.del_str" => 'slet bruger',
+"form.people.th.name" => 'navn',
+"form.people.th.login" => 'login', 
+"form.people.th.role" => 'rolle',
+"form.people.th.edit" => 'rediger',
+"form.people.th.del" => 'slet',
+"form.people.th.status" => 'status',
+"form.people.th.project" => 'projekt',
+"form.people.th.rate" => 'rate',
+"form.people.manager" => 'manager',
+"form.people.comanager" => 'comanager',
+"form.people.empl" => 'bruger',
+"form.people.name" => 'navn',
+"form.people.login" => 'login', 
+
+"form.people.rate" => 'standard tidsfaktor',
+"form.people.comanager" => 'co-manager',
+"form.people.projects" => 'projekter',
+
+// projects form attributes
+"form.project.proj_title" => 'projekter',
+"form.project.edit_str" => 'rediger projekter',
+"form.project.add_str" => 'tilføj projekt', 
+"form.project.del_str" => 'slet projekt',
+"form.project.th.name" => 'navn',
+"form.project.th.edit" => 'rediger',
+"form.project.th.del" => 'slet',
+"form.project.name" => 'navn',
+
+// activities form attributes
+"form.activity.act_title" => 'aktiviteter',
+"form.activity.add_title" => 'tilføj ny aktivitet', 
+"form.activity.edit_str" => 'rediger aktivitet',
+"form.activity.del_str" => 'slet aktivitet',
+"form.activity.name" => 'navn',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'navn',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'rediger',
+"form.activity.th.del" => 'slet',
+
+// report attributes
+"form.report.title" => 'rapport',
+"form.report.from" => 'start dato',
+"form.report.to"=> 'slut dato',
+"form.report.groupby_user" => 'bruger',
+"form.report.groupby_project" => 'projekt',
+"form.report.groupby_activity" => 'aktivitet',
+"form.report.duration" => 'varighed',
+"form.report.start" => 'start',
+"form.report.activity" => 'aktivitet',
+"form.report.show_idle" => 'hvis ledig tid',
+"form.report.finish" => 'slut',
+"form.report.note" => 'notat',
+"form.report.project" => 'projekt',
+"form.report.totals_only" => 'kun totaler',
+"form.report.total" => 'timer totalt',
+"form.report.th.empllist" => 'bruger',
+"form.report.th.date" => 'dato',
+"form.report.th.project" => 'projekt',
+"form.report.th.activity" => 'aktivitet',
+"form.report.th.start" => 'start',
+"form.report.th.finish" => 'slut',
+"form.report.th.duration" => 'varighed',
+"form.report.th.note" => 'notat',
+
+// mail form attributes
+"form.mail.from" => 'fra',
+"form.mail.to" => 'til',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'emne',
+"form.mail.comment" => 'komment',
+"form.mail.above" => 'send denne rapport pr. email',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>mail sendt</b>',
+
+// invoice attributes
+"form.invoice.title" => 'faktura',
+"form.invoice.caption" => 'faktura',
+"form.invoice.above" => 'Yderligere information for faktura',
+"form.invoice.select_cust" => 'vælg kunde', 
+"form.invoice.fillform" => 'udfyld felterne',
+"form.invoice.date" => 'dato',
+"form.invoice.number" => 'faktura nummer',
+"form.invoice.tax" => 'skat',
+"form.invoice.daily_subtotals" => 'daglig mellemregninger',
+"form.invoice.yourcoo" => 'Dit navn<br> og adresse',
+"form.invoice.custcoo" => 'Kunde navn<br> og adresse',
+"form.invoice.comment" => 'kommentar',
+"form.invoice.th.username" => 'person',
+"form.invoice.th.time" => 'timer',
+"form.invoice.th.rate" => 'rate',
+"form.invoice.th.summ" => 'beløb', 
+"form.invoice.subtotal" => 'subtotal',
+"form.invoice.customer" => 'kunde',
+"form.invoice.mailinv_above" => 'send denne faktura pr. e-mail',
+"form.invoice.sending_str" => '<b>faktura sendt</b>',
+
+"form.migration.zip" => 'komprimering',
+"form.migration.file" => 'vælg fil', 
+"form.migration.import.title" => 'import data',
+"form.migration.import.success" => 'import gennemført', 
+"form.migration.import.text" => 'import team data fra en xml fil',
+"form.migration.export.title" => 'export data',
+"form.migration.export.success" => 'export gennemført', 
+"form.migration.export.text" => 'Du kan eksporerer data til enxml fil. det kan være praktisk hvis du flytter til egen server.', 
+// Note to translators: the 3 strings below are missing in the translation and must be added
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'kunder',
+"form.client.add_title" => 'tilføj kunde', 
+"form.client.edit_title" => 'rediger kunde',
+"form.client.del_title" => 'slet kunde',
+"form.client.th.name" => 'navn',
+"form.client.th.edit" => 'rediger',
+"form.client.th.del" => 'slet',
+"form.client.name" => 'naavn',
+"form.client.tax" => 'skat',
+"form.client.daily_subtotals" => 'daglige mellemregninger',
+"form.client.yourcoo" => 'Dit navn<br> og adresse pе faktura',
+"form.client.custcoo" => 'addresse',
+"form.client.comment" => 'kommenter ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'Glemt adgangskode?',
+"forward.edit" => 'rediger',
+"forward.delete" => 'slet',
+"forward.tocsvfile" => 'exporter data til .csv fil',
+// Note to translators:  the string below is missing in the translation and must be added
+// "forward.toxmlfile" => 'export data to .xml file',
+"forward.geninvoice" => 'dan faktura',
+"forward.change" => 'konfigurer kunder',
+
+// strings inside contols on forms
+"controls.select.project" => '--- vælg projekt ---',
+"controls.select.activity" => '--- vælg aktivitet ---',
+"controls.select.client" => '---  vælg kunde---',
+"controls.project_bind" => '--- alle ---',
+"controls.all" => '--- alle ---',
+"controls.notbind" => '--- ingen ---',
+"controls.per_tm" => 'denne mеned',
+"controls.per_lm" => 'sidste mеned',
+"controls.per_tw" => 'denne uge',
+"controls.per_lw" => 'sidste uge',
+// Note to translators: the 3 strings below are missing in the translation and must be added
+// "controls.per_td" => 'this day',
+// "controls.per_at" => 'all time',
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- vælg tids periode ---',
+"controls.sel_groupby" => '--- vælg gruppe ---', 
+// Note to translators: the 3 strings below are missing in the translation and must be added
+// "controls.inc_billable" => 'billable',
+// "controls.inc_nbillable" => 'not billable',
+// "controls.default" => '--- default ---',
+
+// labels
+// Note to translators: the 3 strings below are missing in the translation and must be added
+//"label.chart.title1" => 'activities for user',
+// "label.chart.title2" => 'projects for user',
+// "label.chart.period" => 'chart for period',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>pе vegne af %s</b>',
+"label.pminfo" => ' (manager)',
+"label.pcminfo" => ' (co-manager)',
+"label.painfo" => ' (administrator)',
+"label.time_noentry" => 'ingen input',
+"label.today" => 'idag',
+"label.req_fields"=> '* krævede felter', 
+"label.sel_project" => 'vælg projekt',
+"label.sel_activity" => 'vælg aktivtet',
+"label.sel_tp" => 'vælg periode',
+"label.set_tp" => 'eller vælg datoer',
+"label.fields" => 'Vis fleter',
+"label.group_title" => 'gruper',
+// Note to translators: the string below is missing in the translation and must be added
+// "label.include_title" => 'include records',
+"label.inv_str" => 'faktura',
+"label.set_empl" => 'vælg brugere',
+"label.sel_all" => 'vælg alle',
+"label.sel_none" => 'fravælg alle', 
+"label.or" => 'eller',
+"label.disable" => 'disable',
+"label.enable" => 'enable',
+"label.filter" => 'filtrer',
+// Note to translators: strings below are missing in the translation and must be added
+// "label.timeweek" => 'weekly total',
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/de.lang.php b/WEB-INF/resources/de.lang.php
new file mode 100644 (file)
index 0000000..ee6901a
--- /dev/null
@@ -0,0 +1,401 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+$i18n_language = 'Deutsch';
+$i18n_months = array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember');
+$i18n_weekdays = array('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag');
+$i18n_weekdays_short = array('So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/06', '04/09', '05/01', '05/17', '05/28', '10/03', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Anmelden',
+'menu.logout' => 'Abmelden',
+'menu.forum' => 'Forum',
+'menu.help' => 'Hilfe',
+'menu.create_team' => 'Neues Team',
+'menu.profile' => 'Profil',
+'menu.time' => 'Zeiten',
+'menu.expenses' => 'Kosten',
+'menu.reports' => 'Berichte',
+'menu.charts' => 'Diagramme',
+'menu.projects' => 'Projekte',
+'menu.tasks' => 'Aufgaben',
+'menu.users' => 'Personen',
+'menu.teams' => 'Teams',
+'menu.export' => 'Exportieren',
+'menu.clients' => 'Kunden',
+'menu.options' => 'Optionen',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker funktioniert auch auf dem Smartphone.',
+'footer.credits' => 'Impressum',
+'footer.license' => 'Lizenz',
+
+// Error messages.
+// TODO: translate the following string.
+// 'error.access_denied' => 'Access denied.',
+'error.sys' => 'Systemfehler.',
+'error.db' => 'Datenbankfehler.',
+'error.field' => 'Ungültige "{0}" Daten.',
+'error.empty' => 'Feld "{0}" ist leer.',
+'error.not_equal' => 'Feld "{0}" ist nicht gleich Feld "{1}".',
+'error.interval' => 'Feld "{0}" muss größer sein als "{1}".',
+'error.project' => 'Projekt wählen.',
+'error.task' => 'Aufgabe auswählen.',
+'error.client' => 'Kunde auswählen.',
+// TODO: translate the following string.
+// 'error.report' => 'Select report.',
+'error.auth' => 'Benutzername oder Passwort ungültig.',
+'error.user_exists' => 'Benutzer mit diesem Konto ist bereits vorhanden.',
+'error.project_exists' => 'Es gibt bereits ein Projekt mit diesem Namen.',
+'error.task_exists' => 'Task mit diesem Namen existiert bereits.',
+'error.client_exists' => 'Der Kunde mit dem Namen existiert schon.',
+'error.invoice_exists' => 'Rechnung mit dieser Nummer existiert bereits.',
+'error.no_invoiceable_items' => 'Keine Einträge zur Rechnungsstellung gefunden.',
+'error.no_login' => 'Benutzer mit diesen Anmeldedaten nicht vorhanden.',
+'error.no_teams' => 'Die Datenbank ist leer. Als Administrator anmelden und ein neues Team erzeugen.',
+'error.upload' => 'Fehler beim hochladen einer Datei.',
+'error.period_locked' => 'Kann den Vorgang nicht beenden. Einträge die älter sind als eine bestimmte Anzahl von Tagen, können nicht erstellt oder geändert werden. Teammanager definieren dies in den "Zeitraum in Tagen sperren" Wert auf der "Profil bearbeiten" Seite. Setzen Sie Ihn auf 0 um die Sperre zu entfernen. <br><br>Unvollständige Einträge (mit 0 oder leerem Zeitraum) kann gelöscht werden.',
+'error.mail_send' => 'Fehler beim versenden einer E-Mail.',
+'error.no_email' => 'Dieser Benutzer besitzt keine e-Mail Adresse.',
+'error.uncompleted_exists' => 'Unvollständiger Eintrag bereits vorhanden. Schließen oder Löschen.',
+'error.goto_uncompleted' => 'Zum unvollständigen Eintrag gehen.',
+'error.overlap' => 'Der Zeitinterval überschneidet sich mit vorhandenen Einträgen.',
+'error.future_date' => 'Datum ist in der Zukunft.',
+
+// Labels for buttons.
+'button.login' => 'Anmelden',
+'button.now' => 'Jetzt',
+'button.save' => 'Speichern',
+'button.copy' => 'Kopieren',
+'button.cancel' => 'Abbrechen',
+'button.submit' => 'Abschicken',
+'button.add_user' => 'Benutzerkonto hinzufügen',
+'button.add_project' => 'Projekt anlegen',
+'button.add_task' => 'Task hinzufügen',
+'button.add_client' => 'Auftraggeber anlegen',
+'button.add_invoice' => 'Rechnung hinzufügen',
+'button.add_option' => 'Option hinzufügen',
+'button.add' => 'Hinzufügen',
+'button.generate' => 'Erstellen',
+'button.reset_password' => 'Passwort zurücksetzen',
+'button.send' => 'Senden',
+'button.send_by_email' => 'Als E-Mail senden',
+'button.create_team' => 'Team erstellen',
+'button.export' => 'Team exportieren',
+'button.import' => 'Team importieren',
+'button.close' => 'Schließen',
+'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Teamname',
+'label.address' => 'Adresse',
+'label.currency' => 'Währung',
+'label.manager_name' => 'Manager Name',
+'label.manager_login' => 'Manager Login',
+'label.person_name' => 'Name',
+'label.thing_name' => 'Name',
+'label.login' => 'Anmeldung',
+'label.password' => 'Passwort',
+'label.confirm_password' => 'Passwort bestätigen',
+'label.email' => 'E-Mail',
+'label.date' => 'Datum',
+'label.start_date' => 'Anfangsdatum',
+'label.end_date' => 'Enddatum',
+'label.user' => 'Benutzer',
+'label.users' => 'Personen',
+'label.client' => 'Kunde',
+'label.clients' => 'Kunden',
+'label.option' => 'Option',
+'label.invoice' => 'Rechnung',
+'label.project' => 'Projekt',
+'label.projects' => 'Projekte',
+'label.task' => 'Aufgabe',
+'label.tasks' => 'Aufgaben',
+'label.description' => 'Beschreibung',
+'label.start' => 'Start',
+'label.finish' => 'Ende',
+'label.duration' => 'Dauer',
+'label.note' => 'Beschreibung',
+'label.item' => 'Position',
+'label.cost' => 'Kosten',
+'label.week_total' => 'Summe (Woche)',
+'label.day_total' => 'Summe (Tag)',
+'label.today' => 'Heute',
+'label.total_hours' => 'Gesamtstunden',
+'label.total_cost' => 'Totale Kosten',
+'label.view' => 'Ansicht',
+'label.edit' => 'Editieren',
+'label.delete' => 'Löschen',
+'label.configure' => 'Konfigurieren',
+'label.select_all' => 'Alle auswählen',
+'label.select_none' => 'Alle abwählen',
+'label.id' => 'ID',
+'label.language' => 'Sprache',
+// TODO: translate the following string.
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'Blockierter Zeitraum in Tagen',
+'label.date_format' => 'Datumsformat',
+'label.time_format' => 'Zeitformat',
+'label.week_start' => 'Erster Wochentag',
+'label.comment' => 'Kommentar',
+'label.status' => 'Status',
+'label.tax' => 'Umsatzsteuer',
+'label.subtotal' => 'Zwischensumme',
+'label.total' => 'Gesamtsumme',
+'label.client_name' => 'Kundenname',
+'label.client_address' => 'Adresse',
+'label.or' => 'oder',
+'label.error' => 'Fehler',
+'label.ldap_hint' => 'Geben Sie unten Ihren <b>Windows Benutzernamen</b> und Ihr <b>Passwort</b> ein.',
+'label.required_fields' => '* - Pflichtfelder',
+'label.on_behalf' => 'für',
+'label.role_manager' => '(Manager)',
+'label.role_comanager' => '(Co-Manager)',
+'label.role_admin' => '(Administrator)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Benutzerfelder',
+'label.type' => 'Typ',
+'label.type_dropdown' => 'Ausklappen',
+'label.type_text' => 'Text',
+'label.required' => 'Benötigt',
+'label.fav_report' => 'Bevorzugter Report',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'Anmelden',
+'title.teams' => 'Teams',
+'title.create_team' => 'Arbeitsgruppe anlegen',
+'title.edit_team' => 'Team bearbeiten',
+'title.delete_team' => 'Team löschen',
+'title.reset_password' => 'Passworterinnerung',
+'title.change_password' => 'Passwortänderung',
+'title.time' =>  'Meine Zeiten',
+'title.edit_time_record' => 'Bearbeiten des Stundeneintrags',
+'title.delete_time_record' => 'Eintrag löschen',
+'title.expenses' => 'Kosten',
+'title.edit_expense' => 'Kostenposition ändern',
+'title.delete_expense' => 'Kostenposition löschen',
+'title.reports' => 'Berichte',
+'title.report' => 'Bericht',
+'title.send_report' => 'Bericht senden',
+'title.invoice' => 'Rechnung',
+'title.send_invoice' => 'Rechnung senden',
+'title.charts' => 'Diagramme',
+'title.projects' => 'Projekte',
+'title.add_project' => 'Projekt anlegen',
+'title.edit_project' => 'Projekt bearbeiten',
+'title.delete_project' => 'Projekt löschen',
+'title.tasks' => 'Aufgaben',
+'title.add_task' => 'Aufgabe hinzufügen',
+'title.edit_task' => 'Aufgabe bearbeiten',
+'title.delete_task' => 'Aufgabe löschen',
+'title.users' => 'Personen',
+'title.add_user' => 'Benutzerkonto erstellen',
+'title.edit_user' => 'Benutzerdaten bearbeiten',
+'title.delete_user' => 'Benutzer löschen',
+'title.clients' => 'Kunden',
+'title.add_client' => 'Kunden hinzufügen',
+'title.edit_client' => 'Kunden bearbeiten',
+'title.delete_client' => 'Kunden löschen',
+'title.invoices' => 'Rechnungen',
+'title.add_invoice' => 'Rechnung hinzufügen',
+'title.view_invoice' => 'Rechnung ansehen',
+'title.delete_invoice' => 'Rechnung löschen',
+// TODO: translate the following strings.
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'Daten exportieren',
+'title.import' => 'Daten importieren',
+'title.options' => 'Optionen',
+'title.profile' => 'Profil',
+'title.cf_custom_fields' => 'Benutzerfelder',
+'title.cf_add_custom_field' => 'Benutzerfeld hinzufügen',
+'title.cf_edit_custom_field' => 'Benutzerfeld bearbeiten',
+'title.cf_delete_custom_field' => 'Benutzerfeld löschen',
+'title.cf_dropdown_options' => 'Auswahlmöglichkeiten',
+'title.cf_add_dropdown_option' => 'Auswahlmöglichkeit hinzufügen',
+'title.cf_edit_dropdown_option' => 'Auswahlmöglichkeit bearbeiten',
+'title.cf_delete_dropdown_option' => 'Auswahlmöglichkeit löschen',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- alle ---',
+'dropdown.no' => '--- nein ---',
+'dropdown.this_day' => 'aktueller Tag',
+'dropdown.this_week' => 'aktuelle Woche',
+'dropdown.last_week' => 'vorherige Woche',
+'dropdown.this_month' => 'aktueller Monat',
+'dropdown.last_month' => 'vorheriger Monat',
+'dropdown.this_year' => 'aktuelles Jahr',
+'dropdown.all_time' => 'Gesamtzeitraum',
+'dropdown.projects' => 'Projekte',
+'dropdown.tasks' => 'Aufgaben',
+'dropdown.clients' => 'Kunden',
+'dropdown.select' => '--- auswählen ---',
+'dropdown.select_invoice' => '--- Rechnung auswählen ---',
+'dropdown.status_active' => 'aktiv',
+'dropdown.status_inactive' => 'inaktiv',
+// TODO: translate the following strings.
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Passwort vergessen?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> ist ein einfaches, leicht zu bedienendes, Open-Source Zeiterfassungssystem.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Anfrage zur Zurücksetzung des Passwortes wurde per E-mail gesendet.',
+'form.reset_password.email_subject' => 'Anuko Time Tracker Anfrage zur Zurücksetzung des Passwortes',
+'form.reset_password.email_body' => "Sehr geehrter Nutzer,\n\nJemand, vielleicht Sie, sendete die Aufforderung Ihr Anuko Time Tracker Passwort zurückzusetzen. Bitte rufen Sie diesen Link auf wenn Sie Ihr Passwort zurücksetzen möchten.\n\n%s\n\nAnuko Time Tracker ist ein einfaches, leicht zu bedienendes, Open-Source Zeiterfassungs-System. Besuchen Sie https://www.anuko.com für weitere Informationen.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Um das Passwort zurückzusetzen, geben Sie ein Neues ein und klicken dann auf Speichern.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm oder 0.0h)',
+'form.time.billable' => 'In Rechnung stellen',
+'form.time.uncompleted' => 'Unvollständig',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Dieser Eintrag wurde ohne Startzeit gespeichert. Dies ist kein Fehler.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Als bevorzugt speichern',
+'form.reports.confirm_delete' => 'Sind Sie sicher, dass der bevorzugte Report gelöscht werden soll?',
+'form.reports.include_records' => 'Daten hinzufügen',
+'form.reports.include_billable' => 'in Rechnung stellen',
+'form.reports.include_not_billable' => 'nicht in Rechnung stellen',
+// TODO: translate the following strings.
+// 'form.reports.include_invoiced' => 'invoiced',
+// 'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'Zeitraum auswählen',
+'form.reports.set_period' => 'oder Datum eingeben',
+'form.reports.show_fields' => 'Felder anzeigen',
+'form.reports.group_by' => 'Gruppieren nach',
+'form.reports.group_by_no' => '--- keine Gruppierung ---',
+'form.reports.group_by_date' => 'Datum',
+'form.reports.group_by_user' => 'Benutzer',
+'form.reports.group_by_client' => 'Kunde',
+'form.reports.group_by_project' => 'Projekt',
+'form.reports.group_by_task' => 'Aufgabe',
+'form.reports.totals_only' => 'Nur Gesamtstunden',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Exportiere',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Rechnungsnummer',
+'form.invoice.person' => 'Person',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Zeitraum',
+'form.charts.chart' => 'Diagramm',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Aktive Projekte',
+'form.projects.inactive_projects' => 'Inaktive Projekte',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Aktive Tasks',
+'form.tasks.inactive_tasks' => 'Inaktive Tasks',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Aktive Nutzer',
+'form.users.inactive_users' => 'Inaktive Nutzer',
+'form.users.role' => 'Rolle',
+'form.users.manager' => 'Manager',
+'form.users.comanager' => 'Co-Manager',
+'form.users.rate' => 'Stundensatz',
+'form.users.default_rate' => 'Normaler Stundensatz',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Aktive Kunden',
+'form.clients.inactive_clients' => 'Inaktive Kunden',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Sie können alle Teamdaten in eine XML-Datei exportieren. Diese können in andere Zeiterfassungs-Programme importiert werden.',
+'form.export.compression' => 'Kompression',
+'form.export.compression_none' => 'Keine',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Teamdaten von einer XML-Datei importieren.',
+'form.import.file' => 'Datei auswählen',
+'form.import.success' => 'Import erfolgreich abgeschlossen.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' => 'Das Erzeugen eines neuen Manager Kontos, erzeugt eine neues Team.<br>Diese Teams können auch von XML-Dateien importiert werden.',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 Stunden',
+'form.profile.24_hours' => '24 Stunden',
+'form.profile.tracking_mode' => 'Nachverfolgung',
+'form.profile.mode_time' => 'Zeit',
+'form.profile.mode_projects' => 'Projekte',
+'form.profile.mode_projects_and_tasks' => 'Projekte und Aufgaben',
+'form.profile.record_type' => 'Zeiterfassungstyp',
+'form.profile.type_all' => 'alle',
+'form.profile.type_start_finish' => 'Start und Ende',
+'form.profile.type_duration' => 'Dauer',
+'form.profile.plugins' => 'Erweiterungen',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'Von',
+'form.mail.to' => 'An',
+'form.mail.cc' => 'CC',
+'form.mail.subject' => 'Betreff',
+'form.mail.report_subject' => 'Time Tracker Bericht',
+'form.mail.footer' => 'Anuko Time Tracker ist ein einfaches, leicht zu bedienendes, Open-Source<br>Zeitverwaltungs-System. Besuchen Sie <a href="https://www.anuko.com">www.anuko.com</a> für weitere Informationen.',
+'form.mail.report_sent' => 'Der Bericht wurde gesendet.',
+'form.mail.invoice_sent' => 'Die Rechnung wurde gesendet.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/en.lang.php b/WEB-INF/resources/en.lang.php
new file mode 100644 (file)
index 0000000..d3834a4
--- /dev/null
@@ -0,0 +1,396 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'English';
+$i18n_months = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
+$i18n_weekdays = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+$i18n_weekdays_short = array('Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/16', '02/20', '05/28', '07/04', '09/03', '10/10', '11/11', '11/24', '12/25');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Login',
+'menu.logout' => 'Logout',
+'menu.forum' => 'Forum',
+'menu.help' => 'Help',
+'menu.create_team' => 'Create Team',
+'menu.profile' => 'Profile',
+'menu.time' => 'Time',
+'menu.expenses' => 'Expenses',
+'menu.reports' => 'Reports',
+'menu.charts' => 'Charts',
+'menu.projects' => 'Projects',
+'menu.tasks' => 'Tasks',
+'menu.users' => 'Users',
+'menu.teams' => 'Teams',
+'menu.export' => 'Export',
+'menu.clients' => 'Clients',
+'menu.options' => 'Options',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker is available on mobile phones.',
+'footer.credits' => 'Credits',
+'footer.license' => 'License',
+
+// Error messages.
+'error.access_denied' => 'Access denied.',
+'error.sys' => 'System error.',
+'error.db' => 'Database error.',
+'error.field' => 'Incorrect "{0}" data.',
+'error.empty' => 'Field "{0}" is empty.',
+'error.not_equal' => 'Field "{0}" is not equal to field "{1}".',
+'error.interval' => 'Field "{0}" must be greater than "{1}".',
+'error.project' => 'Select project.',
+'error.task' => 'Select task.',
+'error.client' => 'Select client.',
+'error.report' => 'Select report.',
+'error.auth' => 'Incorrect login or password.',
+'error.user_exists' => 'User with this login already exists.',
+'error.project_exists' => 'Project with this name already exists.',
+'error.task_exists' => 'Task with this name already exists.',
+'error.client_exists' => 'Client with this name already exists.',
+'error.invoice_exists' => 'Invoice with this number already exists.',
+'error.no_invoiceable_items' => 'There are no invoiceable items.',
+'error.no_login' => 'No user with this login.',
+'error.no_teams' => 'Your database is empty. Login as admin and create a new team.',
+'error.upload' => 'File upload error.',
+'error.period_locked' => 'Can\\\'t complete the operation. Records older than a certain number of days cannot be created or modified. Team manager defines this in the "Lock interval in days" value on the "Profile" page. Set it to 0 to remove locking. <br><br>Uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => 'Error sending mail.',
+'error.no_email' => 'No email associated with this login.',
+'error.uncompleted_exists' => 'Uncompleted entry already exists. Close or delete it.',
+'error.goto_uncompleted' => 'Go to uncompleted entry.',
+'error.overlap' => 'Time interval overlaps with existing records.',
+'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'Login',
+'button.now' => 'Now',
+'button.save' => 'Save',
+'button.copy' => 'Copy',
+'button.cancel' => 'Cancel',
+'button.submit' => 'Submit',
+'button.add_user' => 'Add user',
+'button.add_project' => 'Add project',
+'button.add_task' => 'Add task',
+'button.add_client' => 'Add client',
+'button.add_invoice' => 'Add invoice',
+'button.add_option' => 'Add option',
+'button.add' => 'Add',
+'button.generate' => 'Generate',
+'button.reset_password' => 'Reset password',
+'button.send' => 'Send',
+'button.send_by_email' => 'Send by e-mail',
+'button.create_team' => 'Create team',
+'button.export' => 'Export team',
+'button.import' => 'Import team',
+'button.close' => 'Close',
+'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Team name',
+'label.address' => 'Address',
+'label.currency' => 'Currency',
+'label.manager_name' => 'Manager name',
+'label.manager_login' => 'Manager login',
+'label.person_name' => 'Name',
+'label.thing_name' => 'Name',
+'label.login' => 'Login',
+'label.password' => 'Password',
+'label.confirm_password' => 'Confirm password',
+'label.email' => 'Email',
+'label.date' => 'Date',
+'label.start_date' => 'Start date',
+'label.end_date' => 'End date',
+'label.user' => 'User',
+'label.users' => 'Users',
+'label.client' => 'Client',
+'label.clients' => 'Clients',
+'label.option' => 'Option',
+'label.invoice' => 'Invoice',
+'label.project' => 'Project',
+'label.projects' => 'Projects',
+'label.task' => 'Task',
+'label.tasks' => 'Tasks',
+'label.description' => 'Description',
+'label.start' => 'Start',
+'label.finish' => 'Finish',
+'label.duration' => 'Duration',
+'label.note' => 'Note',
+'label.item' => 'Item',
+'label.cost' => 'Cost',
+'label.week_total' => 'Week total',
+'label.day_total' => 'Day total',
+'label.today' => 'Today',
+'label.total_hours' => 'Total hours',
+'label.total_cost' => 'Total cost',
+'label.view' => 'View',
+'label.edit' => 'Edit',
+'label.delete' => 'Delete',
+'label.configure' => 'Configure',
+'label.select_all' => 'Select all',
+'label.select_none' => 'Deselect all',
+'label.id' => 'ID',
+'label.language' => 'Language',
+'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'Lock interval in days',
+'label.date_format' => 'Date format',
+'label.time_format' => 'Time format',
+'label.week_start' => 'First day of week',
+'label.comment' => 'Comment',
+'label.status' => 'Status',
+'label.tax' => 'Tax',
+'label.subtotal' => 'Subtotal',
+'label.total' => 'Total',
+'label.client_name' => 'Client name',
+'label.client_address' => 'Client address',
+'label.or' => 'or',
+'label.error' => 'Error',
+'label.ldap_hint' => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+'label.required_fields' => '* - required fields',
+'label.on_behalf' => 'on behalf of',
+'label.role_manager' => '(manager)',
+'label.role_comanager' => '(co-manager)',
+'label.role_admin' => '(administrator)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Custom fields',
+'label.type' => 'Type',
+'label.type_dropdown' => 'dropdown',
+'label.type_text' => 'text',
+'label.required' => 'Required',
+'label.fav_report' => 'Favorite report',
+'label.cron_schedule' => 'Cron schedule',
+'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'Login',
+'title.teams' => 'Teams',
+'title.create_team' => 'Creating Team',
+'title.edit_team' => 'Editing Team',
+'title.delete_team' => 'Deleting Team',
+'title.reset_password' => 'Resetting Password',
+'title.change_password' => 'Changing Password',
+'title.time' => 'Time',
+'title.edit_time_record' => 'Editing Time Record',
+'title.delete_time_record' => 'Deleting Time Record',
+'title.expenses' => 'Expenses',
+'title.edit_expense' => 'Editing Expense Item',
+'title.delete_expense' => 'Deleting Expense Item',
+'title.reports' => 'Reports',
+'title.report' => 'Report',
+'title.send_report' => 'Sending Report',
+'title.invoice' => 'Invoice',
+'title.send_invoice' => 'Sending Invoice',
+'title.charts' => 'Charts',
+'title.projects' => 'Projects',
+'title.add_project' => 'Adding Project',
+'title.edit_project' => 'Editing Project',
+'title.delete_project' => 'Deleting Project',
+'title.tasks' => 'Tasks',
+'title.add_task' => 'Adding Task',
+'title.edit_task' => 'Editing Task',
+'title.delete_task' => 'Deleting Task',
+'title.users' => 'Users',
+'title.add_user' => 'Adding User',
+'title.edit_user' => 'Editing User',
+'title.delete_user' => 'Deleting User',
+'title.clients' => 'Clients',
+'title.add_client' => 'Adding Client',
+'title.edit_client' => 'Editing Client',
+'title.delete_client' => 'Deleting Client',
+'title.invoices' => 'Invoices',
+'title.add_invoice' => 'Adding Invoice',
+'title.view_invoice' => 'Viewing Invoice',
+'title.delete_invoice' => 'Deleting Invoice',
+'title.notifications' => 'Notifications',
+'title.add_notification' => 'Adding Notification',
+'title.edit_notification' => 'Editing Notification',
+'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'Exporting Team Data',
+'title.import' => 'Importing Team Data',
+'title.options' => 'Options',
+'title.profile' => 'Profile',
+'title.cf_custom_fields' => 'Custom Fields',
+'title.cf_add_custom_field' => 'Adding Custom Field',
+'title.cf_edit_custom_field' => 'Editing Custom Field',
+'title.cf_delete_custom_field' => 'Deleting Custom Field',
+'title.cf_dropdown_options' => 'Dropdown Options',
+'title.cf_add_dropdown_option' => 'Adding Option',
+'title.cf_edit_dropdown_option' => 'Editing Option',
+'title.cf_delete_dropdown_option' => 'Deleting Option',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- all ---',
+'dropdown.no' => '--- no ---',
+// NOTE TO TRANSLATORS: dropdown.this_day does not necessarily means "today". It means a specific ("this") day selected on calendar. See Charts.
+'dropdown.this_day' => 'this day',
+'dropdown.this_week' => 'this week',
+'dropdown.last_week' => 'last week',
+'dropdown.this_month' => 'this month',
+'dropdown.last_month' => 'last month',
+'dropdown.this_year' => 'this year',
+'dropdown.all_time' => 'all time',
+'dropdown.projects' => 'projects',
+'dropdown.tasks' => 'tasks',
+'dropdown.clients' => 'clients',
+'dropdown.select' => '--- select ---',
+'dropdown.select_invoice' => '--- select invoice ---',
+'dropdown.status_active' => 'active',
+'dropdown.status_inactive' => 'inactive',
+'dropdown.delete'=>'delete',
+'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Forgot password?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> is a simple, easy to use, open source time tracking system.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Password reset request sent by email.',
+'form.reset_password.email_subject' => 'Anuko Time Tracker password reset request',
+'form.reset_password.email_body' => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Type new password and click on Save.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm or 0.0h)',
+'form.time.billable' => 'Billable',
+'form.time.uncompleted' => 'Uncompleted',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'This record was saved with only start time. It is not an error.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Save as favorite',
+'form.reports.confirm_delete' => 'Are you sure you want to delete this favorite report?',
+'form.reports.include_records' => 'Include records',
+'form.reports.include_billable' => 'billable',
+'form.reports.include_not_billable' => 'not billable',
+'form.reports.include_invoiced' => 'invoiced',
+'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'Select time period',
+'form.reports.set_period' => 'or set dates',
+'form.reports.show_fields' => 'Show fields',
+'form.reports.group_by' => 'Group by',
+'form.reports.group_by_no' => '--- no grouping ---',
+'form.reports.group_by_date' => 'date',
+'form.reports.group_by_user' => 'user',
+'form.reports.group_by_client' => 'client',
+'form.reports.group_by_project' => 'project',
+'form.reports.group_by_task' => 'task',
+'form.reports.totals_only' => 'Totals only',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Export',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Invoice number',
+'form.invoice.person' => 'Person',
+'form.invoice.invoice_to_delete' => 'Invoice to delete',
+'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Interval',
+'form.charts.chart' => 'Chart',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Active Projects',
+'form.projects.inactive_projects' => 'Inactive Projects',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Active Tasks',
+'form.tasks.inactive_tasks' => 'Inactive Tasks',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Active Users',
+'form.users.inactive_users' => 'Inactive Users',
+'form.users.role' => 'Role',
+'form.users.manager' => 'Manager',
+'form.users.comanager' => 'Co-manager',
+'form.users.rate' => 'Rate',
+'form.users.default_rate' => 'Default hourly rate',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+'form.client.client_to_delete' => 'Client to delete',
+'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Active Clients',
+'form.clients.inactive_clients' => 'Inactive Clients',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'You can export all team data into an xml file. It could be useful if you are migrating data to your own server.',
+'form.export.compression' => 'Compression',
+'form.export.compression_none' => 'none',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Import team data from an xml file.',
+'form.import.file' => 'Select file',
+'form.import.success' => 'Import completed successfully.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' =>  'Create a new team by creating a new team manager account.<br>You can also import team data from an xml file from another Anuko Time Tracker server (no login collisions are allowed).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 hours',
+'form.profile.24_hours' => '24 hours',
+'form.profile.tracking_mode' => 'Tracking mode',
+'form.profile.mode_time' => 'time',
+'form.profile.mode_projects' => 'projects',
+'form.profile.mode_projects_and_tasks' => 'projects and tasks',
+'form.profile.record_type' => 'Record type',
+'form.profile.type_all' => 'all',
+'form.profile.type_start_finish' => 'start and finish',
+'form.profile.type_duration' => 'duration',
+'form.profile.plugins' => 'Plugins',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'From',
+'form.mail.to' => 'To',
+'form.mail.cc' => 'Cc',
+'form.mail.subject' => 'Subject',
+'form.mail.report_subject' => 'Time Tracker Report',
+'form.mail.footer' => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+'form.mail.report_sent' => 'Report sent.',
+'form.mail.invoice_sent' => 'Invoice sent.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/es.lang.php b/WEB-INF/resources/es.lang.php
new file mode 100644 (file)
index 0000000..e7f8866
--- /dev/null
@@ -0,0 +1,466 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Español';
+$i18n_months = array('Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre');
+$i18n_weekdays = array('Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado');
+$i18n_weekdays_short = array('Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/06', '04/05', '04/06', '05/01', '08/15', '10/12', '11/01', '12/06', '12/08', '12/25');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Iniciar sesión',
+'menu.logout' => 'Finalizar sesión',
+'menu.forum' => 'Foro',
+'menu.help' => 'Ayuda',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'Crear una nueva cuenta de manejador',
+'menu.profile' => 'Perfil',
+'menu.time' => 'Tiempo',
+// TODO: translate the following string.
+// 'menu.expenses' => 'Expenses',
+'menu.reports' => 'Reportes',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'Charts',
+'menu.projects' => 'Proyectos',
+// TODO: translate menu.tasks.
+// 'menu.tasks' => 'Tasks',
+'menu.users' => 'Personas',
+'menu.teams' => 'Equipos',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'Export',
+'menu.clients' => 'Clientes',
+'menu.options' => 'Opciones',
+
+// Footer - strings on the bottom of most pages.
+// TODO: translate the following strings.
+// 'footer.mobile_phones' => 'Time Tracker is available on mobile phones.',
+// 'footer.credits' => 'Credits',
+// 'footer.license' => 'License',
+
+// Error messages.
+// TODO: translate the following strings.
+// 'error.access_denied' => 'Access denied.',
+// 'error.sys' => 'System error.',
+'error.db' => 'Error de la Base de Datos.',
+'error.field' => 'Dato "{0}" incorrecto.',
+'error.empty' => 'El archivo "{0}" esta vacío.',
+'error.not_equal' => 'El archivo "{0}" no es igual al archivo "{1}".',
+// TODO: translate error.interval.
+// 'error.interval' => 'Field "{0}" must be greater than "{1}".',
+'error.project' => 'Seleccionar Proyecto.',
+// TODO: translate the following strings.
+// 'error.task' => 'Select task.',
+// 'error.client' => 'Select client.',
+// 'error.report' => 'Select report.',
+'error.auth' => 'Usuario o contraseña incorrecta.',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'User with this login already exists.',
+'error.project_exists' => 'Ya existe un proyecto con este nombre.',
+// TODO: translate the following strings.
+// 'error.task_exists' => 'Task with this name already exists.',
+// 'error.client_exists' => 'Client with this name already exists.',
+// 'error.invoice_exists' => 'Invoice with this number already exists.',
+// 'error.no_invoiceable_items' => 'There are no invoiceable items.',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'No existe ningún usuario con este e-mail.',
+'error.no_teams' => 'Su base de datos esta vacía. Inicie sesión como administrador y cree un nuevo grupo.',
+'error.upload' => 'Error subiendo el archivo.',
+// Note to translators: the strings below are missing in the translation and must be added.
+// 'error.period_locked' => 'Can\\\'t complete the operation. Records older than a certain number of days cannot be created or modified. Team manager defines this in the "Lock interval in days" value on the "Profile" page. Set it to 0 to remove locking. <br><br>Uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'Error sending mail.',
+// 'error.no_email' => 'No email associated with this login.',
+// 'error.uncompleted_exists' => 'Uncompleted entry already exists. Close or delete it.',
+// 'error.goto_uncompleted' => 'Go to uncompleted entry.',
+// 'error.overlap' => 'Time interval overlaps with existing records.',
+// 'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'Iniciar sesion',
+'button.now' => 'Ahora',
+'button.save' => 'Guardar',
+// TODO: translate the following string.
+// 'button.copy' => 'Copy',
+'button.cancel' => 'Cancelar',
+'button.submit' => 'Enviar',
+'button.add_user' => 'Agregar usuario ',
+'button.add_project' => 'Agregar proyecto',
+// TODO: translate button.add_task
+// 'button.add_task' => 'Add task',
+'button.add_client' => 'Agregar cliente',
+// TODO: translate the following strings.
+// 'button.add_invoice' => 'Add invoice',
+// 'button.add_option' => 'Add option',
+'button.add' => 'Agregar',
+'button.generate' => 'Generar',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'Ir',
+'button.send' => 'Enviar',
+'button.send_by_email' => 'Enviar por correo',
+'button.create_team' => 'Crear grupo',
+'button.export' => 'Exportar grupo',
+'button.import' => 'Importar grupo',
+// TODO: translate button.close.
+// 'button.close' => 'Close',
+// TODO: translate the following string. 
+// 'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.address' => 'Dirección',
+'label.currency' => 'Moneda',
+// TODO: translate label.manager_name, label.manager_login, and label.login.
+// 'label.manager_name' => 'Manager name',
+// 'label.manager_login' => 'Manager login',
+'label.person_name' => 'Nombre',
+'label.thing_name' => 'Nombre',
+// 'label.login' => 'login',
+'label.password' => 'Contraseña',
+'label.confirm_password' => 'Confirmar Contraseña',
+'label.email' => 'Email',
+'label.date' => 'Fecha',
+'label.start_date' => 'Fecha de inicio',
+'label.end_date' => 'Fecha de fin',
+'label.user' => 'Usuario',
+'label.users' => 'Personas',
+// TODO: translate the following strings.
+// 'label.client' => 'Client',
+// 'label.clients' => 'Clients',
+'label.option' => 'Opción',
+// TODO: translate the following string.
+// 'label.invoice' => 'Invoice',
+'label.project' => 'Proyecto',
+'label.projects' => 'Proyectos',
+// TODO: translate the following strings.
+// 'label.task' => 'Task',
+// 'label.tasks' => 'Tasks',
+// 'label.description' => 'Description',
+'label.start' => 'Inicio',
+'label.finish' => 'Fin',
+'label.duration' => 'Duración',
+'label.note' => 'Nota',
+// TODO: translate the following strings.
+// 'label.item' => 'Item',
+// 'label.cost' => 'Cost',
+// 'label.week_total' => 'Week total',
+// 'label.day_total' => 'Day total',
+'label.today' => 'Hoy',
+'label.total_hours' => 'Horas totales',
+// TODO: translate the following strings.
+// 'label.total_cost' => 'Total cost',
+// 'label.view' => 'View',
+'label.edit' => 'Modificar',
+'label.delete' => 'Eliminar',
+// TODO: translate label.configure.
+// 'label.configure' => 'Configure',
+'label.select_all' => 'Seleccionar todos',
+'label.select_none' => 'Quitar todas las selecciones',
+'label.id' => 'Identificación',
+// TODO: translate the following strings.
+// 'label.language' => 'Language',
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'Intervalo de cierre en días',
+// TODO: translate the following strings.
+// 'label.date_format' => 'Date format',
+// 'label.time_format' => 'Time format',
+// 'label.week_start' => 'First day of week',
+'label.comment' => 'Comentario',
+// TODO: translate label.status.
+// 'label.status' => 'Status',
+'label.tax' => 'Impuesto',
+// TODO: check whether label.subtotal is translated correctly.
+'label.subtotal' => 'Subtotal',
+'label.total' => 'Total',
+// TODO: translate the following strings.
+// 'label.client_name' => 'Client name',
+// 'label.client_address' => 'Client address',
+'label.or' => 'o',
+// TODO: translate the strings below.
+// 'label.error' => 'Error',
+// 'label.ldap_hint' => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+'label.required_fields' => '* - campos requeridos',
+'label.on_behalf' => 'a nombre de',
+'label.role_manager' =>'(manejador)',
+'label.role_comanager' => '(auxiliar del manejador)',
+'label.role_admin' => '(administrador)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+// TODO: translate the following strings.
+// 'label.custom_fields' => 'Custom fields',
+// 'label.type' => 'Type',
+// 'label.type_dropdown' => 'dropdown',
+// 'label.type_text' => 'text',
+// 'label.required' => 'Required',
+'label.fav_report' => 'Reporte favorito',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'Sesión iniciada',
+'title.teams' => 'Grupos',
+// Note to translators: we need a more accurate translation of title.create_team. English is "Creating Team".
+// 'title.create_team' => 'Crear una nueva cuenta de manejador',
+// TODO: translate the following strings.
+// 'title.edit_team' => 'Editing Team',
+// 'title.delete_team' => 'Deleting Team',
+'title.reset_password' => 'Reestablecer contraseña',
+// TODO: translate title.change_password.
+// 'title.change_password' => 'Changing Password',
+'title.time' => 'Tiempo',
+'title.edit_time_record' => 'Modificando el historial de tiempo',
+'title.delete_time_record' => 'Eliminando el historial de tiempo',
+// TODO: translate the following strings.
+// 'title.expenses' => 'Expenses',
+// 'title.edit_expense' => 'Editing Expense Item',
+// 'title.delete_expense' => 'Deleting Expense Item',
+'title.reports' => 'Reportes',
+// TODO: translate title.report, title.send_report.
+// 'title.report' => 'Report',
+// 'title.send_report' => 'Sending Report',
+'title.invoice' => 'Factura',
+// TODO: translate the following strings.
+// 'title.send_invoice' => 'Sending Invoice',
+// 'title.charts' => 'Charts',
+'title.projects' => 'Proyectos',
+'title.add_project' => 'Agregando proyecto',
+'title.edit_project' => 'Modificando proyecto',
+'title.delete_project' => 'Eliminando proyecto',
+// TODO: translate the following strings.
+// 'title.tasks' => 'Tasks',
+// 'title.add_task' => 'Adding Task',
+// 'title.edit_task' => 'Editing Task',
+// 'title.delete_task' => 'Deleting Task',
+'title.users' => 'Personas',
+'title.add_user' => 'Creando usuario',
+'title.edit_user' => 'Modificando usuario',
+'title.delete_user' => 'Eliminando usuario',
+'title.clients' => 'Clientes',
+'title.add_client' => 'Agregar cliente',
+'title.edit_client' => 'Modificar cliente',
+'title.delete_client' => 'Eliminar cliente',
+// TODO: translate the following strings.
+// 'title.invoices' => 'Invoices',
+// 'title.add_invoice' => 'Adding Invoice',
+// 'title.view_invoice' => 'Viewing Invoice',
+// 'title.delete_invoice' => 'Deleting Invoice',
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'Exportar datos',
+'title.import' => 'Importar datos',
+'title.options' => 'Opciones',
+'title.profile' => 'Perfil',
+// TODO: translate the following strings.
+// 'title.cf_custom_fields' => 'Custom Fields',
+// 'title.cf_add_custom_field' => 'Adding Custom Field',
+// 'title.cf_edit_custom_field' => 'Editing Custom Field',
+// 'title.cf_delete_custom_field' => 'Deleting Custom Field',
+// 'title.cf_dropdown_options' => 'Dropdown Options',
+// 'title.cf_add_dropdown_option' => 'Adding Option',
+// 'title.cf_edit_dropdown_option' => 'Editing Option',
+// 'title.cf_delete_dropdown_option' => 'Deleting Option',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- todos ---',
+'dropdown.no' => '--- no ---',
+// TODO: translate dropdown.this_day
+// 'dropdown.this_day' => 'this day',
+'dropdown.this_week' => 'esta semana',
+// TODO: translate dropdown.last_week.
+// 'dropdown.last_week' => 'last week',
+'dropdown.this_month' => 'este mes',
+'dropdown.last_month' => 'el mes pasado',
+// TODO: translate dropdown.this_year and dropdown.all_time.
+// 'dropdown.this_year' => 'this year',
+// 'dropdown.all_time' => 'all time',
+'dropdown.projects' => 'proyectos',
+// TODO: translate the following strings.
+// 'dropdown.tasks' => 'tasks',
+// 'dropdown.clients' => 'clients',
+'dropdown.select' => '--- seleccionar ---',
+// TODO: translate the following strings.
+// 'dropdown.select_invoice' => '--- select invoice ---',
+// 'dropdown.status_active' => 'active',
+// 'dropdown.status_inactive' => 'inactive',
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => '¿Olvido su contraseña?',
+// 'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> is a simple, easy to use, open source time tracking system.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+// TODO: check / improve translation of form.reset_password.message.
+'form.reset_password.message' => 'Se ha enviado la petición de reestablecer contraseña.',
+'form.reset_password.email_subject' => 'Solicitud de reestablecimiento de la contraseña de Anuko Time Tracker',
+// Note to translators: the ending of this string needs to be translated.
+'form.reset_password.email_body' => "Querido usuario, Alguien, posiblemente usted, solicitó reestablecer su contraseña de Anuko Time Tracker. Por favor visite este enlace si quiere reestablecer su contraseña.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+// TODO: improve translation of form.change_password.tip.
+'form.change_password.tip' => 'Para reestablecer su contraseña, por favor digítela y de clic en Guardar.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+// Note to translators: translate form.time.duration_format.
+// 'form.time.duration_format' => '(hh:mm or 0.0h)',
+'form.time.billable' => 'Facturable',
+// TODO: translate form.time.uncompleted.
+// 'form.time.uncompleted' => 'Uncompleted',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Este historial fue guardado solamente con la hora de Inicio. Esto no es un error.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Guardar como favorito',
+// TODO: translate form.reports.confirm_delete.
+// 'form.reports.confirm_delete' => 'Are you sure you want to delete this favorite report?',
+// TODO: translate form.reports.include_records.
+// 'form.reports.include_records' => 'Include records',
+'form.reports.include_billable' => 'facturable',
+'form.reports.include_not_billable' => 'no facturable',
+// TODO: translate the following strings.
+// 'form.reports.include_invoiced' => 'invoiced',
+// 'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'Seleccionar período de tiempo',
+'form.reports.set_period' => 'o establecer fechas',
+'form.reports.show_fields' => 'Mostrar campos',
+'form.reports.group_by' => 'Agrupar por',
+'form.reports.group_by_no' => '--- no agrupar ---',
+'form.reports.group_by_date' => 'fecha',
+'form.reports.group_by_user' => 'usuario',
+// TODO: translate the following string.
+// 'form.reports.group_by_client' => 'client',
+'form.reports.group_by_project' => 'proyecto',
+// TODO: traslate the following string.
+// 'form.reports.group_by_task' => 'task',
+'form.reports.totals_only' => 'Solo totales',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Exportar',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Número de factura',
+'form.invoice.person' => 'Persona',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+// TODO: translate form.charts.interval and form.charts.chart.
+// 'form.charts.interval' => 'Interval',
+// 'form.charts.chart' => 'Chart',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+// TODO: translate the following strings.
+// 'form.projects.active_projects' => 'Active Projects',
+// 'form.projects.inactive_projects' => 'Inactive Projects',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+// 'form.tasks.active_tasks' => 'Active Tasks',
+// 'form.tasks.inactive_tasks' => 'Inactive Tasks',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+// TODO: translate the following strings.
+// 'form.users.active_users' => 'Active Users',
+// 'form.users.inactive_users' => 'Inactive Users',
+'form.users.role' => 'Rol',
+'form.users.manager' => 'Manejador',
+'form.users.comanager' => 'Auxiliar del manejador',
+'form.users.rate' => 'Tasa',
+// TODO: translate form.users.default_rate.
+// 'form.users.default_rate' => 'Default hourly rate',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+// TODO: translate the following strings.
+// 'form.clients.active_clients' => 'Active Clients',
+// 'form.clients.inactive_clients' => 'Inactive Clients',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Usted puede exportar todos los datos del grupo dentro de un archivo xml. Ésto puede ser útil si necesita migrar datos a su propio sevidor.',
+'form.export.compression' => 'Comprimir',
+// Note to translators: the strings below are missing in the translation and must be added.
+// 'form.export.compression_none' => 'none',
+// 'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Importar datos del grupo desde un archivo xml.',
+'form.import.file' => 'Seleccione el archivo',
+'form.import.success' => 'Importación finalizada con éxito.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+// TODO: improve translation of form.admin.hint - no login collisions are allowed.
+'form.teams.hint' => 'Crear un nuevo grupo, creando una nueva cuenta del manejador del equipo.<br>También puede importar datos de grupos, de un archivo xml de otro servidor Anuko Time Tracker (no estan permitidad colisiones de e-mail).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 horas',
+'form.profile.24_hours' => '24 horas',
+// TODO: translate the following strings.
+// 'form.profile.tracking_mode' => 'Tracking mode',
+// 'form.profile.mode_time' => 'time',
+// 'form.profile.mode_projects' => 'projects',
+// 'form.profile.mode_projects_and_tasks' => 'projects and tasks',
+// 'form.profile.record_type' => 'Record type',
+// 'form.profile.type_all' => 'all',
+// 'form.profile.type_start_finish' => 'start and finish',
+// 'form.profile.type_duration' => 'duration',
+// 'form.profile.plugins' => 'Plugins',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'De',
+'form.mail.to' => 'Para',
+'form.mail.cc' => 'Cc',
+'form.mail.subject' => 'Asunta',
+// TODO: translate form.mail.report_subject.
+// 'form.mail.report_subject' => 'Time Tracker Report',
+// Note to translators: the following strings need to be translated.
+// 'form.mail.footer' => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+// 'form.mail.report_sent' => 'Report sent.',
+'form.mail.invoice_sent' => 'Factura enviada.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/et.lang.php b/WEB-INF/resources/et.lang.php
new file mode 100644 (file)
index 0000000..3d709b2
--- /dev/null
@@ -0,0 +1,415 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Eesti';
+$i18n_months = array('jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember');
+$i18n_weekdays = array('pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev');
+$i18n_weekdays_short = array('P', 'E', 'T', 'K', 'N', 'R', 'L');
+// format mm/dd
+$i18n_holidays = array('01/01', '02/24', '04/10', '04/12', '05/01', '05/31', '06/23', '06/24', '08/20', '12/24', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'login',
+'menu.logout' => 'logout',
+'menu.feedback' => 'tagasiside',
+'menu.help' => 'abiinfo',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'loo uus managerikonto',
+'menu.edit_profile' => 'muuda profiili',
+'menu.my_time' => 'minu aeg',
+'menu.reports' => 'raportid',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projektid',
+'menu.activities' => 'tegevused',
+'menu.people' => 'inimesed',
+'menu.teams' => 'meeskonnad',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'kliendid',
+'menu.options' => 'suvandid',
+'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'andmebaasi viga',
+'error.field' => 'valed "{0}" andmed',
+'error.empty' => 'väli "{0}" on tühi',
+'error.not_equal' => 'väli "{0}" ei ole väljaga "{1}" võrdne',
+'error.interval' => 'ebakorrektne intervall',
+'error.project' => 'vali projekt',
+'error.activity' => 'vali tegevus',
+'error.auth' => 'vale login või salasõna',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'selle nimega projekt on juba olemas',
+'error.activity_exists' => 'selle nimega tegevus on juba olemas',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'sellise e-mail aadressiga kasutajat ei ole', 
+'error.upload' => 'viga faili vastuvõtmisel',
+// Note to translators: the strings below are missing and must be added to the translation
+// 'error.period_locked' => 'can\\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'login',
+'button.now' => 'kohe',
+// 'button.set' => 'aseta',
+'button.save' => 'salvesta',
+'button.delete' => 'kustuta',
+'button.cancel' => 'tühista',
+'button.submit' => 'postita',
+'button.add_user' => 'lisa kasutaja',
+'button.add_project' => 'lisa projekt',
+'button.add_activity' => 'lisa tegevus',
+'button.add_client' => 'lisa klient',
+'button.add' => 'lisa',
+'button.generate' => 'loo',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'edasi',
+'button.send' => 'saada',
+'button.send_by_email' => 'saada e-mailiga',
+'button.save_as_new' => 'salvesta uuena',
+'button.create_team' => 'loo meeskond',
+'button.export' => 'ekspordi meeskond',
+'button.import' => 'impordi meeskond',
+'button.apply' => 'rakenda',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'valuuta',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.name' => 'nimi',
+
+'label.password' => 'salasõna',
+'label.confirm_password' => 'kinnita salasõna',
+// 'label.email' => 'email',
+'label.total' => 'kokku',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'lemmikraport',
+"form.filter.filter_new" => 'salvesta lemmikuna',
+// Note to translators: the string below is missing and must be added to the translation
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'login',
+"form.login.login" => 'login',
+
+// password reminder form attributes
+"form.fpass.title" => 'tühjenda salasõna',
+"form.fpass.login" => 'login',
+"form.fpass.send_pass_str" => 'salasõna tühjendamise käsk edastatud',
+// Note to translators: the 3 strings below must be translated
+// "form.fpass.send_pass_subj" => 'AnukoTime Tracker password reset request',
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+// "form.fpass.reset_comment" => "to reset your password please type it in and click on save",
+
+// administrator form
+"form.admin.title" => 'administraator',
+// Note to translators: the string below must be translated
+// "form.admin.duty_text" => 'create a new meeskond by creating a new meeskond manager account.<br>you can also import meeskond data from an xml file from another Anuko time tracker server (no e-mail collisions are allowed).',
+
+"form.admin.change_pass" => 'muuda administraatori konto salasõna',
+"form.admin.profile.title" => 'meeskonnad',
+"form.admin.profile.noprofiles" => 'sinu andmebaas on tühi. logi adminina sisse ja loo uus meeskond.',
+"form.admin.profile.comment" => 'kustuta meeskond',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'nimi',
+"form.admin.profile.th.edit" => 'muuda',
+"form.admin.profile.th.del" => 'kustuta',
+"form.admin.profile.th.active" => 'aktiivne',
+"form.admin.lock.period" => 'lukusta intervall päevades',
+"form.admin.options" => 'suvandid',
+// Note to translators: the strings below are missing in the translation and must be translated
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'minu aeg',
+"form.mytime.edit_title" => 'ajakande muutmine',
+"form.mytime.del_str" => 'ajakande kustutamine',
+// Note to translators: the string below must be translated
+// "form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'kuupäev',
+"form.mytime.project" => 'projekt',
+"form.mytime.activity" => 'tegevus',
+"form.mytime.start" => 'algus',
+"form.mytime.finish" => 'lõpp',
+"form.mytime.duration" => 'kestus',
+"form.mytime.note" => 'märkus',
+// Note to translators: "form.mytime.behalf" => 'igapäevane töö', // the translation is incorrect
+"form.mytime.daily" => 'igapäevane töö',
+"form.mytime.total" => 'tunde kokku: ',
+"form.mytime.th.project" => 'projekt',
+"form.mytime.th.activity" => 'tegevus',
+"form.mytime.th.start" => 'algus',
+"form.mytime.th.finish" => 'lõpp',
+"form.mytime.th.duration" => 'kestus',
+"form.mytime.th.note" => 'märkus',
+"form.mytime.th.edit" => 'muuda',
+"form.mytime.th.delete" => 'kustuta',
+"form.mytime.del_yes" => 'ajakanne kustutatud',
+"form.mytime.no_finished_rec" => 'kanne salvestati ainult alguse ajaga. see ei ole viga. logi välja kui vaja peaks olema.',
+"form.mytime.billable" => 'arvestatav',
+"form.mytime.warn_tozero_rec" => 'see ajakanne tuleb kustutada kuna see ajaperiood on lukustatud',
+// Note to translators: the string below must be translated and added
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'loo uus halduri konto',
+"form.profile.edit_title" => 'profiili muutmine',
+"form.profile.name" => 'nimi',
+"form.profile.login" => 'login',
+
+// Note to translators: the strings below must be translated and added to the localization file
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'inimesed',
+"form.people.createu_str" => 'loo uus kasutaja',
+"form.people.edit_str" => 'kasutaja muutmine',
+"form.people.del_str" => 'kasutaja kustutamine',
+"form.people.th.name" => 'nimi',
+"form.people.th.login" => 'login',
+"form.people.th.role" => 'roll',
+"form.people.th.edit" => 'muuda',
+"form.people.th.del" => 'kustuta',
+"form.people.th.status" => 'seisund',
+"form.people.th.project" => 'projekt',
+"form.people.th.rate" => 'hind',
+"form.people.manager" => 'haldur',
+"form.people.comanager" => 'kaashaldur',
+"form.people.empl" => 'kasutaja',
+"form.people.name" => 'nimi',
+"form.people.login" => 'login',
+
+"form.people.rate" => 'vaikimisi tunni hind',
+"form.people.comanager" => 'kaashaldur',
+"form.people.projects" => 'projektid',
+
+// projects form attributes
+"form.project.proj_title" => 'projektid',
+"form.project.edit_str" => 'projektide muutmine',
+"form.project.add_str" => 'uue projekti lisamine',
+"form.project.del_str" => 'projekti kustutamine',
+"form.project.th.name" => 'nimi',
+"form.project.th.edit" => 'muuda',
+"form.project.th.del" => 'kustuta',
+"form.project.name" => 'nimi',
+
+// activities form attributes
+"form.activity.act_title" => 'tegevus',
+"form.activity.add_title" => 'uue tegevuse lisamine',
+"form.activity.edit_str" => 'tegevuse muutmine',
+"form.activity.del_str" => 'tegevuse kustutamine',
+"form.activity.name" => 'nimi',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'nimi',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'muuda',
+"form.activity.th.del" => 'kustuta',
+
+// report attributes
+"form.report.title" => 'aruanded',
+"form.report.from" => 'algab kuupäevast',
+"form.report.to" => 'lõpeb kuupäeval',
+"form.report.groupby_user" => 'kasutaja',
+"form.report.groupby_project" => 'projekt',
+"form.report.groupby_activity" => 'tegevus',
+"form.report.duration" => 'kestus',
+"form.report.start" => 'algus',
+"form.report.activity" => 'tegevus',
+"form.report.show_idle" => 'näita tühja aega',
+"form.report.finish" => 'lõpp',
+"form.report.note" => 'märkus',
+"form.report.project" => 'projekt',
+"form.report.totals_only" => 'ainult summad',
+"form.report.total" => 'tunde kokku',
+"form.report.th.empllist" => 'kasutaja',
+"form.report.th.date" => 'kuupäev',
+"form.report.th.project" => 'projekt',
+"form.report.th.activity" => 'tegevus',
+"form.report.th.start" => 'algus',
+"form.report.th.finish" => 'lõpp',
+"form.report.th.duration" => 'kestus',
+"form.report.th.note" => 'märkus',
+
+// mail form attributes
+"form.mail.from" => 'kellelt',
+"form.mail.to" => 'kellele',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'teema',
+"form.mail.comment" => 'märkus',
+"form.mail.above" => 'saada aruanne e-mailiga',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>teade saadetud</b>',
+
+// invoice attributes
+"form.invoice.title" => 'arve',
+"form.invoice.caption" => 'arve',
+"form.invoice.above" => 'lisainformatsioon arvele',
+"form.invoice.select_cust" => 'vali klient',
+"form.invoice.fillform" => 'täida väljad',
+"form.invoice.date" => 'kuupäev',
+"form.invoice.number" => 'arve number',
+"form.invoice.tax" => 'maks',
+"form.invoice.daily_subtotals" => 'igapäevased vahesummad',
+"form.invoice.yourcoo" => 'sinu nimi<br> ja aadress',
+"form.invoice.custcoo" => 'kliendi nimi<br> ja aadress',
+"form.invoice.comment" => 'kommentaar ',
+"form.invoice.th.username" => 'isik',
+"form.invoice.th.time" => 'tunde',
+"form.invoice.th.rate" => 'hind',
+"form.invoice.th.summ" => 'summa',
+"form.invoice.subtotal" => 'vahesumma',
+"form.invoice.customer" => 'klient',
+"form.invoice.mailinv_above" => 'saada see arve e-mailiga',
+"form.invoice.sending_str" => '<b>arve saadetud</b>',
+
+"form.migration.zip" => 'pakkimine',
+"form.migration.file" => 'vali fail',
+"form.migration.import.title" => 'impordi andmed',
+"form.migration.import.success" => 'andmed imporditud',
+"form.migration.import.text" => 'impordi meeskonna andmed xml-failist',
+"form.migration.export.title" => 'ekspordi andmed',
+"form.migration.export.success" => 'andmed eksporditud',
+"form.migration.export.text" => 'võid kogu meeskonna andmed eksportida xml-faili. sellest võib olla kasu kui vahetad serverit.',
+// Note to translators: the string below must be translated and added
+// "form.migration.compression.none" => 'none',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'kliendid',
+"form.client.add_title" => 'lisa klient',
+"form.client.edit_title" => 'muuda klienti',
+"form.client.del_title" => 'kustuta klient',
+"form.client.th.name" => 'nimi',
+"form.client.th.edit" => 'muuda',
+"form.client.th.del" => 'kustuta',
+"form.client.name" => 'nimi',
+"form.client.tax" => 'maks',
+"form.client.daily_subtotals" => 'igapäevased vahesummad',
+"form.client.yourcoo" => 'sinu nimi<br> ja aadress arvel',
+"form.client.custcoo" => 'aadress',
+"form.client.comment" => 'märkus ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'unustasid salasõna?',
+"forward.edit" => 'muuda',
+"forward.delete" => 'kustuta',
+"forward.tocsvfile" => 'ekspordi andmed .csv faili',
+"forward.toxmlfile" => 'ekspordi andmed .xml faili',
+"forward.geninvoice" => 'loo arve',
+"forward.change" => 'konfigureeri kliendid',
+
+// strings inside contols on forms
+"controls.select.project" => '--- vali projekt ---',
+"controls.select.activity" => '--- vali tegevus ---',
+"controls.select.client" => '--- vali klient ---',
+"controls.project_bind" => '--- kõik ---',
+"controls.all" => '--- kõik ---',
+"controls.notbind" => '--- ei ---',
+"controls.per_tm" => 'käesolev kuu',
+"controls.per_lm" => 'eelmine kuu',
+"controls.per_tw" => 'käesolev nädal',
+"controls.per_lw" => 'eelmine nädal',
+"controls.per_td" => 'täna',
+"controls.per_at" => 'kõik ajad',
+// Note to translators: the string below must be translated and added
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- vali ajaperiood ---',
+"controls.sel_groupby" => '--- ilma grupeerimata ---',
+"controls.inc_billable" => 'arvestatav',
+"controls.inc_nbillable" => 'mittearvestatav',
+// Note to translators: the string below must be translated and added
+// "controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'tegevused kasutajal',
+// Note to translators: the string below is missing and must be translated and added
+// "label.chart.title2" => 'projects for user',
+"label.chart.period" => 'tabel perioodiks',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>on behalf of %s</b>',
+"label.pminfo" => ' (haldur)',
+"label.pcminfo" => ' (kaashaldur)',
+"label.painfo" => ' (administraator)',
+"label.time_noentry" => 'sissekanne puudub',
+"label.today" => 'täna',
+"label.req_fields" => '* nõutud väljad',
+"label.sel_project" => 'vali projekt',
+"label.sel_activity" => 'vali tegevus',
+"label.sel_tp" => 'vali ajaperiood',
+"label.set_tp" => 'või märgi kuupäevad',
+"label.fields" => 'näita välju',
+"label.group_title" => 'grupeeri',
+"label.include_title" => 'kaasa kanded',
+"label.inv_str" => 'arved',
+"label.set_empl" => 'vali kasutajad',
+"label.sel_all" => 'vali kõik',
+"label.sel_none" => 'märgi kõik mittevalituks',
+"label.or" => 'või',
+"label.disable" => 'keela',
+"label.enable" => 'luba',
+"label.filter" => 'filtreeri',
+"label.timeweek" => 'nädalane summa',
+// Note to translators: the strings below must be translated and added to the localization file
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/fa.lang.php b/WEB-INF/resources/fa.lang.php
new file mode 100644 (file)
index 0000000..5cbfa9e
--- /dev/null
@@ -0,0 +1,418 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'فارسی';
+$i18n_months = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
+$i18n_weekdays = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+$i18n_weekdays_short = array('Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/16', '02/20', '05/28', '07/04', '09/03', '10/10', '11/11', '11/24', '12/25');
+
+$i18n_key_words = array(
+'language.rtl' => 'true', // Right-to-left language. Do not remove this line from RTL language files. This is the only string that is not found in the master English file.
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'ورود',
+'menu.logout' => 'خروج',
+'menu.forum' => 'فروم',
+'menu.help' => 'راهنما',
+'menu.create_team' => 'ایجاد گروه',
+'menu.profile' => 'پروفايل',
+'menu.time' => 'زمان',
+'menu.expenses' => 'هزينه ها',
+'menu.reports' => 'گزارشات',
+'menu.charts' => 'نمودارها',
+'menu.projects' => 'پروژه ها',
+'menu.tasks' => 'وظايف',
+'menu.users' => 'کاربران',
+'menu.teams' => 'گروه ها',
+'menu.export' => 'پشتیبانی',
+'menu.clients' => 'مشتری ها',
+'menu.options' => 'تنظیمات',
+
+// Footer - strings on the bottom of most pages.
+// TODO: translate the following strings.
+'footer.mobile_phones' => 'Time Tracker is available on mobile phones.',
+'footer.credits' => 'Credits',
+'footer.license' => 'License',
+
+// Error messages.
+// TODO: translate the following string.
+// 'error.access_denied' => 'Access denied.',
+'error.sys' => 'خطا در سیستم.',
+'error.db' => 'خطا در پایگاه داده.',
+'error.field' => 'داده اشتباه در "{0}".',
+'error.empty' => 'فیلد "{0}" خالیست.',
+'error.not_equal' => 'فیلد "{0}" با فیلد "{1}" برابر نیست.',
+// TODO: translate error.interval.
+'error.interval' => 'Field "{0}" must be greater than "{1}".',
+'error.project' => 'انتخاب پروژه.',
+'error.task' => 'انتخاب وظیفه.',
+'error.client' => 'انتخاب مشتری.',
+// TODO: translate the following string.
+// 'error.report' => 'Select report.',
+'error.auth' => 'نام کاربری یا رمز عبور اشتباه است.',
+'error.user_exists' => 'کاربری با این نام کاربری موجود است.',
+'error.project_exists' => 'پروژه ای با این نام موجود است.',
+'error.task_exists' => 'وظیفه ای با این نام هم اکنون وجود دارد.',
+'error.client_exists' => 'مشتری با این نام هم اکنون وجود دارد.',
+'error.invoice_exists' => 'فاکتوری با این شماره هم اکنون موجود است.',
+'error.no_invoiceable_items' => 'آیتمی جهت فاکتور کردن وجود ندارد.',
+'error.no_login' => 'کاربری با این نام کاربری موجود نیست.',
+'error.no_teams' => 'پایگاه داده شما خالی است با کاربر admin وارد شوید و تیم ایجاد کنید.',
+'error.upload' => 'خطا در آپلود فایل.',
+'error.period_locked' => 'کامل کردن عملیات مقدور نیست. رکورد های قدیمی تر از تعداد محدودی روز را نمی توان ایجاد یا اصلاح کرد.مدیر تیم در پروفایل خود قسمت "قفل بازه زمانی" قادر به تغییر این مقدار است. برای غیر فعال کردن قفل بازه زمانی مقدار صفر را درج کنید.<br><br>رکوردهای ناتمام بدون زمان یا با زمان صفر می تواند حذف شود.',
+'error.mail_send' => 'خطا در ارسال ایمیل.',
+'error.no_email' => 'ایمیل مرتبط با این نام کاربری موجود نیست.',
+// TODO: check translation and punctuation of error.uncompleted_exists. Is the sentence ending dot in the right place?
+'error.uncompleted_exists' => 'قسمت ناتمامی موجود است. آن را تمام یا حذف کنید.',
+'error.goto_uncompleted' => 'مراجعه به قسمت ناتمام.',
+'error.overlap' => 'بازه زمانی با سوابق موجود هم پوشانی دارد.',
+// TODO: translate the following string.
+// 'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'ورود',
+'button.now' => 'هم اکنون',
+'button.save' => 'ذخیره',
+'button.copy' => 'کپی',
+'button.cancel' => 'لغو',
+'button.submit' => 'ثبت',
+'button.add_user' => 'درج کاربر',
+'button.add_project' => 'درج پروژه',
+'button.add_task' => 'درج وظیفه',
+'button.add_client' => 'درج مشتری',
+'button.add_invoice' => 'درج فاکتور',
+'button.add_option' => 'درج گزینه',
+'button.add' => 'درج',
+'button.generate' => 'تولید',
+'button.reset_password' => 'بازسازی رمزعبور',
+'button.send' => 'ارسال',
+'button.send_by_email' => 'ارسال به ایمیل',
+'button.create_team' => 'ایجاد تیم',
+'button.export' => 'ایجاد پشتیبان از تیم',
+'button.import' => 'وارد کردن تیم',
+'button.close' => 'بستن',
+'button.stop' => 'توقف',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'نام تیم',
+'label.address' => 'آدرس',
+'label.currency' => 'واحد پول',
+'label.manager_name' => 'نام مدیر',
+'label.manager_login' => 'نام کاربری مدیر',
+'label.person_name' => 'نام',
+'label.thing_name' => 'نام',
+'label.login' => 'نام کاربری',
+'label.password' => 'رمز عبور',
+'label.confirm_password' => 'تکرار رمزعبور',
+'label.email' => 'ایمیل',
+'label.date' => 'تاریخ',
+'label.start_date' => 'تاریخ شروع',
+'label.end_date' => 'تاریخ اتمام',
+'label.user' => 'کاربر',
+'label.users' => 'کاربران',
+'label.client' => 'مشتری',
+'label.clients' => 'مشتریان',
+// TODO: translate the following string.
+// 'label.option' => 'Option',
+'label.invoice' => 'فاکتور',
+'label.project' => 'پروژه',
+'label.projects' => 'پروژه ها',
+'label.task' => 'وظیفه',
+'label.tasks' => 'وظیفه ها',
+'label.description' => 'شرح',
+'label.start' => 'شروع',
+'label.finish' => 'اتمام',
+'label.duration' => 'مدت زمان',
+'label.note' => 'توضیح',
+'label.item' => 'آیتم',
+'label.cost' => 'هزینه',
+'label.week_total' => 'کل هفته',
+'label.day_total' => 'کل روز',
+'label.today' => 'امروز',
+'label.total_hours' => 'مجموع ساعت',
+'label.total_cost' => 'مجموع هزینه ها',
+'label.view' => 'نمایش',
+'label.edit' => 'ویرایش',
+'label.delete' => 'حذف',
+'label.configure' => 'پیکربندی',
+'label.select_all' => 'انتخاب همه',
+'label.select_none' => 'لغو انتخاب همه',
+'label.id' => 'شناسه',
+'label.language' => 'زبان',
+// TODO: translate the following string.
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'قفل کردن  فرصت بازه زمانی',
+'label.date_format' => 'قالب تاریخ',
+'label.time_format' => 'قالب زمان',
+'label.week_start' => 'روز اول هفته',
+'label.comment' => 'توضیح',
+'label.status' => 'وضعیت',
+'label.tax' => 'مالیات',
+'label.subtotal' => 'جمع جز',
+'label.total' => 'کل',
+'label.client_name' => 'نام مشتری',
+'label.client_address' => 'آدرس مشتری',
+'label.or' => 'یا',
+'label.error' => 'خطا',
+'label.ldap_hint' => '<b>نام کاربری ویندوز</b>و <b>رمزعبور</b>خود را در فیلدهای زیر وارد کنید',
+'label.required_fields' => '* - فیلد های اجباری',
+'label.on_behalf' => 'از دیدگاه',
+'label.role_manager' => '(مدیر)',
+'label.role_comanager' => '(دستیار مدیر)',
+'label.role_admin' => '(مدیر ارشد)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'فیلدهای سفارشی',
+'label.type' => 'نوع',
+'label.type_dropdown' => 'منو کشویی',
+'label.type_text' => 'متن',
+'label.required' => 'اجباری',
+'label.fav_report' => 'گزارش های برگزیده',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'ورود',
+'title.teams' => 'تیم ها',
+'title.create_team' => 'ایجاد تیم',
+// TODO: translate the following string.
+// 'title.edit_team' => 'Editing Team',
+'title.delete_team' => 'حذف تیم',
+'title.reset_password' => 'بازیابی رمزعبور',
+'title.change_password' => 'تغییر رمزعبور',
+'title.time' => 'زمان',
+'title.edit_time_record' => 'ویرایش رکورد زمان',
+'title.delete_time_record' => 'حذف رکورد زمان',
+'title.expenses' => 'هزینه ها',
+'title.edit_expense' => 'ویرایش آیتم هزینه ها',
+'title.delete_expense' => 'حذف آیتم هزینه ها',
+'title.reports' => 'گزارشات',
+'title.report' => 'گزارش',
+'title.send_report' => 'ارسال گزارش',
+'title.invoice' => 'فاکتور',
+'title.send_invoice' => 'ارسال فاکتور',
+'title.charts' => 'نمودارها',
+'title.projects' => 'پروژه ها',
+'title.add_project' => 'درج پروژه',
+'title.edit_project' => 'ویرایش پروژه',
+'title.delete_project' => 'حذف پروژه',
+'title.tasks' => 'وظایف',
+'title.add_task' => 'درج وظیفه',
+'title.edit_task' => 'ویرایش وظیفه',
+'title.delete_task' => 'حذف وظیفه',
+'title.users' => 'کاربران',
+'title.add_user' => 'درج کاربر',
+'title.edit_user' => 'ویرایش کاربر',
+'title.delete_user' => 'حذف کاربر',
+'title.clients' => 'مشتریان',
+'title.add_client' => 'درج مشتری',
+'title.edit_client' => 'ویرایش مشتری',
+'title.delete_client' => 'حذف مشتری',
+'title.invoices' => 'فاکتورها',
+'title.add_invoice' => 'درج فاکتور',
+'title.view_invoice' => 'نمایش فاکتور',
+'title.delete_invoice' => 'حذف فاکتور',
+// TODO: translate the following strings.
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'پشتیانی گرفتن از اطلاعات تیم',
+'title.import' => 'وارد کردن اطلاعات تیم',
+'title.options' => 'گزینه ها',
+'title.profile' => 'پروفایل',
+'title.cf_custom_fields' => 'فیلدهای سفارشی',
+'title.cf_add_custom_field' => 'درج فیلد سفارشی',
+'title.cf_edit_custom_field' => 'ویرایش فیلد سفارشی',
+'title.cf_delete_custom_field' => 'حذف فیلد سفارشی',
+'title.cf_dropdown_options' => 'گزینه های منو کشویی',
+'title.cf_add_dropdown_option' => 'درج گزینه',
+'title.cf_edit_dropdown_option' => 'ویرایش گزینه',
+'title.cf_delete_dropdown_option' => 'حذف گزینه',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- همه ---',
+'dropdown.no' => '--- هیچکدام ---',
+// TODO: check translation of dropdown.this_day. It does not necessarily means "today". It means a specific ("this") day selected on calendar. See charts.php.
+// 'dropdown.this_day' => 'امروز',
+'dropdown.this_week' => 'هفته جاری',
+'dropdown.last_week' => 'هفته آخر',
+'dropdown.this_month' => 'ماه جاری',
+'dropdown.last_month' => 'ماه آخر',
+'dropdown.this_year' => 'سال جاری',
+'dropdown.all_time' => 'همه زمان ها',
+'dropdown.projects' => 'پروژه ها',
+'dropdown.tasks' => 'وظایف',
+'dropdown.clients' => 'مشتریان',
+// TODO: translate the following string.
+// 'dropdown.select' => '--- select ---',
+'dropdown.select_invoice' => '--- انتخاب فاکتور ---',
+'dropdown.status_active' => 'فعال',
+'dropdown.status_inactive' => 'غیرفعال',
+// TODO: translate the following strings.
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'بازیابی رمز عبور؟',
+// TODO: translate form.login.about.
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> is a simple, easy to use, open source time tracking system.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'درخواست بازیابی رمزعبور به ایمیل فرستاده شد.',
+// TODO: check translation of form.reset_password.email_subject. This is the subject for email message for password reset. Below is the English original.
+// 'form.reset_password.email_subject' => 'Anuko Time Tracker password reset request',
+'form.reset_password.email_subject' => 'درخواست بازیابی رمزعبور فرستاده شد',
+'form.reset_password.email_body' => "کاربران گرامی\n\n یک نفر، شاید خودتان، درخواست بازیابی رمزعبور نرم افزار رهگیری زمان شما را داشته است.لطفا برای تغییر رمزعبور روی لینک زیر کلیک کنید: \n\n%s\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'رمز عبور جدید را وارد کنید سپس روی ذخیره کلیک کنید',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm یا 0.0h)',
+'form.time.billable' => 'قابل پرداخت',
+'form.time.uncompleted' => 'ناتمام',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+// TODO: translate form.time_edit.uncompleted. 
+'form.time_edit.uncompleted' => 'This record was saved with only start time. It is not an error.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'ذخیره به عنوان برگزیده',
+'form.reports.confirm_delete' => 'آیا می خواهید گزارش برگزیده حذف شود؟',
+'form.reports.include_records' => 'شامل رکوردهای',
+'form.reports.include_billable' => 'قابل پرداخت',
+'form.reports.include_not_billable' => 'غیرقابل پرداخت',
+'form.reports.select_period' => 'انتخاب بازه زمانی',
+'form.reports.set_period' => 'یا تعیین تاریخ',
+'form.reports.show_fields' => 'نمایش فیلدها',
+'form.reports.group_by' => 'گروه بندی شده با',
+'form.reports.group_by_no' => '--- بدون گروه ---',
+'form.reports.group_by_date' => 'تاریخ',
+'form.reports.group_by_user' => 'کاربر',
+'form.reports.group_by_client' => 'مشتری',
+'form.reports.group_by_project' => 'پروژه',
+'form.reports.group_by_task' => 'وظیفه',
+// TODO: translate form.reports.totals_only. Selecting this option means to print subtotals only for a "grouped by" report.
+// In other words, items are not printed, only subtotals for grouped items are printed.  
+'form.reports.totals_only' => 'Totals only',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'پشتیبانی',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'شماره فاکتور',
+'form.invoice.person' => 'شخص',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'بازه',
+'form.charts.chart' => 'نمودار',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'پروژه های فعال',
+'form.projects.inactive_projects' => 'پروژه های غیرفعال',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'وظایف فعال',
+'form.tasks.inactive_tasks' => 'وظایف غیرفعال',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'کاربران فعال',
+'form.users.inactive_users' => 'کاربران غیرفعال',
+'form.users.role' => 'سمت',
+'form.users.manager' => 'مدیر',
+'form.users.comanager' => 'دستیار مدیر',
+'form.users.rate' => 'نرخ',
+'form.users.default_rate' => 'نرخ ساعتی پیش فرض',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'مشتری های فعال',
+'form.clients.inactive_clients' => 'مشتری های غیرفعال',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'می توانید از همه اطلاعات تیم یک پشتیبان به فرمت xml تهیه کنید. اگر میخواهید داده ها را به سرور خودتان منتقل کنید این قسمت می تواند مفید باشد.',
+'form.export.compression' => 'فشرده سازی',
+'form.export.compression_none' => 'هیچ کدام',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'وارد کردن اطلاعات تیم از یک فایل xml',
+'form.import.file' => 'انتخاب فایل',
+'form.import.success' => 'وارد کردن اطلاعات با موفقیت انجام شد',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+// TODO: translate form.teams.hint.
+'form.teams.hint' =>  'Create a new team by creating a new team manager account.<br>You can also import team data from an xml file from another Anuko Time Tracker server (no login collisions are allowed).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 ساعت',
+'form.profile.24_hours' => '24 ساعت',
+'form.profile.tracking_mode' => 'حالت رهگیری',
+'form.profile.mode_time' => 'زمان',
+'form.profile.mode_projects' => 'پروژه ها',
+'form.profile.mode_projects_and_tasks' => 'پروژه ها و وظایف',
+'form.profile.record_type' => 'نوع رکورد',
+'form.profile.type_all' => 'همه',
+'form.profile.type_start_finish' => 'شروع و اتمام',
+'form.profile.type_duration' => 'مدت زمان',
+'form.profile.plugins' => 'پلاگین ها',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'از',
+'form.mail.to' => 'به',
+'form.mail.cc' => 'کپی',
+'form.mail.subject' => 'موضوع',
+'form.mail.report_subject' => 'گزارش تایم شیت',
+// TODO: translate form.mail.footer.
+// 'form.mail.footer' => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+'form.mail.report_sent' => 'گزارش ارسال شد.',
+'form.mail.invoice_sent' => 'فاکتور ارسال شد.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/fi.lang.php b/WEB-INF/resources/fi.lang.php
new file mode 100644 (file)
index 0000000..b8f9055
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+$i18n_language = 'Suomi';
+$i18n_months = array('Tammikuu', 'Helmikuu', 'Maaliskuu', 'Huhtikuu', 'Toukokuu', 'Kesäkuu', 'Heinäkuu', 'Elokuu', 'Syyskuu', 'Lokakuu', 'Marraskuu', 'Joulukuu');
+$i18n_weekdays = array('Sunnuntai', 'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai');
+$i18n_weekdays_short = array('su', 'ma', 'ti', 'ke', 'to', 'pe', 'la');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/06', '05/01', '06/24', '12/06', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Kirjaudu',
+'menu.logout' => 'Kirjaudu ulos',
+'menu.forum' => 'Keskustelupalsta',
+'menu.help' => 'Apua',
+'menu.create_team' => 'Luo tiimi',
+'menu.profile' => 'Profiili',
+'menu.time' => 'Tunnit',
+// TODO: translate the following string.
+// 'menu.expenses' => 'Expenses',
+'menu.reports' => 'Raportit',
+'menu.charts' => 'Kaaviot',
+'menu.projects' => 'Projektit',
+'menu.tasks' => 'Tehtävät',
+'menu.users' => 'Käyttäjät',
+'menu.teams' => 'Tiimit',
+'menu.export' => 'Vie',
+'menu.clients' => 'Asiakkaat',
+'menu.options' => 'Optiot',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker on saatavana myös mobiililaitteille.',
+// 'Credits' is a bit difficult to translate, the exact literal term might be 'Ansiot' or 'Antaa tunnustus' but that's not widely used in this meaning.
+// The term that is used is something like 'We are thanking' ('Kiitämme') but that does not sound good, either. So I just let it be as it was for time being as everybody (?) understands the meaning anyway.
+'footer.credits' => 'Credits',
+'footer.license' => 'Lisenssi',
+
+// Error messages.
+'error.access_denied' => 'Pääsy estetty.',
+'error.sys' => 'Järjestelmävirhe.',
+'error.db' => 'Tietokantavirhe.',
+'error.field' => 'Virheellinen "{0}" tieto.',
+'error.empty' => 'Kenttä "{0}" on tyhjä.',
+'error.not_equal' => 'Kentät "{0}" ja "{1}" eivät ole samat.',
+'error.interval' => 'Kentän "{0}" arvon tulee olla suurempi kuin kentän "{1}".',
+'error.project' => 'Valitse projekti.',
+'error.task' => 'Valitse tehtävä.',
+'error.client' => 'Valitse asiakas.',
+'error.report' => 'Valitse raportti.',
+'error.auth' => 'Virheellinen käyttäjänimi tai salasana.',
+'error.user_exists' => 'Tämä käyttäjänimi on jo olemassa.',
+'error.project_exists' => 'Tämän niminen projekti on jo olemassa.',
+'error.task_exists' => 'Tämän niminen tehtävä on jo olemassa.',
+'error.client_exists' => 'Tämän niminen asiakas on jo olemassa.',
+'error.invoice_exists' => 'Tällä numerolla oleva lasku on jo olemassa.',
+'error.no_invoiceable_items' => 'Ei laskutettavia syötteitä.',
+'error.no_login' => 'Tuntematon käyttäjänimi.',
+'error.no_teams' => 'Tietokanta on tyhjä. Kirjaudu ylläpitäjänä ja luo uusi tiimi.',
+'error.upload' => 'Virhe tiedoston lataus.',
+'error.period_locked' => 'Toimintoa ei voi suorittaa. Liian vanhojen tietueiden päivitys tai luonti ei onnistu. Tiimin esimies asettaa "Lukitusväli päivinä" arvon "Profiili"-sivulla. Arvolla 0 lukitusta ei tehdä. <br><br>Keskeneräiset tietueet (kestona 0 tai määrittelemätön) voidaan poistaa.',
+'error.mail_send' => 'Virhe postinlähetyksessä.',
+'error.no_email' => 'Käyttäjätunnukseen ei ole liitetty sähköpostiosoitetta.',
+'error.uncompleted_exists' => 'Kesken oleva syötetieto on jo olemassa. Sulje tai poista se.',
+'error.goto_uncompleted' => 'Siirry kesken olevaan syötteeseen.',
+'error.overlap' => 'Aikavälillä on päällekkäisiä syötteitä.',
+'error.future_date' => 'Aika on tulevaisuudessa.',
+
+// Labels for buttons.
+'button.login' => 'Kirjaudu',
+'button.now' => 'Nyt',
+'button.save' => 'Tallenna',
+'button.copy' => 'Kopioi',
+'button.cancel' => 'Keskeytä',
+'button.submit' => 'Hyväksy',
+'button.add_user' => 'Lisää käyttäjä',
+'button.add_project' => 'Lisää projekti',
+'button.add_task' => 'Lisää tehtävä',
+'button.add_client' => 'Lisää asiakas',
+'button.add_invoice' => 'Lisää lasku',
+'button.add_option' => 'Lisää optio',
+'button.add' => 'Lisää',
+'button.generate' => 'Luo',
+'button.reset_password' => 'Nollaa salasana',
+'button.send' => 'Lähetä',
+'button.send_by_email' => 'Lähetä sähköpostilla',
+'button.create_team' => 'Luo tiimi',
+'button.export' => 'Vie tiimi',
+'button.import' => 'Tuo tiimi',
+'button.close' => 'Sulje',
+'button.stop' => 'Lopeta',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Tiimin nimi',
+'label.address' => 'Osoite',
+'label.currency' => 'Valuutta',
+'label.manager_name' => 'Esimies',
+'label.manager_login' => 'Esimiehen käyttäjätunnus',
+'label.person_name' => 'Nimi',
+'label.thing_name' => 'Nimi',
+'label.login' => 'Käyttäjätunnus',
+'label.password' => 'Salasana',
+'label.confirm_password' => 'Vahvista salasana',
+'label.email' => 'Sähköposti',
+'label.date' => 'Päiväys',
+'label.start_date' => 'Aloituspäivä',
+'label.end_date' => 'Päättymispäivä',
+'label.user' => 'Käyttäjä',
+'label.users' => 'Käyttäjät',
+'label.client' => 'Asiakas',
+'label.clients' => 'Asiakkaat',
+'label.option' => 'Optio',
+'label.invoice' => 'Lasku',
+'label.project' => 'Projekti',
+'label.projects' => 'Projektit',
+'label.task' => 'Tehtävä',
+'label.tasks' => 'Tehtävät',
+'label.description' => 'Kuvaus',
+'label.start' => 'Aloitus',
+'label.finish' => 'Lopetus',
+'label.duration' => 'Kesto',
+'label.note' => 'Huom',
+'label.item' => 'Syöte',
+'label.cost' => 'Hinta',
+'label.week_total' => 'Viikko yhteensä',
+'label.day_total' => 'Päivä yhteensä',
+'label.today' => 'Tänään',
+'label.total_hours' => 'Tunnit yhteensä',
+'label.total_cost' => 'Hinta yhteensä',
+'label.view' => 'Näytä',
+'label.edit' => 'Muokkaa',
+'label.delete' => 'Poista',
+'label.configure' => 'Aseta',
+'label.select_all' => 'Valitse kaikki',
+'label.select_none' => 'Poista kaikki valinnat',
+'label.id' => 'ID',
+'label.language' => 'Kieli',
+'label.decimal_mark' => 'Desimaalierotin',
+'label.lock_interval' => 'Lukitusväli päivinä',
+'label.date_format' => 'Päiväyksen muoto',
+'label.time_format' => 'Kellonajan muoto',
+'label.week_start' => 'Viikon 1. päivä',
+'label.comment' => 'Kommentti',
+'label.status' => 'Tila',
+'label.tax' => 'Vero',
+'label.subtotal' => 'Välisumma',
+'label.total' => 'Yhteensä',
+'label.client_name' => 'Asiakkaan nimi',
+'label.client_address' => 'Asiakkaan osoite',
+'label.or' => 'tai',
+'label.error' => 'Virhe',
+'label.ldap_hint' => 'Syötä <b>Windows-käyttäjätunnuksesi</b> ja <b>salasanasi</b> ao. kenttiin.',
+'label.required_fields' => '* - pakolliset kentät',
+'label.on_behalf' => 'puolesta',
+'label.role_manager' => '(esimies)',
+'label.role_comanager' => '(apu-esimies)',
+'label.role_admin' => '(ylläpitäjä)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Omat kentät',
+'label.type' => 'Tyyppi',
+'label.type_dropdown' => 'pudotusvalikko',
+'label.type_text' => 'teksti',
+'label.required' => 'Pakollinen',
+'label.fav_report' => 'Raporttipohja',
+'label.cron_schedule' => 'Cron-ajoitus',
+'label.what_is_it' => 'Mikä se on?',
+
+// Form titles.
+'title.login' => 'Kirjautuminen',
+'title.teams' => 'Tiimit',
+'title.create_team' => 'Tiimin luonti',
+'title.edit_team' => 'Tiimin muokkaus',
+'title.delete_team' => 'Tiimin poisto',
+'title.reset_password' => 'Salasanan nollaus',
+'title.change_password' => 'Salasanan vaihto',
+'title.time' => 'Tuntien kirjaus',
+'title.edit_time_record' => 'Tuntikirjausten muokkaus',
+'title.delete_time_record' => 'Tuntikirjausten poisto',
+'title.expenses' => 'Kulut',
+'title.edit_expense' => 'Kulutietojen muokkaus',
+'title.delete_expense' => 'Kulutiedon poisto',
+'title.reports' => 'Raportit',
+'title.report' => 'Raportti',
+'title.send_report' => 'Raportin lähetys',
+'title.invoice' => 'Lasku',
+'title.send_invoice' => 'Laskun lähetys',
+'title.charts' => 'Kaaviot',
+'title.projects' => 'Projektit',
+'title.add_project' => 'Projektin lisäys',
+'title.edit_project' => 'Projektin muokkaus',
+'title.delete_project' => 'Projektin poisto',
+'title.tasks' => 'Tehtävät',
+'title.add_task' => 'Tehtävän lisäys',
+'title.edit_task' => 'Tehtävän muokkaus',
+'title.delete_task' => 'Tehtävän poisto',
+'title.users' => 'Käyttäjät',
+'title.add_user' => 'Käyttäjän lisäys',
+'title.edit_user' => 'Käyttäjän muokkaus',
+'title.delete_user' => 'Käyttäjän poisto',
+'title.clients' => 'Asiakkaat',
+'title.add_client' => 'Asiakkaan lisäys',
+'title.edit_client' => 'Asiakkaan muokkaus',
+'title.delete_client' => 'Asiakkaan poisto',
+'title.invoices' => 'Laskut',
+'title.add_invoice' => 'Laskun lisäys',
+'title.view_invoice' => 'Laskun tarkastelu',
+'title.delete_invoice' => 'Laskun poisto',
+'title.notifications' => 'Ilmoitukset',
+'title.add_notification' => 'Ilmoituksen lisäys',
+'title.edit_notification' => 'Ilmoituksen muokkaus',
+'title.delete_notification' => 'Ilmoituksen poisto',
+'title.export' => 'Tiimitietojen vienti',
+'title.import' => 'Tiimitietojen tunti',
+'title.options' => 'Optiot',
+'title.profile' => 'Profiili',
+'title.cf_custom_fields' => 'Omat kentät',
+'title.cf_add_custom_field' => 'Oman kentän lisäys',
+'title.cf_edit_custom_field' => 'Oman kentän muokkaus',
+'title.cf_delete_custom_field' => 'Oman kentän poisto',
+'title.cf_dropdown_options' => 'Pudotusvalikon vaihtoehdot',
+'title.cf_add_dropdown_option' => 'Vaihtoehdon lisäys',
+'title.cf_edit_dropdown_option' => 'Vaihtoehdon muokkaus',
+'title.cf_delete_dropdown_option' => 'Vaihtoehdon poisto',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- kaikki ---',
+'dropdown.no' => '--- ei ---',
+'dropdown.this_day' => 'tämä päivä',
+'dropdown.this_week' => 'tämä viikko',
+'dropdown.last_week' => 'viime viikko',
+'dropdown.this_month' => 'tämä kuu',
+'dropdown.last_month' => 'viime kuu',
+'dropdown.this_year' => 'tämä vuosi',
+'dropdown.all_time' => 'kaikki tunnit',
+'dropdown.projects' => 'projektit',
+'dropdown.tasks' => 'tehtävät',
+'dropdown.clients' => 'asiakkaat',
+'dropdown.select' => '--- valitse ---',
+'dropdown.select_invoice' => '--- valitse lasku ---',
+'dropdown.status_active' => 'aktiivinen',
+'dropdown.status_inactive' => 'inaktiivinen',
+'dropdown.delete'=>'poista',
+'dropdown.do_not_delete'=>'älä poista',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Salasana hukassa?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> on yksinkertainen ja helppokäyttöinen vapaan koodin tuntiseurantaohjelmisto.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Salasanan nollauspyyntöviesti lähetetty.',
+'form.reset_password.email_subject' => 'Anuko Time Tracker -salasanan nollauspyyntö',
+'form.reset_password.email_body' => "Hyvä käyttäjä,\n\nJoku, mahdollisesti sinä itse, on pyytänyt nollaamaan Anuko Time Tracker -ohjelman salasanasi. Jos haluat nollata salasanasi, käy sivulla \n\n%s\n\nAnuko Time Tracker on yksinkertainen ja helppokäyttöinen vapaan koodin tuntiseurantaohjelmisto. Lisätietoja sivulla https://www.anuko.com.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Syötä uusi salasana ja osoita Tallenna.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm tai 0.0h)',
+'form.time.billable' => 'Laskutettava',
+'form.time.uncompleted' => 'Keskeneräinen',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Vain aloitusaika tallennettiin tietueeseen. Kyseessä ei ole virhe.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Tallenna raporttipohjaksi',
+'form.reports.confirm_delete' => 'Haluatko varmasti poistaa tämän raporttipohjan?',
+'form.reports.include_records' => 'Sisällytä tietueet',
+'form.reports.include_billable' => 'laskutettavat',
+'form.reports.include_not_billable' => 'ei-laskutettavat',
+'form.reports.include_invoiced' => 'laskutettu',
+'form.reports.include_not_invoiced' => 'laskuttamatta',
+'form.reports.select_period' => 'Valitse ajanjakso',
+'form.reports.set_period' => 'tai aseta päivät',
+'form.reports.show_fields' => 'Näytä kentät',
+'form.reports.group_by' => 'Ryhmittelyperuste',
+'form.reports.group_by_no' => '--- ei ryhmitystä ---',
+'form.reports.group_by_date' => 'päivä',
+'form.reports.group_by_user' => 'käyttäjä',
+'form.reports.group_by_client' => 'asiakas',
+'form.reports.group_by_project' => 'projekti',
+'form.reports.group_by_task' => 'tehtävä',
+'form.reports.totals_only' => 'Vain yhteissummat',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Vie',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Laskun numero',
+'form.invoice.person' => 'Henkilö',
+'form.invoice.invoice_to_delete' => 'Poistettava lasku',
+'form.invoice.invoice_entries' => 'Laskurivit',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Ajalta',
+'form.charts.chart' => 'Kaavio',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Aktiiviset projektit',
+'form.projects.inactive_projects' => 'Ei-aktiiviset projektit',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Aktiiviset tehtävät',
+'form.tasks.inactive_tasks' => 'Ei-aktiiviset tehtävät',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Aktiiviset käyttäjät',
+'form.users.inactive_users' => 'Ei-aktiiviset käyttäjät',
+'form.users.role' => 'Rooli',
+'form.users.manager' => 'Esimies',
+'form.users.comanager' => 'Apuesimies',
+'form.users.rate' => 'Taksa',
+'form.users.default_rate' => 'Oletustuntitaksa',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+'form.client.client_to_delete' => 'Poistettava asiakas',
+'form.client.client_entries' => 'Asiakassyötteet',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Aktiiviset asiakkaat',
+'form.clients.inactive_clients' => 'Ei-aktiiviset asiakkaat',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Voit viedä tiimin tiedot xml-tiedostoksi, mikä voi helpottaa tietojen siirtoa omalle palvelimelle.',
+'form.export.compression' => 'Pakkaus',
+'form.export.compression_none' => 'ei pakata',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Tuo tiimitiedot xml-tiedostosta.',
+'form.import.file' => 'Valitse tiedosto',
+'form.import.success' => 'Tietojen tuonti onnistui.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' =>  'Luo uusi tiimi luomalla ensin tiimin esimiehen käyttäjätili.<br>Tiimin tiedot voi myös tuoda toiselta Anuko Time Tracker -palvelimelta xml-muodossa (käyttäjänimien oltava uusia).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12-tuntinen',
+'form.profile.24_hours' => '24-tuntinen',
+'form.profile.tracking_mode' => 'Seurantamuoto',
+'form.profile.mode_time' => 'aika',
+'form.profile.mode_projects' => 'projektit',
+'form.profile.mode_projects_and_tasks' => 'projektit ja tehtävät',
+'form.profile.record_type' => 'Tietueen tyyppi',
+'form.profile.type_all' => 'kaikki',
+'form.profile.type_start_finish' => 'aloitus ja lopetus',
+'form.profile.type_duration' => 'kesto',
+'form.profile.plugins' => 'Lisäosat',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'Lähettäjä',
+'form.mail.to' => 'Vastaanottaja',
+'form.mail.cc' => 'Kopio',
+'form.mail.subject' => 'Aihe',
+'form.mail.report_subject' => 'Time Tracker -raportti',
+'form.mail.footer' => 'Anuko Time Tracker on yksinkertainen ja helppokäyttöinen vapaan koodin tuntiseurantaohjelmisto. Lisätietoja sivulla <a href="https://www.anuko.com">www.anuko.com</a>.',
+'form.mail.report_sent' => 'Raportti lähetetty.',
+'form.mail.invoice_sent' => 'Lasku lähetetty.',
+);
+?>
diff --git a/WEB-INF/resources/fr.lang.php b/WEB-INF/resources/fr.lang.php
new file mode 100644 (file)
index 0000000..09f38aa
--- /dev/null
@@ -0,0 +1,437 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+$i18n_language = 'Français';
+$i18n_months = array('Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'); 
+$i18n_weekdays = array('Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi');
+$i18n_weekdays_short = array('Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/06', '04/09', '05/01', '05/08', '05/17', '05/28', '07/14', '08/15', '11/01', '11/11', '12/25');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Connexion',
+'menu.logout' => 'Quitter',
+'menu.forum' => 'Forum',
+'menu.help' => 'Aide',
+// Note to translators: menu.create_team needs a more accurate translation.
+// Meaning that we are now creating a "team", not a manager account. The translation should reflect that (creating a TEAM).
+// 'menu.create_team' => 'Créer un compte pour un responsable',
+'menu.profile' => 'Profil',
+'menu.time' => 'Temps',
+// TODO: translate the following string.
+// 'menu.expenses' => 'Expenses',
+'menu.reports' => 'Rapports',
+'menu.charts' => 'Graphiques',
+'menu.projects' => 'Projets',
+'menu.tasks' => 'Tâches',
+'menu.users' => 'Personnes',
+'menu.teams' => 'Équipes',
+'menu.export' => 'Export',
+'menu.clients' => 'Clients',
+'menu.options' => 'Options',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker est disponible sur les smartphones.',
+'footer.credits' => 'Credits',
+'footer.license' => 'License',
+
+// Error messages.
+// TODO: translate the following string.
+// 'error.access_denied' => 'Access denied.',
+'error.sys' => 'Erreur système.',
+'error.db' => 'Erreur de base de données.',
+'error.field' => 'Données "{0}" incorrecte.',
+'error.empty' => 'Le champ "{0}" est vide.',
+'error.not_equal' => 'Le champ "{0}" n\\\'est pas égal au champ "{1}".',
+// TODO: trabslate error.interval.
+// 'error.interval' => 'Field "{0}" must be greater than "{1}".',
+'error.project' => 'Sélectionner un projet.',
+'error.task' => 'Selectionner une tâche.',
+'error.client' => 'Selectionner un client.',
+// TODO: translate the following string.
+// 'error.report' => 'Select report.',
+'error.auth' => 'Nom d\\\'utilisateur ou mot de passe incorrect.',
+'error.user_exists' => 'Un utilisateur avec ce login existe déjà.',
+'error.project_exists' => 'Un projet avec ce nom existe déjà.',
+'error.task_exists' => 'Une tâche avec ce nom existe déjà.',
+'error.client_exists' => 'Un client avec ce nom existe déjà.',
+'error.invoice_exists' => 'Une facture avec ce numéro existe déjà.',
+'error.no_invoiceable_items' => 'Il n\\\'y a pas d\\\'éléments à facturer.',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// Meaning that email should not be referenced at all here. There are no user with this LOGIN (not email).
+// 'error.no_login' => 'Aucun utilisateur avec cette adresse email.',
+'error.no_teams' => 'Votre base de données est vide. Connectez-vous comme administrateur et créez une nouvelle équipe.',
+'error.upload' => 'Erreur de chargement de fichier.',
+'error.period_locked' => 'Impossible de terminer l\\\'opération. Les éléments datés de plus d\\\'un certain nombre de jours ne peuvent être créés ou modifiés. Le responsable d\\\'équipe définit cela dans la valeur "Intervalle de verrouillage en jours" dans la page "Profil". Mettre à 0 pour enlever le verrou. <br><br>Les éléments non terminés (avec une durée à 0 ou vide) peuvent être supprimés.',
+'error.mail_send' => 'Erreur durant l\\\'envoi de l\\\'e-mail.',
+'error.no_email' => 'Aucun e-mail associé à ce login.',
+'error.uncompleted_exists' => 'Une entrée non terminée existe déjà. Fermer ou supprimer.',
+'error.goto_uncompleted' => 'Aller à l\\\'entrée non terminée.',
+// TODO: translate the following strings.
+// 'error.overlap' => 'Time interval overlaps with existing records.',
+// 'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'Connexion',
+'button.now' => 'Maintenant',
+'button.save' => 'Sauvegarder',
+// TODO: translate the following string.
+// 'button.copy' => 'Copy',
+'button.cancel' => 'Annuler',
+'button.submit' => 'Soumettre',
+'button.add_user' => 'Ajouter un utilisateur',
+'button.add_project' => 'Ajouter un projet',
+'button.add_task' => 'Ajouter une tâche',
+'button.add_client' => 'Ajouter un client',
+'button.add_invoice' => 'Ajouter une facture',
+'button.add_option' => 'Ajouter une option',
+'button.add' => 'Ajouter',
+'button.generate' => 'Générer',
+'button.reset_password' => 'Réinitialiser',
+'button.send' => 'Envoyer',
+'button.send_by_email' => 'Envoyer par email',
+'button.create_team' => 'Créer une équipe',
+'button.export' => 'Exporter l\\\'équipe',
+'button.import' => 'Importer une équipe',
+'button.close' => 'Fermer',
+// TODO: translate the following string. 
+// 'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Nom équipe',
+'label.address' => 'Adresse',
+'label.currency' => 'Devise',
+'label.manager_name' => 'Nom du responsable',
+'label.manager_login' => 'Identifiant responsable',
+'label.person_name' => 'Nom',
+'label.thing_name' => 'Nom',
+'label.login' => 'Identifiant',
+'label.password' => 'Mot de passe',
+'label.confirm_password' => 'Confirmez le mot de passe',
+'label.email' => 'Email',
+'label.date' => 'Date',
+'label.start_date' => 'Date de début',
+'label.end_date' => 'Date de fin',
+'label.user' => 'Utilisateur',
+'label.users' => 'Personnes',
+'label.client' => 'Client',
+'label.clients' => 'Clients',
+'label.option' => 'Option',
+'label.invoice' => 'Facture',
+'label.project' => 'Projet',
+'label.projects' => 'Projets',
+'label.task' => 'Tâche',
+'label.tasks' => 'Tâches',
+'label.description' => 'Description',
+'label.start' => 'Début',
+'label.finish' => 'Fin',
+'label.duration' => 'Durée',
+'label.note' => 'Note',
+// TODO: translate label.item
+// 'label.item' => 'Item',
+'label.cost' => 'Coût',
+'label.week_total' => 'Total hebdomadaire',
+// TODO: translate the following string.
+// 'label.day_total' => 'Day total',
+'label.today' => 'Aujourd\\\'hui',
+'label.total_hours' => 'Total d\\\'heures',
+'label.total_cost' => 'Coût total',
+'label.view' => 'Voir',
+'label.edit' => 'Modifier',
+'label.delete' => 'Supprimer',
+'label.configure' => 'Configurer',
+'label.select_all' => 'Tout sélectionner',
+'label.select_none' => 'Tout désélectionner',
+'label.id' => 'ID',
+'label.language' => 'Langage',
+// TODO: translate the following string.
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'Intervalle de verrouillage en jours',
+'label.date_format' => 'Format date',
+'label.time_format' => 'Format heure',
+'label.week_start' => '1er jour de la semaine',
+'label.comment' => 'Commentaire',
+'label.status' => 'Statut',
+'label.tax' => 'Taxe',
+'label.subtotal' => 'Sous-total',
+'label.total' => 'Total',
+'label.client_name' => 'Nom du client',
+'label.client_address' => 'Adresse du client',
+'label.or' => 'ou',
+'label.error' => 'Erreur',
+'label.ldap_hint' => 'Entrer votre <b>Identifiant Windows</b> et <b>votre mot de passe</b> dans les champs suivants.',
+'label.required_fields' => '* - champs obligatoires',
+'label.on_behalf' => 'de la part de',
+'label.role_manager' => '(responsable)',
+'label.role_comanager' => '(co-responsable)',
+'label.role_admin' => '(administrateur)',
+
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Champs personalisés',
+'label.type' => 'Type',
+'label.type_dropdown' => 'liste déroulante',
+'label.type_text' => 'texte',
+'label.required' => 'Obligatoire',
+'label.fav_report' => 'Rapport favori',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'Connexion',
+'title.teams' => 'Équipes',
+// TODO: for consistency, the titles below need to be refactored. Recommended way is using nouns such as in 'Modification du projet', not 'Modifier la tâche'.
+// Notice usage of the noun Modification versus the verb Modifier.
+'title.create_team' => 'Créez une nouvelle équipe', // TODO: use a noun.
+// TODO: translate the following string.
+// 'title.edit_team' => 'Editing Team',
+'title.delete_team' => 'Suppression de l\\\'équipe',
+'title.reset_password' => 'Réinitialisation du mot de passer',
+'title.change_password' => 'Modification du mot de passe',
+'title.time' => 'Temps',
+'title.edit_time_record' => 'Édition du l\\\'entrée de temps',
+'title.delete_time_record' => 'Supprimer l\\\'entrée de temps', // TODO: use a noun.
+// TODO: translate the following strings.
+// 'title.expenses' => 'Expenses',
+// 'title.edit_expense' => 'Editing Expense Item',
+// 'title.delete_expense' => 'Deleting Expense Item',
+'title.reports' => 'Rapports',
+'title.report' => 'Rapport',
+'title.send_report' => 'Envoyer rapport', // TODO: use a noun.
+'title.invoice' => 'Facture',
+'title.send_invoice' => 'Envoi de la facture',
+'title.charts' => 'Graphiques',
+'title.projects' => 'Projets',
+'title.add_project' => 'Ajout du projet',
+'title.edit_project' => 'Modification du projet',
+'title.delete_project' => 'Suppression du projet',
+'title.tasks' => 'Tâches',
+'title.add_task' => 'Ajouter une tâche', // TODO: use a noun.
+'title.edit_task' => 'Modifier la tâche', // TODO: use a noun.
+'title.delete_task' => 'Supprimer une tâche', // TODO: use a noun.
+'title.users' => 'Personnes',
+'title.add_user' => 'Création de l\\\'utilisateur',
+'title.edit_user' => 'Modification de l\\\'utilisateur',
+'title.delete_user' => 'Suppression de l\\\'utilisateur',
+'title.clients' => 'Clients',
+'title.add_client' => 'Ajouter un client', // TODO: use a noun.
+'title.edit_client' => 'Modifier un client', // TODO: use a noun.
+'title.delete_client' => 'Supprimer un client', // TODO: use a noun.
+'title.invoices' => 'Factures',
+'title.add_invoice' => 'Ajouter une facture', // TODO: use a noun.
+'title.view_invoice' => 'Voir la facture', // TODO: use a noun.
+'title.delete_invoice' => 'Supprimer la facture', // TODO: use a noun.
+// TODO: translate the following strings.
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'Exporter les données', // TODO: use a noun.
+'title.import' => 'Importer les données', // TODO: use a noun.
+'title.options' => 'Options',
+'title.profile' => 'Profil',
+'title.cf_custom_fields' => 'Champs personalisés',
+'title.cf_add_custom_field' => 'Ajouter un champ', // TODO: use a noun.
+'title.cf_edit_custom_field' => 'Editer un champ', // TODO: use a noun.
+'title.cf_delete_custom_field' => 'Supprimer un champ', // TODO: use a noun.
+'title.cf_dropdown_options' => 'Options de liste',
+'title.cf_add_dropdown_option' => 'Ajouter une option', // TODO: use a noun.
+'title.cf_edit_dropdown_option' => 'Modifier l\\\'option', // TODO: use a noun.
+'title.cf_delete_dropdown_option' => 'Supprimer l\\\'option', // TODO: use a noun.
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- tous ---',
+'dropdown.no' => '--- non ---',
+'dropdown.this_day' => 'ce jour',
+'dropdown.this_week' => 'cette semaine',
+'dropdown.last_week' => 'la semaine passée',
+'dropdown.this_month' => 'ce mois',
+'dropdown.last_month' => 'le mois passé',
+'dropdown.this_year' => 'cette année',
+'dropdown.all_time' => 'depuis toujours',
+'dropdown.projects' => 'projets',
+'dropdown.tasks' => 'tâches',
+// TODO: translate the following string.
+// 'dropdown.clients' => 'clients',
+'dropdown.select' =>  '--- choisir ---',
+// TODO: translate the following string.
+// 'dropdown.select_invoice' => '--- select invoice ---',
+'dropdown.status_active' => 'actif',
+'dropdown.status_inactive' => 'inactif',
+// TODO: translate the following strings.
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Mot de passe oublié?',
+// TODO: check translation of the form.login.about. It does not look like a complete sentence. The English equivalent is:
+// 'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> is a simple, easy to use, open source time tracking system.'
+// 'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> système de gestion des temps open source simple et facile à utiliser.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+// TODO: improve translation of form.reset_password.message and form.reset_password.email_subject.
+// The English equivalents have changed. This means that French translations below are now inaccurate. The difference is that
+// we no longer send passwords, but RESET REQUESTS. Compare with master English file:
+// 'form.reset_password.message' => 'Password reset request sent by email.',
+// 'form.reset_password.email_subject' => 'Anuko Time Tracker password reset request',
+'form.reset_password.message' => 'Le mot de passe a été envoyé.', // TODO: redo this line, see the comment above.
+'form.reset_password.email_subject' => 'Votre mot de passe Anuko Time Tracker', // TODO: redo this line, see the comment above.
+// Note to translators: the sentence BEFORE LAST in form.reset_password.email_body needs to be translated.
+'form.reset_password.email_body' => "Cher utilisateur,\n\nQuelqu\'un, probablement vous, avez demandé une réinitialisation de votre mot de passe. Merci d\'aller à ce lien pour le réinitialiser\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visiter https://www.anuko.com pour plus d\'informations.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+// TODO: improve translation of form.change_password.tip.
+// The English line is: 'form.change_password.tip' => 'Type new password and click on Save.',
+'form.change_password.tip' => 'Pour réinitialiser votre mot de passe, saisissez le et cliquez sur Sauvegarder.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm ou 0.0h)',
+'form.time.billable' => 'Facturable',
+'form.time.uncompleted' => 'Non terminée',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Cet enregistrement a été enregistré avec seulement une heure de début. Il ne s\\\'agit pas d\\\'une erreur.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Enregistrer comme favori',
+'form.reports.confirm_delete' => 'Êtes-vous sur de vouloir supprimer ce rapport favori?',
+'form.reports.include_records' => 'Inclure les enregistrements',
+'form.reports.include_billable' => 'facturables',
+'form.reports.include_not_billable' => 'non facturables',
+// TODO: translate the following strings.
+// 'form.reports.include_invoiced' => 'invoiced',
+// 'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'Choisissez la période de temps', // TODO: should this be Choisir as above?
+'form.reports.set_period' => 'ou dates indiquées',
+'form.reports.show_fields' => 'Montrer les champs',
+'form.reports.group_by' => 'Regroupés par',
+'form.reports.group_by_no' => '--- aucun regroupage ---',
+'form.reports.group_by_date' => 'date',
+'form.reports.group_by_user' => 'utilisateur',
+'form.reports.group_by_client' => 'client',
+'form.reports.group_by_project' => 'projet',
+'form.reports.group_by_task' => 'tâche',
+'form.reports.totals_only' => 'Totaux uniquement',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Export',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Numéro de facture',
+'form.invoice.person' => 'Personne',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Intervalle',
+'form.charts.chart' => 'Graphique',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Projets actifs',
+'form.projects.inactive_projects' => 'Projets inactifs',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+// 'form.tasks.active_tasks' => 'Active Tasks',
+// 'form.tasks.inactive_tasks' => 'Inactive Tasks',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+// TODO: translate the following strings.
+// 'form.users.active_users' => 'Active Users',
+// 'form.users.inactive_users' => 'Inactive Users',
+'form.users.role' => 'Rôle',
+'form.users.manager' => 'Responsable',
+'form.users.comanager' => 'Co-responsable',
+'form.users.rate' => 'Tarif',
+'form.users.default_rate' => 'Tarif horaire standard',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+// TODO: translate the following strings.
+// 'form.clients.active_clients' => 'Active Clients',
+// 'form.clients.inactive_clients' => 'Inactive Clients',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Vous pouvez exporter toute les données d\\\'une équipe dans un ficheir xml. Cela peut être utile si vous transférer des données vers votre serveur.',
+'form.export.compression' => 'Compression',
+'form.export.compression_none' => 'aucune',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Importer les donnés des équipes depuis un fichier xml.',
+'form.import.file' => 'Sélectionner le fichier',
+'form.import.success' => 'Import réussi.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+// TODO: improve translation of form.admin.hint. No login collisions are allowed (not email).
+// Current translations says that no emails may collide. The truth is that no LOGINS (not emails) may collide. Emails actually MAY collide. The English string is:
+// 'form.teams.hint' =>  'Create a new team by creating a new team manager account.<br>You can also import team data from an xml file from another Anuko Time Tracker server (no login collisions are allowed).',
+'form.teams.hint' => 'Créer une nouvelle équipe en créant un nouveau compte de responsable d\\\'équipe.<br>Vous pouvez également importer des données sur une équipe depuis un fichier xml provenant d\\\'un autre serveur Anuko Time Tracker (les doublons d\\\'email ne sont pas autorisés).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 heures',
+'form.profile.24_hours' => '24 heures',
+// TODO: translate the following strings.
+// 'form.profile.tracking_mode' => 'Tracking mode',
+// 'form.profile.mode_time' => 'time',
+// 'form.profile.mode_projects' => 'projects',
+// 'form.profile.mode_projects_and_tasks' => 'projects and tasks',
+// 'form.profile.record_type' => 'Record type',
+// 'form.profile.type_all' => 'all',
+// 'form.profile.type_start_finish' => 'start and finish',
+// 'form.profile.type_duration' => 'duration',
+'form.profile.plugins' => 'Plugins',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'De',
+'form.mail.to' => 'À',
+'form.mail.cc' => 'Cc',
+'form.mail.subject' => 'Objet',
+'form.mail.report_subject' => 'Rapport Time Tracker',
+// Note to translators: the following strings need to be translated.
+// The problem seems to be that the first sentence does not seem like a complete sentence.
+// 'form.mail.footer' => 'Anuko Time Tracker système de gestion des temps open source simple et facile à utiliser. Visiter <a href="https://www.anuko.com">www.anuko.com</a> pour plus d\\\'informations.',
+'form.mail.report_sent' => 'Le rapport a été envoyé.',
+'form.mail.invoice_sent' => 'La facture a été envoyée.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/he.lang.php b/WEB-INF/resources/he.lang.php
new file mode 100644 (file)
index 0000000..389ac05
--- /dev/null
@@ -0,0 +1,417 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'עברית';
+$i18n_months = array('ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר');
+$i18n_weekdays = array('ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת');
+$i18n_weekdays_short = array('א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז');
+// format mm/dd
+$i18n_holidays = array('02/10', '04/09', '04/15', '04/29', '05/29', '09/19', '09/20', '09/28', '10/03', '10/10');
+
+$i18n_key_words = array(
+'language.rtl' => 'true', // Right-to-left language. Do not remove this line from RTL language files. This is the only string that is not found in the master English file.
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'כניסה',
+'menu.logout' => 'יציאה',
+'menu.forum' => 'פורום',
+'menu.help' => 'עזרה',
+'menu.create_team' => 'צור צוות',
+'menu.profile' => 'פרופיל',
+'menu.time' => 'זמן',
+// TODO: translate the following string.
+// 'menu.expenses' => 'Expenses',
+'menu.reports' => 'דוחות',
+'menu.charts' => 'תרשימים',
+'menu.projects' => 'פרוייקטים',
+'menu.tasks' => 'משימות',
+'menu.users' => 'משתמשים',
+'menu.teams' => 'צוותים',
+'menu.export' => 'ייצוא',
+'menu.clients' => 'לקוחות',
+'menu.options' => 'אפשרויות',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'הינו זמין לטלפונים חכמים Time Tracker',
+'footer.credits' => 'קרדיטס',
+'footer.license' => 'רשיון',
+
+// Error messages.
+// TODO: translate the following string.
+// 'error.access_denied' => 'Access denied.',
+'error.sys' => 'שגיאת מערכת',
+'error.db' => 'שגיאה של בסיס הנתונים',
+'error.field' => 'נתון "{0}" שגוי',
+'error.empty' => 'השדה "{0}" ריק',
+'error.not_equal' => 'השדה "{1}" אינו שווה לשדה "{0}"',
+// TODO: add quotes around field names in error.interval.
+'error.interval' => 'השדדה {0} צריך להיות גדול יותר מהשדה {1}',
+// TO TEST: change the string on a local Time Tracker and then try to enter a time with
+// end time less than start time. For example: Start time: 09:00, End time: 08:00.
+// Then you should see the error on screen and the problems will be clearly visible.
+'error.project' => 'בחר פרוייקט',
+'error.task' => 'בחר משימה',
+'error.client' => 'בחר לקוח',
+// TODO: translate the following string.
+// 'error.report' => 'Select report.',
+'error.auth' => 'שם משתמש או סיסמה שגויים',
+'error.user_exists' => 'שם משתמש כבר קיים',
+'error.project_exists' => 'שם פרוייקט כבר קיים',
+'error.task_exists' => 'קיימת משימה עם שם דומה',
+'error.client_exists' => 'שם לקוח כבר קיים',
+'error.invoice_exists' => 'קיימת חשבונית עם מספר זה',
+'error.no_invoiceable_items' => 'אין פריטים לחיוב',
+'error.no_login' => 'משתמש זה אינו קיים',
+'error.no_teams' => 'בסיס הנתונים שלך ריק. התחבר כמנהל וצור צוות חדש',
+'error.upload' => 'שגיאה בהעלת קובץ',
+'error.period_locked' => 'אין אפשרות להשלים את הפעולה. הרישומיםאינם ניתנים לשינוי או מחיקה. מנהל צוות יכול לשנות הגדרה (טווח ימים לשינוי או מחיקה) זו בדף הפרופיל שלו. ערך של 0 מנטרל הגדרה זו. <br><br>רישומים חלקיים (עם משך זמן שווה ל-0 או ריק) ניתנים למחיקה',
+'error.mail_send' => 'שגיאה בשליחת הדואר אלקטרוני',
+'error.no_email' => 'אין דואר אלקטרוני השייך לשם משתמש זה',
+'error.uncompleted_exists' => 'רישום חלקי כבר קיים. סגור או מחק אותו.',
+'error.goto_uncompleted' => 'פתח את הרישום החלקי.',
+'error.overlap' => 'טווח הזמן מתנגש עם רישומים קיימים.',
+// TODO: translate the following string.
+// 'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'היכנס',
+'button.now' => 'עכשיו',
+'button.save' => 'שמור',
+'button.copy' => 'העתק',
+'button.cancel' => 'ביטול',
+'button.submit' => 'שלח',
+'button.add_user' => 'הוסף משתמש',
+'button.add_project' => 'הוסף פרוייקט',
+'button.add_task' => 'הוסף משימה',
+'button.add_client' => 'הוסף לקוח',
+'button.add_invoice' => 'הוסף חשבונית',
+'button.add_option' => 'הוסף אפשרות',
+'button.add' => 'הוסף',
+'button.generate' => 'הרץ',
+'button.reset_password' => 'איפוס סיסמה',
+'button.send' => 'שלח',
+'button.send_by_email' => 'שלח בדואר אלקטרוני',
+'button.create_team' => 'צור צוות',
+'button.export' => 'ייצא צוות',
+'button.import' => 'ייבא צוות',
+'button.close' => 'סגור',
+'button.stop' => 'עצור',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'שם הצוות',
+'label.address' => 'כתובת',
+'label.currency' => 'מטבע',
+'label.manager_name' => 'שם של המנהל',
+'label.manager_login' => 'שם משתמש של המנהל',
+'label.person_name' => 'שם',
+'label.thing_name' => 'שם',
+'label.login' => 'שם משתמש',
+'label.password' => 'סיסמה',
+'label.confirm_password' => 'בדיקת סיסמה',
+'label.email' => 'דואר אלקטרוני',
+'label.date' => 'תאריך',
+'label.start_date' => 'תאריך התחלה',
+'label.end_date' => 'תאריך סיום',
+'label.user' => 'משתמש',
+'label.users' => 'משתמשים',
+'label.client' => 'לקוח',
+'label.clients' => 'לקוחות',
+// TODO: translate the following string.
+// 'label.option' => 'Option',
+'label.invoice' => 'חשבונית',
+'label.project' => 'פרוייקט',
+'label.projects' => 'פרוייקטים',
+'label.task' => 'משימה',
+'label.tasks' => 'משימות',
+'label.description' => 'תיאור',
+'label.start' => 'התחלה',
+'label.finish' => 'סיום',
+'label.duration' => 'משך זמן',
+'label.note' => 'הערה',
+// TODO: translate label.item
+// 'label.item' => 'Item',
+'label.cost' => 'עלות',
+'label.week_total' => 'סיכום שבועי',
+'label.day_total' => 'סיכום יומי',
+'label.today' => 'היום',
+'label.total_hours' => 'סך הכל שעות',
+'label.total_cost' => 'סך הכל עלות',
+'label.view' => 'הצג',
+'label.edit' => 'ערוך',
+'label.delete' => 'מחק',
+'label.configure' => 'הגדר',
+'label.select_all' => 'בחר הכל',
+'label.select_none' => 'בטל בחירה',
+'label.id' => 'מזהה',
+'label.language' => 'שפה',
+// TODO: translate the following string.
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'טווח זמן לנעילה',
+'label.date_format' => 'תבנית של תאריך',
+'label.time_format' => 'תבנית של שעה',
+'label.week_start' => 'היום הראשון בשבוע',
+'label.comment' => 'הערה',
+'label.status' => 'סטטוס',
+'label.tax' => 'מעמ',
+'label.subtotal' => 'סיכום חלקי',
+'label.total' => 'סך הכל',
+'label.client_name' => 'שם הלקוח',
+'label.client_address' => 'כתובת הלקוח',
+'label.or' => 'או',
+'label.error' => 'שגיאה',
+'label.ldap_hint' => 'הכנס את <b>שם המשתמש</b> ואת <b>הסיסמה</b> של ווינדוז בשדות.',
+'label.required_fields' => '* - שדות חובה',
+'label.on_behalf' => 'מטעם',
+'label.role_manager' => '(מנהל)',
+'label.role_comanager' => '(מנהל משנה)',
+'label.role_admin' => '(מנהל המערכת)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'שדות אישיים',
+'label.type' => 'סוג',
+'label.type_dropdown' => 'רשימה',
+'label.type_text' => 'טקסט',
+'label.required' => 'חובה',
+'label.fav_report' => 'דוח מועדף',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'כניסה',
+'title.teams' => 'צוותים',
+'title.create_team' => 'יצירת צוות',
+// TODO: translate the following string.
+// 'title.edit_team' => 'Editing Team',
+'title.delete_team' => 'מחיקת צוות',
+'title.reset_password' => 'איפוס סיסמה',
+'title.change_password' => 'שינוי סיסמה',
+'title.time' => 'זמן',
+'title.edit_time_record' => 'עריכת רשומה',
+'title.delete_time_record' => 'מחיקת רשומה',
+// TODO: translate the following strings.
+// 'title.expenses' => 'Expenses',
+// 'title.edit_expense' => 'Editing Expense Item',
+// 'title.delete_expense' => 'Deleting Expense Item',
+'title.reports' => 'דוחות',
+'title.report' => 'דוח',
+'title.send_report' => 'שליחת דוח',
+'title.invoice' => 'חשבונית',
+'title.send_invoice' => 'שליחת חשבונית',
+'title.charts' => 'תרשימים',
+'title.projects' => 'פרוייקטים',
+'title.add_project' => 'הוסף פרוייקט',
+'title.edit_project' => 'עריכת פרוייקט',
+'title.delete_project' => 'מחיקת פרוייקט',
+'title.tasks' => 'משימות',
+'title.add_task' => 'הוסף משימה',
+'title.edit_task' => 'ערוך משימה',
+'title.delete_task' => 'מחק משימה',
+'title.users' => 'משתמשים',
+'title.add_user' => 'הוספת משתמש',
+'title.edit_user' => 'עריכת משתמש',
+'title.delete_user' => 'מחיקת משתמש',
+'title.clients' => 'לקוחות',
+'title.add_client' => 'הוספת לקוח',
+'title.edit_client' => 'עריכת לקוח',
+'title.delete_client' => 'מחיקת לקוח',
+'title.invoices' => 'חשבוניות',
+'title.add_invoice' => 'הוספת חשבונית',
+'title.view_invoice' => 'הצגת חשבונית',
+'title.delete_invoice' => 'מחיקת חשבונית',
+// TODO: translate the following strings.
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'ייצוא נתוני צוות',
+'title.import' => 'ייבוא נתוני צוות',
+'title.options' => 'אפשרויות',
+'title.profile' => 'פרופיל',
+'title.cf_custom_fields' => 'שדות אישיים',
+'title.cf_add_custom_field' => 'הוספת שדה אישי',
+'title.cf_edit_custom_field' => 'עריכת שדה אישי',
+'title.cf_delete_custom_field' => 'מחיקת שדה אישי',
+'title.cf_dropdown_options' => 'אפשרויות רשימה',
+'title.cf_add_dropdown_option' => 'הוספת אפשרות',
+'title.cf_edit_dropdown_option' => 'עריכת אפשרות',
+'title.cf_delete_dropdown_option' => 'מחיקת אפשרות',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- כולם ---',
+'dropdown.no' => '--- ללא ---',
+'dropdown.this_day' => 'יום מסויים',
+'dropdown.this_week' => 'שבוע זה',
+'dropdown.last_week' => 'שבוע שעבר',
+'dropdown.this_month' => 'חודש זה',
+'dropdown.last_month' => 'החודש שעבר',
+'dropdown.this_year' => 'שנה זו',
+'dropdown.all_time' => 'הכל',
+'dropdown.projects' => 'פרוייקטים',
+'dropdown.tasks' => 'משימות',
+'dropdown.clients' => 'לקוחות',
+// TODO: translate the following string.
+// 'dropdown.select' => '--- select ---',
+'dropdown.select_invoice' => '--- בחר חשבונית ---',
+'dropdown.status_active' => 'פעיל',
+'dropdown.status_inactive' => 'לא פעיל',
+// TODO: translate the following strings.
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'שכחת סיסמה?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> הינה מערכת פשוטה, קלה לשימוש וחינמית לניהול זמן.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'הבקשה לאיפוס בסיסמה נשלחה בדואר אלקטרוני.',
+'form.reset_password.email_subject' => 'בקשה לאיפוס סיסמה למערכת Anuko Time Tracker',
+'form.reset_password.email_body' => "משתמש יקר,\n\n התקבלה בקשה לאיפוס סיסמתך. נא ללחוץ על קישור זה אם ברצונך לאפס את הסיסמה.\n\n%s\n\n. Anuko Time Tracker הינה מערכת לניהול זמן פשוטה וחינמית. בקר באתרנו בכתובת https://www.anuko.com לפרטים נוספים.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'הכנס סיסמה חדשה ולחץ על שמירה',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm או 0.0h)',
+'form.time.billable' => 'לחיוב',
+'form.time.uncompleted' => 'רישום חסר',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'רישום זה נשמר עם שעת התחלה בלבד. זאת איננה טעות.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'שמור כמועדף',
+'form.reports.confirm_delete' => 'האם ברצונך למחוק את הדוח המועדף הזה ?',
+'form.reports.include_records' => 'כלול רישומים',
+'form.reports.include_billable' => 'לחיוב',
+'form.reports.include_not_billable' => 'לא לחיוב',
+// TODO: translate the following strings.
+// 'form.reports.include_invoiced' => 'invoiced',
+// 'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'בחר תקופת זמן',
+'form.reports.set_period' => 'או הגדר תאריכים',
+'form.reports.show_fields' => 'הראה שדות',
+'form.reports.group_by' => 'סדר לפי',
+'form.reports.group_by_no' => '--- ללא סדר ---',
+'form.reports.group_by_date' => 'תאריך',
+'form.reports.group_by_user' => 'משתמש',
+'form.reports.group_by_client' => 'לקוח',
+'form.reports.group_by_project' => 'פרוייקט',
+'form.reports.group_by_task' => 'משימה',
+'form.reports.totals_only' => 'סיכומים בלבד',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+// TODO: form.report.export is just "Export" now in the English file. Shorten this translation.
+'form.report.export' => 'ייצא נתונים בתבנית',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'מספר חשבונית',
+'form.invoice.person' => 'משתמש',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'טווח',
+'form.charts.chart' => 'תרשים',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'פרוייקטים פעילים',
+'form.projects.inactive_projects' => 'פרוייקטים לא פעילים',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'משימות פעילות',
+'form.tasks.inactive_tasks' => 'משימות לא פעילות',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'משתמשים פעילים',
+'form.users.inactive_users' => 'משתמשים לא פעילים',
+'form.users.role' => 'תפקיד',
+'form.users.manager' => 'מנהל',
+'form.users.comanager' => 'מנהל משנה',
+'form.users.rate' => 'תעריף',
+'form.users.default_rate' => 'תעריף ברירת מחדל לשעה',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'לקוחות פעילים',
+'form.clients.inactive_clients' => 'לקוחות לא פעילים',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'ניתן לייצא את כל נתוני הצוות בקובץ XML. זה מאד שימושי אם ברצונך להשתמש בשרת משלך.',
+'form.export.compression' => 'דחיסה',
+'form.export.compression_none' => 'ללא',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'ייבא נתוני צוות מתוך קובץ XML.',
+'form.import.file' => 'בחר קובץ',
+'form.import.success' => 'הייבוא הושלם בהצלחה.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' => 'ניתן ליצור צוות חדש על-ידי יצירת מנהל צוות חדש.<br>ניתן לייבא נתוני צוות מקובץ XML משרת Anuko Time Tracker אחר (אין אפשרות לשמות משתמש זהים)',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 שעות',
+'form.profile.24_hours' => '24 שעות',
+'form.profile.tracking_mode' => 'סוג מעקב',
+'form.profile.mode_time' => 'זמן',
+'form.profile.mode_projects' => 'פרוייקטים',
+'form.profile.mode_projects_and_tasks' => 'פרוייקטים ומשימות',
+'form.profile.record_type' => 'סוג רישום',
+'form.profile.type_all' => 'הכל',
+'form.profile.type_start_finish' => 'התחלה וסיום',
+'form.profile.type_duration' => 'משך זמן',
+'form.profile.plugins' => 'תוספים',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'מאת',
+'form.mail.to' => 'אל',
+'form.mail.cc' => 'העתק',
+'form.mail.subject' => 'נושא',
+'form.mail.report_subject' => 'דוח Time Tracker',
+'form.mail.footer' => 'Anuko Time Tracker הינה מערכת פשוטה, קלה לשימוש וחינמית לניהול זמן. בקר באתר <a href="https://www.anuko.com">www.anuko.com</a> לפרטים נוספים.',
+'form.mail.report_sent' => 'הדוח נשלח.',
+'form.mail.invoice_sent' => 'החשבונית נשלחה.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/hu.lang.php b/WEB-INF/resources/hu.lang.php
new file mode 100644 (file)
index 0000000..bc498b8
--- /dev/null
@@ -0,0 +1,416 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+$i18n_language = 'Magyar';
+$i18n_months = array('január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december');
+$i18n_weekdays = array('vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat');
+$i18n_weekdays_short = array('V', 'H', 'K', 'Sz', 'Cs', 'P', 'Sz');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/02', '03/15', '04/12', '04/13', '05/01', '05/31', '06/01', '08/20', '08/21', '10/23', '11/01', '12/24', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'bejelentkezés',
+'menu.logout' => 'kijelentkezés',
+'menu.feedback'  => 'megjegyzés',
+'menu.help' => 'segítség',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'új vezetői jogosultság létrehozása',
+'menu.edit_profile' => 'profil szerkesztése',
+'menu.my_time' => 'munkaidő',
+'menu.reports' => 'riportok',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projektek',
+'menu.activities' => 'tevékenységek',
+'menu.people' => 'munkatársak',
+'menu.teams' => 'csoportok',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'ügyfelek',
+'menu.options' => 'opciók',
+// Note to translators: the string is missing and must be added and translated // 'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'adatbázis hiba',
+'error.field' => 'hibás "{0}" mező tartalma',
+'error.empty' => 'a "{0}" mező üres',
+'error.not_equal' => 'A "{0}" mező tartalma nem egyezik meg a "{1}" mező tartalmával!',
+'error.interval' => 'hibás időszak megadás',
+'error.project' => 'válassz projektet',
+'error.activity' => 'válassz tevékenységet',
+'error.auth' => 'hibás bejelentkezési adatok',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'ilyen nevű projekt már létezik',
+'error.activity_exists' => 'ilyen névvel már van definiálva tevékenység',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'nincs ilyen e-mail címmel definiált felhasználó',
+'error.upload' => 'file feltöltési hiba',
+// Note to translators: the strings below are missing and must be added and translated 
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'bejelentkezés',
+'button.now' => 'most',
+// 'button.set' => 'beállítás',
+'button.save' => 'mentés',
+'button.delete' => 'törlés',
+'button.cancel' => 'vissza',
+'button.submit' => 'mentés',
+'button.add_user' => 'felhasználó felvétele',
+'button.add_project' => 'projekt felvétele',
+'button.add_activity' => 'tevékenyég felvétele',
+'button.add_client' => 'ügyfél hozzáadása',
+'button.add' => 'hozzáadás',
+'button.generate' => 'generálás',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'mehet',
+'button.send' => 'küld',
+'button.send_by_email' => 'küldés e-mail-ben',
+'button.save_as_new' => 'mentés újként',
+'button.create_team' => 'csoport létrehozása',
+'button.export' => 'csoportok exportálása',
+'button.import' => 'csoportok importálása',
+'button.apply' => 'alkalmaz',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'pénznem',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.name' => 'név',
+
+'label.password' => 'jelszó',
+'label.confirm_password' => 'jelszó megerősítése',
+// 'label.email' => 'email',
+'label.total' => 'összesen',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'előre definiált riport formátum',
+"form.filter.filter_new" => 'mentsük el ezt a riport formátumot',
+// Note to translators: the string below is missing and must be added and translated
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'bejelentkezés',
+// Note to translators: "form.login.login" => 'e-mail cím', // email has been changed to login
+
+// password reminder form attributes
+"form.fpass.title" => 'a jelszó alap állapotra állítása',
+// Note to translators: "form.fpass.login" => 'e-mail cím', // email has been changed to login
+"form.fpass.send_pass_str" => 'jelszó alap állapotra állítása megkezdve',
+"form.fpass.send_pass_subj" => 'A jelszó alap állapotra állítása a Anuko TimeTracker-ben',
+// Note to translators: the string below must be translated
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "a jelszót a megváltoztatásához írja be és mentse el",
+
+// administrator form
+"form.admin.title" => 'Adminisztrátor',
+"form.admin.duty_text" => 'új csoport létrehozása egy csoport-vezetői jogosultsággal.<br>a csoport adatokat importálhatjuk XML-ből (csak az e-mail címek ne ütközzenek).',
+
+"form.admin.change_pass" => 'az adminisztrátori jelszó megváltoztatása',
+"form.admin.profile.title" => 'csoportok',
+"form.admin.profile.noprofiles" => 'az adatbázis üres. lépj be adminisztrátorként és hozz létre egyet.',
+"form.admin.profile.comment" => 'csoport törlése',
+"form.admin.profile.th.id" => 'azonosító',
+"form.admin.profile.th.name" => 'név',
+"form.admin.profile.th.edit" => 'szerkesztés',
+"form.admin.profile.th.del" => 'törlés',
+"form.admin.profile.th.active" => 'aktív',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.admin.lock.period" => 'lock interval in days',
+"form.admin.options" => 'opciók',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'munkaidőm',
+"form.mytime.edit_title" => 'szerkesztés',
+"form.mytime.del_str" => 'törlés',
+"form.mytime.time_form" => ' (óó:pp)',
+"form.mytime.date" => 'dátum',
+"form.mytime.project" => 'projekt',
+"form.mytime.activity" => 'tevékenység',
+"form.mytime.start" => 'kezdete',
+"form.mytime.finish" => 'vége',
+"form.mytime.duration" => 'hossz',
+"form.mytime.note" => 'megjegyzés',
+"form.mytime.behalf" => 'napi tevékenység lista, munkatárs:',
+"form.mytime.daily" => 'napi munka',
+"form.mytime.total" => 'összesített óraszám: ',
+"form.mytime.th.project" => 'projekt',
+"form.mytime.th.activity" => 'tevékenység',
+"form.mytime.th.start" => 'kezdete',
+"form.mytime.th.finish" => 'vége',
+"form.mytime.th.duration" => 'hossz',
+"form.mytime.th.note" => 'megjegyzés',
+"form.mytime.th.edit" => 'szerkesztés',
+"form.mytime.th.delete" => 'törlés',
+"form.mytime.del_yes" => 'a bejegyzés törölve',
+"form.mytime.no_finished_rec" => 'csak az munka kezdete lett megjelölve, ha később visszalépsz a rendszerbe beállíthatod a vég-időpontot...',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.mytime.billable" => 'billable',
+// "form.mytime.warn_tozero_rec" => 'this time record must be deleted because this time period is locked',
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'új vezetői jogosultság létrehozása',
+"form.profile.edit_title" => 'profil szerkesztése',
+"form.profile.name" => 'név',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.profile.login" => 'login',
+
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'munkatársak',
+"form.people.createu_str" => 'új munkatárs hozzáadása',
+"form.people.edit_str" => 'munkatárs adatainak szerkesztése',
+"form.people.del_str" => 'munkatárs adatainak törlése',
+"form.people.th.name" => 'név',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.people.th.login" => 'login',
+"form.people.th.role" => 'szerepkör',
+"form.people.th.edit" => 'szerkesztés',
+"form.people.th.del" => 'törlés',
+"form.people.th.status" => 'státusz',
+"form.people.th.project" => 'projekt',
+"form.people.th.rate" => 'tarifa',
+"form.people.manager" => 'vezető',
+"form.people.comanager" => 'helyettes',
+"form.people.empl" => 'dolgozó',
+"form.people.name" => 'név',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.people.login" => 'login',
+
+"form.people.rate" => 'általános óradíj',
+"form.people.comanager" => 'helyettes',
+"form.people.projects" => 'projektek',
+// Note to translators: the string below is missing and must be added and translated 
+
+// projects form attributes
+"form.project.proj_title" => 'projektek',
+"form.project.edit_str" => 'projekt adatainak szerkesztése',
+"form.project.add_str" => 'új projekt hozzáadása',
+"form.project.del_str" => 'projekt törlése',
+"form.project.th.name" => 'név',
+"form.project.th.edit" => 'szerkesztés',
+"form.project.th.del" => 'törlés',
+"form.project.name" => 'név',
+
+// activities form attributes
+"form.activity.act_title" => 'tevékenységek',
+"form.activity.add_title" => 'új tevékenyég felvétele',
+"form.activity.edit_str" => 'tevékenység szerkesztése',
+"form.activity.del_str" => 'tevékenység törlése',
+"form.activity.name" => 'név',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'név',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'szerkesztés',
+"form.activity.th.del" => 'törlés',
+
+// report attributes
+"form.report.title" => 'riportok',
+"form.report.from" => 'kezdő időpont',
+"form.report.to" => 'vég időpont',
+"form.report.groupby_user" => 'személyek szerint',
+"form.report.groupby_project" => 'projektek szerint',
+"form.report.groupby_activity" => 'tevékenységek szerint',
+"form.report.duration" => 'időtartam',
+"form.report.start" => 'kezdet',
+"form.report.activity" => 'tevékenység',
+"form.report.show_idle" => 'az üres időszakok megjelenítése',
+"form.report.finish" => 'befejezés',
+"form.report.note" => 'megjegyzés',
+"form.report.project" => 'projekt',
+"form.report.totals_only" => 'csak a teljes óraszám',
+"form.report.total" => 'összesített óraszám',
+"form.report.th.empllist" => 'dolgozó',
+"form.report.th.date" => 'dátum',
+"form.report.th.project" => 'projekt',
+"form.report.th.activity" => 'tevékenység',
+"form.report.th.start" => 'elkezdve',
+"form.report.th.finish" => 'befejezve',
+"form.report.th.duration" => 'időtartam',
+"form.report.th.note" => 'megjegyzés',
+
+// mail form attributes
+"form.mail.from" => 'feladó',
+"form.mail.to" => 'címzett',
+"form.mail.cc" => 'másolatot kap',
+"form.mail.subject" => 'tárgy',
+"form.mail.comment" => 'megjegyzés',
+"form.mail.above" => 'küldjük el ezt a riportot e-mail-ben...',
+// Note to translators: the string below must be translated
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>az üzenet elküldve</b>',
+
+// invoice attributes
+"form.invoice.title" => 'számla',
+"form.invoice.caption" => 'Számla',
+"form.invoice.above" => 'a számlához tartozó adatok',
+"form.invoice.select_cust" => 'válassz ügyfelet',
+"form.invoice.fillform" => 'töltsd ki a mezőket',
+"form.invoice.date" => 'Dátum',
+"form.invoice.number" => 'számla azonosító száma',
+"form.invoice.tax" => 'adó',
+"form.invoice.daily_subtotals" => 'napi részösszeg',
+"form.invoice.yourcoo" => 'az ön neve<br> és címe',
+"form.invoice.custcoo" => 'az ügyfél nevebr> és címe',
+"form.invoice.comment" => 'megjegyzés ',
+"form.invoice.th.username" => 'személy',
+"form.invoice.th.time" => 'óra',
+"form.invoice.th.rate" => 'tarifa',
+"form.invoice.th.summ" => 'darab',
+"form.invoice.subtotal" => 'részösszeg',
+"form.invoice.customer" => 'Ügyfél',
+"form.invoice.mailinv_above" => 'küldjük el ezt a számlát e-mail-en',
+"form.invoice.sending_str" => '<b>a számla elküldve</b>',
+
+"form.migration.zip" => 'tömörítés',
+"form.migration.file" => 'válassz file-nevet',
+"form.migration.import.title" => 'adatok importálása',
+"form.migration.import.success" => 'az importálás sikeresen véget ért',
+"form.migration.import.text" => 'csoport adatok importja XML file-ból',
+"form.migration.export.title" => 'az adatok exportálása',
+"form.migration.export.success" => 'az exportálás sikeres volt',
+"form.migration.export.text" => 'kimentheted az összes felvitt csoport adatait egy XML file-ba, ami megkönnyíti a TimeTracker szerverek közötti adatátvitelt...',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+"form.client.title"=> 'ügyfelek',
+"form.client.add_title" => 'új ügyfél hozzáadása',
+"form.client.edit_title" => 'ügyfél adatainak szerkesztése',
+"form.client.del_title" => 'ügyfél törlése',
+"form.client.th.name" => 'név',
+"form.client.th.edit" => 'szerkesztés',
+"form.client.th.del" => 'törlés',
+"form.client.name" => 'név',
+"form.client.tax" => 'adó',
+"form.client.daily_subtotals" => 'napi részösszeg',
+"form.client.yourcoo" => 'az Ön neve<br> és számlázási címe',
+"form.client.custcoo" => 'cím',
+"form.client.comment" => 'megjegyzés ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'elfelejtetted a jelszót?',
+"forward.edit" => 'szerkesztés',
+"forward.delete" => 'törlés',
+"forward.tocsvfile" => 'az adatok exportálása CSV file-ba',
+// Note to translators: the string below is missing and must be added and translated 
+// "forward.toxmlfile" => 'export data to .xml file',
+"forward.geninvoice" => 'számla készítés',
+"forward.change" => 'ügyfelek adatainak beállítása',
+
+// strings inside contols on forms
+"controls.select.project" => '--- válassz projektet ---',
+"controls.select.activity" => '--- válassz tevékenységet ---',
+"controls.select.client" => '--- válassz ügyfelet ---',
+"controls.project_bind" => '--- összes ---',
+"controls.all" => '--- összes ---',
+"controls.notbind" => '--- nincs ---',
+"controls.per_tm" => 'ebben a hónapban',
+"controls.per_lm" => 'múlt hónapban',
+"controls.per_tw" => 'ezen a héten',
+"controls.per_lw" => 'múlt héten',
+// Note to translators: the strings below are missing and must be added and translated 
+// "controls.per_td" => 'this day',
+// "controls.per_at" => 'all time',
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- válassz időszakot ---',
+"controls.sel_groupby" => '--- csoportosítás nélkül ---',
+// Note to translators: the strings below are missing and must be added and translated 
+// "controls.inc_billable" => 'billable',
+// "controls.inc_nbillable" => 'not billable',
+// "controls.default" => '--- default ---',
+
+// labels
+// Note to translators: the strings below are missing and must be added and translated 
+// "label.chart.title1" => 'activities for user',
+// "label.chart.title2" => 'projects for user',
+// "label.chart.period" => 'chart for period',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>helyett %s</b>',
+"label.pminfo" => ' (vezető)',
+"label.pcminfo" => ' (helyettes)',
+"label.painfo" => ' (adminisztrátor)',
+"label.time_noentry" => 'nincs bejegyzés',
+"label.today" => 'ma',
+"label.req_fields" => '* kötelezően kitöltendő mezők',
+"label.sel_project" => 'válassz projektet',
+"label.sel_activity" => 'válassz tevékenységet',
+"label.sel_tp" => 'jelölj meg egy időszakot',
+"label.set_tp" => '... vagy állíts be konkrét dátumot',
+"label.fields" => 'csak a kijelölt mezők fognak szerepelni a riportban',
+"label.group_title" => 'csoportosítva',
+// Note to translators: the string below is missing and must be added and translated 
+// "label.include_title" => 'include records',
+"label.inv_str" => 'számla',
+"label.set_empl" => 'válassz dolgozót',
+"label.sel_all" => 'mindenkit kijelöl',
+"label.sel_none" => 'senkit nem jelöl ki',
+"label.or" => 'vagy',
+"label.disable" => 'tiltva',
+"label.enable" => 'engedélyezve',
+"label.filter" => 'szűrés',
+// Note to translators: the strings below are missing and must be added and translated 
+//"label.timeweek" => 'weekly total',
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/is.lang.php b/WEB-INF/resources/is.lang.php
new file mode 100644 (file)
index 0000000..3a7d292
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+$i18n_language = 'Íslenska';
+$i18n_months = array('janúar', 'febrúar', 'mars', 'apríl', 'maí', 'júní', 'júlí', 'ágúst', 'september', 'október', 'nóvember', 'desember');
+$i18n_weekdays = array('sunnudagur', 'mánudagur', 'þriðjudagur', 'miðvikudagur', 'fimmtudagur', 'föstudagur', 'laugardagur');
+$i18n_weekdays_short = array('sun', 'mon', 'þri', 'mið', 'fim', 'fös', 'lau');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/09', '04/10', '04/12', '04/13', '04/23', '05/01', '05/21', '05/31', '06/01', '06/17', '08/03', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'innskráning',
+'menu.logout' => 'skrá út',
+'menu.feedback' => 'skilaboð',
+'menu.help' => 'hjálp',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'nýskráning',
+'menu.edit_profile' => 'uppsetning',
+'menu.my_time' => 'tími',
+'menu.reports' => 'skýrslur',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'verkefni',
+// Note to translators: is menu.activities translated correctly?
+'menu.activities' => 'í gangi',
+'menu.people' => 'fólk',
+'menu.teams' => 'teymi',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'viðskiptavinir',
+// Note to translators: menu.options needs to be translated.
+// 'menu.options' => 'options',
+'menu.admin' => 'stjórnun',
+
+// error strings
+'error.db' => 'gagnagrunnsvilla',
+'error.field' => 'röng "{0}" gögn',
+'error.empty' => 'svæði "{0}" er tómt',
+// Note to translators: a large portion of the master English file from this point down below is not translated.
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/it.lang.php b/WEB-INF/resources/it.lang.php
new file mode 100644 (file)
index 0000000..1e83f63
--- /dev/null
@@ -0,0 +1,411 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Italiano';
+$i18n_months = array('gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luiglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre');
+$i18n_weekdays = array('domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato');
+$i18n_weekdays_short = array('do', 'lu', 'ma', 'me', 'gi', 've', 'sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/06', '04/12', '04/13', '04/25', '05/01', '06/02', '08/15', '11/01', '12/08', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+// Note to translators: these strings need to be translated.
+// 'menu.login' => 'login',
+// 'menu.logout' => 'logout',
+// 'menu.feedback' => 'feedback',
+// 'menu.help' => 'help',
+// Note to translators: menu.create_team a more accurate translation.
+'menu.create_team' => 'crea un nuovo account',
+'menu.edit_profile' => 'modifica profilo',
+'menu.my_time' => 'tempo di lavoro',
+// Note to translators: menu.reports and menu.charts need to be translated.
+// 'menu.reports' => 'reports',
+// 'menu.charts' => 'charts',
+'menu.projects' => 'progetti',
+'menu.activities' => 'attività',
+'menu.people' => 'persone',
+// 'menu.teams' => 'teams',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'clienti',
+'menu.options' => 'opzioni',
+'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'database error',
+'error.field' => 'dato "{0}" errato',
+'error.empty' => 'il campo "{0}" è vuoto',
+'error.not_equal' => 'il campo "{0}" non è uguale al campo "{1}"',
+'error.interval' => 'intervallo errato',
+'error.project' => 'seleziona il progetto',
+'error.activity' => 'seleziona la attività',
+'error.auth' => 'login o password errati',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'esiste già un progetto con questo nome',
+'error.activity_exists' => 'esiste già una attività con questo nome',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'nessun utente con questa e-mail',
+// Note to translators: the strings below need to be translated
+// 'error.upload' => 'file upload error',
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'login',
+'button.now' => 'adesso',
+// 'button.set' => 'set',
+'button.save' => 'salva',
+'button.delete' => 'elimina',
+'button.cancel' => 'cancella',
+'button.submit' => 'invia',
+'button.add_user' => 'aggiungi utente',
+'button.add_project' => 'aggiungi progetto',
+'button.add_activity' => 'aggiungi attività',
+'button.add_client' => 'aggiungi cliente',
+'button.add' => 'add',
+'button.generate' => 'genera',
+// Note to translators: button.reset_password needs an improved translation.
+// 'button.reset_password' => 'reset password',
+'button.send' => 'invia',
+'button.send_by_email' => 'invia tramite e-mail',
+'button.save_as_new' => 'salva come nuovo',
+'button.create_team' => 'crea team',
+'button.export' => 'esporta team',
+'button.import' => 'importa team',
+'button.apply' => 'applica',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'moneta',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'password',
+'label.confirm_password' => 'conferma password',
+'label.email' => 'e-mail',
+'label.total' => 'totale',
+
+"form.filter.project" => 'progetto',
+"form.filter.filter" => 'report preferiti',
+"form.filter.filter_new" => 'salva nei preferiti',
+"form.filter.filter_confirm_delete" => 'sei sicuro di voler cancellare questo report dai preferiti?',
+
+// login form attributes
+"form.login.title" => 'login',
+"form.login.login" => 'login',
+
+// password reminder form attributes
+"form.fpass.title" => 'reset password',
+"form.fpass.login" => 'login',
+"form.fpass.send_pass_str" => 'richiesta di reset pasword inviata',
+"form.fpass.send_pass_subj" => 'richiesta di password reset', 
+// Note to translators: the strings below must be translated
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+// "form.fpass.reset_comment" => "to reset your password please type it in and click on save",
+
+// administrator form
+"form.admin.title" => 'amministratore',
+// Note to translators: the string below must be translated
+// "form.admin.duty_text" => 'create a new team by creating a new team manager account.<br>you can also import team data from an xml file from another Anuko Time Tracker server (no e-mail collisions are allowed).',
+
+"form.admin.change_pass" => 'cambia la password dell\\\'amministratore',
+"form.admin.profile.title" => 'teams',
+"form.admin.profile.noprofiles" => 'il database è vuoto. loggati come amministratore e crea un nuovo team.',
+"form.admin.profile.comment" => 'elimina team',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'nome',
+"form.admin.profile.th.edit" => 'edit',
+"form.admin.profile.th.del" => 'elimina',
+"form.admin.profile.th.active" => 'attivo',
+"form.admin.lock.period" => 'blocca l\\\'intervallo di tempo',
+"form.admin.options" => 'opzioni',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'giorno',
+"form.mytime.edit_title" => 'modifica time record',
+"form.mytime.del_str" => 'elimina time record',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'data',
+"form.mytime.project" => 'progetto',
+"form.mytime.activity" => 'attività',
+"form.mytime.start" => 'inizio',
+"form.mytime.finish" => 'fine',
+"form.mytime.duration" => 'durata',
+"form.mytime.note" => 'note',
+"form.mytime.behalf" => 'attività giornaliera per',
+"form.mytime.daily" => 'attività giornaliera',
+"form.mytime.total" => 'ore totali: ',
+"form.mytime.th.project" => 'progetto',
+"form.mytime.th.activity" => 'attività',
+"form.mytime.th.start" => 'inizio',
+"form.mytime.th.finish" => 'fine',
+"form.mytime.th.duration" => 'durata',
+"form.mytime.th.note" => 'note',
+"form.mytime.th.edit" => 'edit',
+"form.mytime.th.delete" => 'elimina',
+"form.mytime.del_yes" => 'time record cancellato',
+"form.mytime.no_finished_rec" => 'questo record è stato salvato con la sola ora di inzio attività. non è un errore. esegui il logout per altro....',
+"form.mytime.billable" => 'fatturabile',
+"form.mytime.warn_tozero_rec" => 'questo time record deve essere cancellato perchè il periodo di riferimento è stato bloccato',
+// Note to translators: the string below is missing and must be added and translated
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'crea un nuovo account manager',
+"form.profile.edit_title" => 'modifca il profilo',
+"form.profile.name" => 'nome',
+"form.profile.login" => 'login',
+
+// Note to translators: the strings below are missing and must be added and translated
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'persone',
+"form.people.createu_str" => 'crea un nuovo utente',
+"form.people.edit_str" => 'modifica utente',
+"form.people.del_str" => 'elimina utente',
+"form.people.th.name" => 'nome',
+"form.people.th.login" => 'login',
+"form.people.th.role" => 'funzione',
+"form.people.th.edit" => 'modifica',
+"form.people.th.del" => 'elimina',
+"form.people.th.status" => 'stato',
+"form.people.th.project" => 'progetto',
+"form.people.th.rate" => 'costo',
+"form.people.manager" => 'manager',
+"form.people.comanager" => 'comanager',
+"form.people.empl" => 'utente',
+"form.people.name" => 'nome',
+"form.people.login" => 'login',
+
+"form.people.rate" => 'costo per ora di default',
+"form.people.comanager" => 'co-manager',
+"form.people.projects" => 'progetti',
+
+// projects form attributes
+"form.project.proj_title" => 'progetti',
+"form.project.edit_str" => 'mofifca progetto',
+"form.project.add_str" => 'aggiungi nuovo progetto',
+"form.project.del_str" => 'elimina progetto',
+"form.project.th.name" => 'nome',
+"form.project.th.edit" => 'modifica',
+"form.project.th.del" => 'elimina',
+"form.project.name" => 'nome',
+
+// activities form attributes
+"form.activity.act_title" => 'attività',
+"form.activity.add_title" => 'aggiungi nuova attività',
+"form.activity.edit_str" => 'modifica attività',
+"form.activity.del_str" => 'elimina attività',
+"form.activity.name" => 'nome',
+"form.activity.project" => 'progetto',
+"form.activity.th.name" => 'nome',
+"form.activity.th.project" => 'progetto',
+"form.activity.th.edit" => 'modifica',
+"form.activity.th.del" => 'elimina',
+
+// report attributes
+"form.report.title" => 'report',
+"form.report.from" => 'data inizio',
+"form.report.to" => 'data fine',
+"form.report.groupby_user" => 'utente',
+"form.report.groupby_project" => 'progetto',
+"form.report.groupby_activity" => 'attività',
+"form.report.duration" => 'durata',
+"form.report.start" => 'inizio',
+"form.report.activity" => 'attività',
+"form.report.show_idle" => 'mostra inattivi',
+"form.report.finish" => 'fine',
+"form.report.note" => 'nota',
+"form.report.project" => 'progetto',
+"form.report.totals_only" => 'solo i totali',
+"form.report.total" => 'ore totali',
+"form.report.th.empllist" => 'utente',
+"form.report.th.date" => 'data',
+"form.report.th.project" => 'progetto',
+"form.report.th.activity" => 'attività',
+"form.report.th.start" => 'inizio',
+"form.report.th.finish" => 'fine',
+"form.report.th.duration" => 'durata',
+"form.report.th.note" => 'note',
+
+// mail form attributes
+"form.mail.from" => 'da',
+"form.mail.to" => 'a',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'oggetto',
+"form.mail.comment" => 'commento',
+"form.mail.above" => 'invia questo report tramite e-mail',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>messaggio inviato</b>',
+
+// invoice attributes
+"form.invoice.title" => 'fattura',
+"form.invoice.caption" => 'fattura',
+"form.invoice.above" => 'informazioni aggiuntive per la fattura',
+"form.invoice.select_cust" => 'seleziona il cliente',
+"form.invoice.fillform" => 'compila i campi',
+"form.invoice.date" => 'data',
+"form.invoice.number" => 'numero fattura',
+"form.invoice.tax" => 'tassa',
+"form.invoice.daily_subtotals" => 'subtotaole giornaliero',
+"form.invoice.yourcoo" => 'tuo nome<br> e indirizzo',
+"form.invoice.custcoo" => 'nome cliente<br> e indirizzo',
+"form.invoice.comment" => 'commento ',
+"form.invoice.th.username" => 'persona',
+"form.invoice.th.time" => 'ore',
+"form.invoice.th.rate" => 'costo',
+"form.invoice.th.summ" => 'ammontare',
+"form.invoice.subtotal" => 'subtotale',
+"form.invoice.customer" => 'cliente',
+"form.invoice.mailinv_above" => 'invia la fattura tramite e-mail',
+"form.invoice.sending_str" => '<b>fattura inviata</b>',
+
+"form.migration.zip" => 'compressione',
+"form.migration.file" => 'seleziona il file',
+"form.migration.import.title" => 'importa i dati',
+"form.migration.import.success" => 'importazione eseguita con successo',
+"form.migration.import.text" => 'importa i dati del team da un file xml',
+"form.migration.export.title" => 'esporta i dati',
+"form.migration.export.success" => 'esportazione eseguita con successo',
+"form.migration.export.text" => 'puoi esporate tutti i dati dei team in un file xml. questo può essere utile se devi trasferire i dati da un server ad un altro.',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'clienti',
+"form.client.add_title" => 'aggiungi cliente',
+"form.client.edit_title" => 'modifica cliente',
+"form.client.del_title" => 'elimina cliente',
+"form.client.th.name" => 'nome',
+"form.client.th.edit" => 'modifica',
+"form.client.th.del" => 'elimina',
+"form.client.name" => 'nome',
+"form.client.tax" => 'tassa',
+"form.client.daily_subtotals" => 'subtotale giornaliero',
+"form.client.yourcoo" => 'il tuo nome<br> e indirizzo nella fattura',
+"form.client.custcoo" => 'indirizzo',
+"form.client.comment" => 'commento ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'password dimenticata?',
+"forward.edit" => 'modifica',
+"forward.delete" => 'elimina',
+"forward.tocsvfile" => 'esporta i dati in un file .csv',
+"forward.toxmlfile" => 'esporta i dati in un file .xml',
+"forward.geninvoice" => 'genera la fattura',
+"forward.change" => 'configura i clienti',
+
+// strings inside contols on forms
+"controls.select.project" => '--- seleziona il progetto ---',
+"controls.select.activity" => '--- seleziona la attività ---',
+"controls.select.client" => '--- seleziona il cliente ---',
+"controls.project_bind" => '--- tutti ---',
+"controls.all" => '--- tutti ---',
+"controls.notbind" => '--- no ---',
+"controls.per_tm" => 'questo mese',
+"controls.per_lm" => 'mese scorso',
+"controls.per_tw" => 'questa settimana',
+"controls.per_lw" => 'settimana scorsa',
+"controls.per_td" => 'questo giorno',
+"controls.per_at" => 'tutto il tempo',
+"controls.per_ty" => 'quest\\\'anno',
+"controls.sel_period" => '--- seleziona il periodo di tempo ---',
+"controls.sel_groupby" => '--- non raggruppare ---',
+"controls.inc_billable" => 'fatturabile',
+"controls.inc_nbillable" => 'non fatturabile',
+// Note to translators: the string below must be translated
+// "controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'attività per utente',
+// Note to translators: the string below is missing and must be added and translated
+// "label.chart.title2" => 'projects for user',
+"label.chart.period" => 'grafico per il periodo',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>a favore di %s</b>',
+"label.pminfo" => ' (manager)',
+"label.pcminfo" => ' (co-manager)',
+"label.painfo" => ' (amministratore)',
+"label.time_noentry" => 'nessun inserimento',
+"label.today" => 'oggi',
+"label.req_fields" => '* campi obbligatori',
+"label.sel_project" => 'seleziona il progetto',
+"label.sel_activity" => 'seleziona la attività',
+"label.sel_tp" => 'seleziona il periodo di tempo',
+"label.set_tp" => 'oppure setta le date',
+"label.fields" => 'mostra i campi',
+"label.group_title" => 'raggruppa per',
+"label.include_title" => 'includi records',
+"label.inv_str" => 'fattura',
+"label.set_empl" => 'seleziona utenti',
+"label.sel_all" => 'seleziona tutti',
+"label.sel_none" => 'deseleziona tutti',
+"label.or" => 'o',
+"label.disable" => 'disabilita',
+"label.enable" => 'abilita',
+"label.filter" => 'filtro',
+"label.timeweek" => 'totale settimanale',
+"label.hrs" => 'ore',
+// Note to translators: the strings below are missing and must be added and translated
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/ja.lang.php b/WEB-INF/resources/ja.lang.php
new file mode 100644 (file)
index 0000000..4334949
--- /dev/null
@@ -0,0 +1,405 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = '日本語';
+$i18n_months = array('1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月');
+$i18n_weekdays = array('日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日');
+$i18n_weekdays_short = array('日', '月', '火', '水', '木', '金', '土');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/12', '03/20', '05/03', '05/04', '05/05', '05/06', '09/21', '09/22', '09/23', '11/03', '11/23');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'ログイン',
+'menu.logout' => 'ログアウト',
+'menu.feedback' => 'フィードバック',
+'menu.help' => 'ヘルプ',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => '新規管理者のアカウントの作成',
+'menu.edit_profile' => 'プロファイルの編集',
+'menu.time' => '私の時間',
+'menu.reports' => 'レポート',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'プロジェクト',
+'menu.activities' => '活動内容',
+'menu.people' => 'メンバー',
+'menu.teams' => 'チーム',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'クライアント',
+'menu.options' => 'オプション',
+'menu.admin' => '管理',
+
+// error strings
+'error.db' => 'データベースのエラー',
+'error.field' => '不正確な"{0}"データ',
+'error.empty' => '"{0}"のフィールドが空白です',
+'error.not_equal' => '"{0}"のフィールドは"{1}"のフィールドと違います',
+'error.interval' => '不正確な間隔',
+'error.project' => 'プロジェクトの選択',
+'error.activity' => '活動内容の選択',
+'error.auth' => '不正確なログインあるいはパスワードが不正確です',
+'error.user_exists' => 'このログインと関連されたユーザーは既に存在します',
+'error.project_exists' => 'この名前のプロジェクトは既に存在します',
+'error.activity_exists' => 'この名前の活動内容は既に存在します',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+'error.no_login' => 'このログインと関連されたユーザーはいません',
+'error.upload' => 'ファイルのアップロードのエラー',
+// Note to translators: the string below is missing and must be translated.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => 'メールの送信中のエラー',
+// Note to translators: check the meaning of error.no_email. The error should say that there is no email address for user with a login provided.
+'error.no_email' => 'このログインと関連されたメールがありません',
+// Note to translators: the strings below are missing and must be translated.
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'ログイン',
+'button.now' => '現在',
+// 'button.set' => '設定',
+'button.save' => '保存',
+'button.delete' => '削除',
+'button.cancel' => 'キャンセル',
+'button.submit' => '送信',
+// TODO: improve translation of all button.add... strings.
+'button.add_user' => '新規ユーザーの追加',
+'button.add_project' => '新規プロジェクトの追加',
+'button.add_activity' => '新規活動内容の追加',
+'button.add_client' => '新規クライアントの追加',
+'button.add' => '追加',
+'button.generate' => '生成',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => '進む',
+'button.send' => '送信',
+'button.send_by_email' => 'Eメールの送信',
+'button.save_as_new' => '名前を付けて保存',
+// TODO: improve translation of button.create_team
+'button.create_team' => '新規チームの作成',
+'button.export' => 'チームのエクスポート',
+'button.import' => 'チームのインポート',
+'button.apply' => '適用',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => '通貨',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'パスワード',
+'label.confirm_password' => 'パスワードの確認',
+'label.email' => 'Eメール',
+'label.total' => '合計',
+
+"form.filter.project" => 'プロジェクト',
+"form.filter.filter" => 'お気に入りレポート',
+"form.filter.filter_new" => 'お気に入りに保存',
+"form.filter.filter_confirm_delete" => 'このお気に入りレポートを削除しますか?',
+
+// login form attributes
+"form.login.title" => 'ログイン',
+"form.login.login" => 'ログインID',
+
+// password reminder form attributes
+"form.fpass.title" => 'パスワードの初期化',
+"form.fpass.login" => 'ログイン',
+"form.fpass.send_pass_str" => '送信したパスワードの初期化の要求',
+"form.fpass.send_pass_subj" => 'Anuko Time Trackerのパスワードの初期化の要求',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "尊敬なるお客様、\n\n誰から(多分あなた)あなたのAnuko Time Trackerのパスワードの初期化が要求されました。あなたのパスワードを初期化しようとこのリンクを押してください。\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "あなたのパスワードを初期化しようとパスワードを入力して保存をクリックしてください",
+
+// administrator form
+"form.admin.title" => '管理者',
+"form.admin.duty_text" => '新規チームの管理者のアカウントを生成して新規チームを作成します。<br>あなたはなお他のAnuko Time Trackerサーバのxmlのファイルからチームデータをインポートすることができます(ログインの衝突は許可されません)。',
+
+"form.admin.change_pass" => '管理者のアカウントのパスワードの変更',
+"form.admin.profile.title" => 'チーム',
+"form.admin.profile.noprofiles" => 'あなたのデータベースは空いています。管理者にログインして新規チームを作成してください。',
+"form.admin.profile.comment" => 'チームの削除',
+"form.admin.profile.th.id" => '識別子',
+"form.admin.profile.th.name" => '名前',
+"form.admin.profile.th.edit" => '編集',
+"form.admin.profile.th.del" => '削除',
+"form.admin.profile.th.active" => '活動内容',
+"form.admin.lock.period" => '日付の間隔をロック',
+"form.admin.options" => 'オプション',
+"form.admin.lang_default" => 'サイトのデフォルト言語',
+"form.admin.custom_date_format" => "日付形式",
+"form.admin.custom_time_format" => "時間形式",
+"form.admin.start_week" => "週の開始日",
+
+// my time form attributes
+"form.mytime.title" => '私の時間',
+"form.mytime.edit_title" => '時間レコードの編集',
+"form.mytime.del_str" => '時間レコードの削除',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => '日付',
+"form.mytime.project" => 'プロジェクト',
+"form.mytime.activity" => '活動内容',
+"form.mytime.start" => '開始',
+"form.mytime.finish" => '終了',
+"form.mytime.duration" => '期間',
+"form.mytime.note" => 'ノート',
+"form.mytime.behalf" => '日課',
+"form.mytime.daily" => '日課',
+"form.mytime.total" => '合計時間: ',
+"form.mytime.th.project" => 'プロジェクト',
+"form.mytime.th.activity" => '活動内容',
+"form.mytime.th.start" => '開始',
+"form.mytime.th.finish" => '終了',
+"form.mytime.th.duration" => '期間',
+"form.mytime.th.note" => 'ノート',
+"form.mytime.th.edit" => '編集',
+"form.mytime.th.delete" => '削除',
+"form.mytime.del_yes" => '時間レコードが成功的に削除されました',
+"form.mytime.no_finished_rec" => 'このレコードは開始時間だけで保存されました。これはエラーではありません。もし必要があればログアウトしてください。',
+"form.mytime.billable" => '請求できる',
+"form.mytime.warn_tozero_rec" => 'この時間レコードの期間が満了されましたから、この時間レコードは削除されることが必要です',
+"form.mytime.uncompleted" => '未完成の',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => '新規管理者のアカウントの作成',
+"form.profile.edit_title" => 'プロファイルの編集',
+"form.profile.name" => '名前',
+"form.profile.login" => 'ログインID',
+
+"form.profile.showchart" => 'パイ図表の表示',
+"form.profile.lang" => '言語',
+"form.profile.custom_date_format" => "日付形式",
+"form.profile.custom_time_format" => "時間形式",
+"form.profile.default_format" => "(デフォルト)",
+"form.profile.start_week" => "週の開始日",
+
+// people form attributes
+"form.people.ppl_str" => 'メンバー',
+"form.people.createu_str" => '新規ユーザーの作成',
+"form.people.edit_str" => 'ユーザーの編集',
+"form.people.del_str" => 'ユーザーの削除',
+"form.people.th.name" => '名前',
+"form.people.th.login" => 'ログインID',
+"form.people.th.role" => 'ルール',
+"form.people.th.edit" => '編集',
+"form.people.th.del" => '削除',
+"form.people.th.status" => '状態',
+"form.people.th.project" => 'プロジェクト',
+"form.people.th.rate" => '給料',
+"form.people.manager" => '管理者',
+"form.people.comanager" => '共同管理者',
+"form.people.empl" => 'ユーザー',
+"form.people.name" => '名前',
+"form.people.login" => 'ログインID',
+
+"form.people.rate" => 'デフォルト時間当り給料',
+"form.people.comanager" => '共同管理者',
+"form.people.projects" => 'プロジェクト',
+
+// projects form attributes
+"form.project.proj_title" => 'プロジェクト',
+"form.project.edit_str" => 'プロジェクトの編集',
+"form.project.add_str" => '新規プロジェクトの追加',
+"form.project.del_str" => 'プロジェクトの削除',
+"form.project.th.name" => '名前',
+"form.project.th.edit" => '編集',
+"form.project.th.del" => '削除',
+"form.project.name" => '名前',
+
+// activities form attributes
+"form.activity.act_title" => '活動内容',
+"form.activity.add_title" => '新規活動内容の追加',
+"form.activity.edit_str" => '活動内容の編集',
+"form.activity.del_str" => '活動内容の削除',
+"form.activity.name" => '名前',
+"form.activity.project" => 'プロジェクト',
+"form.activity.th.name" => '名前',
+"form.activity.th.project" => 'プロジェクト',
+"form.activity.th.edit" => '編集',
+"form.activity.th.del" => '削除',
+
+// report attributes
+"form.report.title" => 'レポート',
+"form.report.from" => '開始日付',
+"form.report.to" => '終了日付',
+"form.report.groupby_user" => 'ユーザー',
+"form.report.groupby_project" => 'プロジェクト',
+"form.report.groupby_activity" => '活動内容',
+"form.report.duration" => '期間',
+"form.report.start" => '開始',
+"form.report.activity" => '活動内容',
+"form.report.show_idle" => '遊休の表示',
+"form.report.finish" => '終了',
+"form.report.note" => 'ノート',
+"form.report.project" => 'プロジェクト',
+"form.report.totals_only" => '全体だけ',
+"form.report.total" => '合計時間',
+"form.report.th.empllist" => 'ユーザー',
+"form.report.th.date" => '日付',
+"form.report.th.project" => 'プロジェクト',
+"form.report.th.activity" => '活動内容',
+"form.report.th.start" => '開始',
+"form.report.th.finish" => '終了',
+"form.report.th.duration" => '期間',
+"form.report.th.note" => 'ノート',
+
+// mail form attributes
+"form.mail.from" => 'から',
+"form.mail.to" => 'まで',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => '主題',
+"form.mail.comment" => 'コメント',
+"form.mail.above" => 'このレポートをEメールで送信',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>送信したメッセージ</b>',
+
+// invoice attributes
+"form.invoice.title" => '送り状',
+"form.invoice.caption" => '送り状',
+"form.invoice.above" => '送り状の追加の情報',
+"form.invoice.select_cust" => 'クライアントの選択',
+"form.invoice.fillform" => 'フィールドの作成',
+"form.invoice.date" => '日付',
+"form.invoice.number" => '送り状の番号',
+"form.invoice.tax" => '税',
+"form.invoice.daily_subtotals" => '日付小計',
+"form.invoice.yourcoo" => 'あなたの名前<br>とアドレス',
+"form.invoice.custcoo" => 'クライアントの名前<br>とアドレス',
+"form.invoice.comment" => 'コメント ',
+"form.invoice.th.username" => '個人',
+"form.invoice.th.time" => '時間',
+"form.invoice.th.rate" => '給料',
+"form.invoice.th.summ" => '数量',
+"form.invoice.subtotal" => '小計',
+"form.invoice.customer" => 'クライアント',
+"form.invoice.mailinv_above" => '送り状をEメールで送信',
+"form.invoice.sending_str" => '<b>送信した送り状</b>',
+
+"form.migration.zip" => '圧縮',
+"form.migration.file" => 'ファイルの選択',
+"form.migration.import.title" => 'データのインポート',
+"form.migration.import.success" => 'インポートが成功的に完了されました',
+"form.migration.import.text" => 'xmlファイルからチームのデータをインポート',
+"form.migration.export.title" => 'データのエクスポート',
+"form.migration.export.success" => 'エクスポートが成功的に完了されました',
+"form.migration.export.text" => 'あなたはすべてのチームのデータをxmlファイルにエクスポートすることができます。これはあなたの自分のサーバに移動する時に有用します。',
+"form.migration.compression.none" => 'なし',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'クライアント',
+"form.client.add_title" => 'クライアントの追加',
+"form.client.edit_title" => 'クライアントの編集',
+"form.client.del_title" => 'クライアントの削除',
+"form.client.th.name" => '名前',
+"form.client.th.edit" => '編集',
+"form.client.th.del" => '削除',
+"form.client.name" => '名前',
+"form.client.tax" => '税',
+"form.client.daily_subtotals" => '日付小計',
+"form.client.yourcoo" => 'あなたの名前<br>と送り状でのアドレス',
+"form.client.custcoo" => 'アドレス',
+"form.client.comment" => 'コメント ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'パスワードを忘れましたか?',
+"forward.edit" => '編集',
+"forward.delete" => '削除',
+"forward.tocsvfile" => 'csvファイルにエクスポート',
+"forward.toxmlfile" => 'xmlファイルにエクスポート',
+"forward.geninvoice" => '送り状の作成',
+"forward.change" => 'クライアントの構成',
+
+// strings inside contols on forms
+"controls.select.project" => '--- プロジェクトの選択 ---',
+"controls.select.activity" => '--- 活動内容の選択 ---',
+"controls.select.client" => '--- クライアントの選択 ---',
+"controls.project_bind" => '--- すべて ---',
+"controls.all" => '--- すべて ---',
+"controls.notbind" => '--- いいえ ---',
+"controls.per_tm" => '今月',
+"controls.per_lm" => '先月',
+"controls.per_tw" => '今週',
+"controls.per_lw" => '先週',
+"controls.per_td" => '今日',
+"controls.per_at" => 'すべての時間',
+"controls.per_ty" => '今年',
+"controls.sel_period" => '--- 時間期間の選択 ---',
+"controls.sel_groupby" => '--- グループの機能がありません ---',
+"controls.inc_billable" => '請求できる',
+"controls.inc_nbillable" => '請求できません',
+"controls.default" => '--- デフォルト ---',
+
+// labels
+"label.chart.title1" => 'ユーザーに対する活動内容',
+"label.chart.title2" => 'ユーザーに対するプロジェクト',
+"label.chart.period" => '期間表示のチャート',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>%sを代表して</b>',
+"label.pminfo" => ' (管理者)',
+"label.pcminfo" => ' (共同管理者)',
+"label.painfo" => ' (管理者)',
+"label.time_noentry" => '項目なし',
+"label.today" => '今日',
+"label.req_fields" => '* 必須のフィールド',
+"label.sel_project" => 'プロジェクトの選択',
+"label.sel_activity" => '活動内容の選択',
+"label.sel_tp" => '時間期間の選択',
+"label.set_tp" => 'あるいは日付を設定',
+"label.fields" => 'フィールドの表示',
+"label.group_title" => '次のようにグループ化',
+"label.include_title" => 'レコードの含み',
+"label.inv_str" => '送り状',
+"label.set_empl" => 'ユーザーの選択',
+"label.sel_all" => 'すべて選択',
+"label.sel_none" => 'すべて解除',
+"label.or" => 'あるいは',
+"label.disable" => '使用中止',
+"label.enable" => '使用可能',
+"label.filter" => 'フィルター',
+"label.timeweek" => '週合計',
+"label.hrs" => '時間',
+"label.errors" => 'エラー',
+"label.ldap_hint" => '下記のフィールドにあなたの<b>WindowsのログインID</b>と<b>パスワード</b>を入力してください。',
+// Note to translators: the strings below are missing and must be translated.
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/ko.lang.php b/WEB-INF/resources/ko.lang.php
new file mode 100644 (file)
index 0000000..cc50132
--- /dev/null
@@ -0,0 +1,403 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = '한국어';
+$i18n_months = array('1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월');
+$i18n_weekdays = array('일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일');
+$i18n_weekdays_short = array('일', '월', '화', '수', '목', '금', '토');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/25', '01/26', '01/27', '03/02', '03/05', '08/15', '12/25');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => '로그인',
+'menu.logout' => '로그아웃',
+'menu.feedback' => '피드백',
+'menu.help' => '도움말',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => '새로운 관리자 계정을 생성',
+'menu.edit_profile' => '프로필 편집',
+'menu.time' => '나의 시간',
+'menu.reports' => '보고서',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => '프로젝트',
+'menu.activities' => '활동내용',
+'menu.people' => '멤버',
+'menu.teams' => '팀',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => '클라이언트',
+'menu.options' => '옵션',
+'menu.admin' => '관리',
+
+// error strings
+'error.db' => '데이터베이스 오류',
+'error.field' => '부정확한 "{0}" 의 데이터',
+'error.empty' => '"{0}" 의 필드가 비어있습니다',
+'error.not_equal' => '"{0}" 의 필드가 "{1}" 의 필드와 같지 않습니다',
+'error.interval' => '부정확한 간격입니다',
+'error.project' => '프로젝트의 선택',
+'error.activity' => '활동내용의 선택',
+'error.auth' => '부정확한 로그인 혹은 암호가 틀립니다',
+'error.user_exists' => '본 로그인과 연계된 사용자가 이미 있습니다',
+'error.project_exists' => '본 이름과 연계된 프로젝트가 이미 있습니다',
+'error.activity_exists' => '본 이름과 연계된 활동내용이 이미 있습니다',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+'error.no_login' => '본 로그인과 연계된 사용자가 없습니다',
+'error.upload' => '파일 업로드 오류',
+// Note to translators: this string needs to be translated.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => '메일 보내기에서의 오류',
+'error.no_email' => '본 로그인과 연계된 이메일이 없습니다',
+// Note to translators: the strings below need to be translated.
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => '로그인',
+'button.now' => '지금',
+// 'button.set' => '설정',
+'button.save' => '저장',
+'button.delete' => '삭제',
+'button.cancel' => '취소',
+'button.submit' => '발송',
+// TODO: impprove translation of all button.add strings.
+'button.add_user' => '신규 사용자 추가',
+'button.add_project' => '신규 프로젝트 추가',
+'button.add_activity' => '신규 활동내용 추가',
+'button.add_client' => '신규 클라이언트 추가',
+'button.add' => '추가',
+'button.generate' => '생성',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => '실행',
+'button.send' => '송신',
+'button.send_by_email' => '이메일로 송신',
+'button.save_as_new' => '새것으로 저장',
+'button.create_team' => '신규 팀 작성',
+'button.export' => '팀 익스포트',
+'button.import' => '팀 임포트',
+'button.apply' => '적용',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => '화폐',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => '암호',
+'label.confirm_password' => '암호 확인',
+'label.email' => '이메일',
+'label.total' => '합계',
+
+"form.filter.project" => '프로젝트',
+"form.filter.filter" => '좋아하는 보고서',
+"form.filter.filter_new" => '좋아하는 것으로 저장',
+"form.filter.filter_confirm_delete" => '좋아하는 이 보고서를 삭제해도 좋습니까?',
+
+// login form attributes
+"form.login.title" => '로그인',
+"form.login.login" => '로그인ID',
+
+// password reminder form attributes
+"form.fpass.title" => '암호 재설정',
+"form.fpass.login" => '로그인',
+"form.fpass.send_pass_str" => '송신한 암호 재설정 요청',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker 암호 재설정 요청',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "사용자님께,\n\n누군가 (아마 당신) 가 당신의 Anuko Time Tracker 암호 재설정을 요청하였습니다. 당신의 암호를 재설정하기 바란다면 이 링크를 찾아주십시오. \n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "암호를 재설정하려면 암호를 입력하고 저장을 클릭하십시오",
+
+// administrator form
+"form.admin.title" => '관리자',
+"form.admin.duty_text" => '신규 팀관리자 계정을 생성하여 신규 팀을 생성합니다.<br>또한 다른 Anuko Time Tracker 서버 에서 xml 파일로부터 팀 데이터를 임포트할수 있습니다 (로그인 충돌은 허용되지 안음).',
+
+"form.admin.change_pass" => '관리자 계정의 암호 변경',
+"form.admin.profile.title" => '팀',
+"form.admin.profile.noprofiles" => '당신의 데이터베이스는 비어있습니다. 관리자로 로그인하여 새로운 팀을 생성하십시오.',
+"form.admin.profile.comment" => '팀 삭제',
+"form.admin.profile.th.id" => '식별자',
+"form.admin.profile.th.name" => '이름',
+"form.admin.profile.th.edit" => '편집',
+"form.admin.profile.th.del" => '삭제',
+"form.admin.profile.th.active" => '활동내용',
+"form.admin.lock.period" => '날자로 된 간격을 로크하기',
+"form.admin.options" => '옵션',
+"form.admin.lang_default" => '사이트의 디폴트 언어',
+"form.admin.custom_date_format" => "날짜 포맷",
+"form.admin.custom_time_format" => "시간 포맷",
+"form.admin.start_week" => "주의 시작요일",
+
+// my time form attributes
+"form.mytime.title" => '나의 시간',
+"form.mytime.edit_title" => '시간기록을 편집하기',
+"form.mytime.del_str" => '시간기록을 삭제하기',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => '날짜',
+"form.mytime.project" => '프로젝트',
+"form.mytime.activity" => '활동내용',
+"form.mytime.start" => '시작',
+"form.mytime.finish" => '마감',
+"form.mytime.duration" => '기간',
+"form.mytime.note" => '표식',
+"form.mytime.behalf" => '다음을 위한 하루일과',
+"form.mytime.daily" => '하루일과',
+"form.mytime.total" => '전체 시간: ',
+"form.mytime.th.project" => '프로젝트',
+"form.mytime.th.activity" => '활동내용',
+"form.mytime.th.start" => '시작',
+"form.mytime.th.finish" => '마감',
+"form.mytime.th.duration" => '기간',
+"form.mytime.th.note" => '표식',
+"form.mytime.th.edit" => '편집',
+"form.mytime.th.delete" => '삭제',
+"form.mytime.del_yes" => '성과적으로 삭제된 시간기록',
+"form.mytime.no_finished_rec" => '이 기록은 시작 시간으로만 저장되었습니다. 이것은 오류는 아닙니다. 필요하면 로그아웃 하십시오.',
+"form.mytime.billable" => '청구가능',
+"form.mytime.warn_tozero_rec" => '이 시간기간이 로크되었으므로 이 시간기록은 삭제되어야 합니다',
+"form.mytime.uncompleted" => '완성되지 않은',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => '신규 관리자 계정을 생성',
+"form.profile.edit_title" => '프로필을 편집하기',
+"form.profile.name" => '이름',
+"form.profile.login" => '로그인ID',
+
+"form.profile.showchart" => '원 그래프를 보기',
+"form.profile.lang" => '언어',
+"form.profile.custom_date_format" => "날짜 포맷",
+"form.profile.custom_time_format" => "시간 포맷",
+"form.profile.default_format" => "(디폴트)",
+"form.profile.start_week" => "주의 시작요일",
+
+// people form attributes
+"form.people.ppl_str" => '멤버',
+"form.people.createu_str" => '신규 사용자를 만들기',
+"form.people.edit_str" => '사용자를 편집하기',
+"form.people.del_str" => '사용자를 삭제하기',
+"form.people.th.name" => '이름',
+"form.people.th.login" => '로그인ID',
+"form.people.th.role" => '직위',
+"form.people.th.edit" => '편집',
+"form.people.th.del" => '삭제',
+"form.people.th.status" => '상태',
+"form.people.th.project" => '프로젝트',
+"form.people.th.rate" => '급여',
+"form.people.manager" => '관리자',
+"form.people.comanager" => '공동관리자',
+"form.people.empl" => '사용자',
+"form.people.name" => '이름',
+"form.people.login" => '로그인ID',
+
+"form.people.rate" => '디폴트 시간당 급여',
+"form.people.comanager" => '공동관리자',
+"form.people.projects" => '프로젝트',
+
+// projects form attributes
+"form.project.proj_title" => '프로젝트',
+"form.project.edit_str" => '프로젝트를 편집하기',
+"form.project.add_str" => '신규 프로젝트를 추가하기',
+"form.project.del_str" => '프로젝트를 삭제하기',
+"form.project.th.name" => '이름',
+"form.project.th.edit" => '편집',
+"form.project.th.del" => '삭제',
+"form.project.name" => '이름',
+
+// activities form attributes
+"form.activity.act_title" => '활동내용',
+"form.activity.add_title" => '신규 활동내용을 추가하기',
+"form.activity.edit_str" => '활동내용을 편집하기',
+"form.activity.del_str" => '활동내용을 삭제하기',
+"form.activity.name" => '이름',
+"form.activity.project" => '프로젝트',
+"form.activity.th.name" => '이름',
+"form.activity.th.project" => '프로젝트',
+"form.activity.th.edit" => '편집',
+"form.activity.th.del" => '삭제',
+
+// report attributes
+"form.report.title" => '보고서',
+"form.report.from" => '시작 날짜',
+"form.report.to" => '마감 날짜',
+"form.report.groupby_user" => '사용자',
+"form.report.groupby_project" => '프로젝트',
+"form.report.groupby_activity" => '활동내용',
+"form.report.duration" => '기간',
+"form.report.start" => '시작',
+"form.report.activity" => '활동내용',
+"form.report.show_idle" => '유휴 상태를 보기',
+"form.report.finish" => '마감',
+"form.report.note" => '표식',
+"form.report.project" => '프로젝트',
+"form.report.totals_only" => '오직 전체만',
+"form.report.total" => '시간 총합',
+"form.report.th.empllist" => '사용자',
+"form.report.th.date" => '날짜',
+"form.report.th.project" => '프로젝트',
+"form.report.th.activity" => '활동내용',
+"form.report.th.start" => '시작',
+"form.report.th.finish" => '마감',
+"form.report.th.duration" => '기간',
+"form.report.th.note" => '주의',
+
+// mail form attributes
+"form.mail.from" => '부터',
+"form.mail.to" => '까지',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => '제목',
+"form.mail.comment" => '코멘트',
+"form.mail.above" => '이 보고서를 이메일로 송신',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>송신된 메시지</b>',
+
+// invoice attributes
+"form.invoice.title" => '송장',
+"form.invoice.caption" => '송장',
+"form.invoice.above" => '송장에 대한 보충정보',
+"form.invoice.select_cust" => '클라이언트의 선택',
+"form.invoice.fillform" => '필드들을 채우십시오',
+"form.invoice.date" => '날짜',
+"form.invoice.number" => '송장 번호',
+"form.invoice.tax" => '세금',
+"form.invoice.daily_subtotals" => '날짜별 소계',
+"form.invoice.yourcoo" => '당신의 이름<br> 및 주소',
+"form.invoice.custcoo" => '클라이언트 이름<br> 및 주소',
+"form.invoice.comment" => '코멘트 ',
+"form.invoice.th.username" => '개인',
+"form.invoice.th.time" => '시간',
+"form.invoice.th.rate" => '급여',
+"form.invoice.th.summ" => '수량',
+"form.invoice.subtotal" => '소계',
+"form.invoice.customer" => '클라이언트',
+"form.invoice.mailinv_above" => '이 송장을 이메일로 송신',
+"form.invoice.sending_str" => '<b>송신한 송장</b>',
+
+"form.migration.zip" => '압축',
+"form.migration.file" => '파일 선택',
+"form.migration.import.title" => '데이터 임포트',
+"form.migration.import.success" => '성과적으로 완료된 임포트',
+"form.migration.import.text" => 'xml 파일로부터 팀 데이터를 임포트',
+"form.migration.export.title" => '데이터 익스포트',
+"form.migration.export.success" => '성과적으로 완료된 익스포트',
+"form.migration.export.text" => '팀의 모든 데이터를 xml 파일로 익스포트 할수 있습니다. 이것은 데이터를 당신자신의 서버에로 옮길때 쓸모있을수 있습니다.',
+"form.migration.compression.none" => '없음',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => '클라이언트',
+"form.client.add_title" => '클라이언트 추가',
+"form.client.edit_title" => '클라이언트 편집',
+"form.client.del_title" => '클라이언트 삭제',
+"form.client.th.name" => '이름',
+"form.client.th.edit" => '편집',
+"form.client.th.del" => '삭제',
+"form.client.name" => '이름',
+"form.client.tax" => '세금',
+"form.client.daily_subtotals" => '날짜별 소계',
+"form.client.yourcoo" => '송장에 밝힌 당신의 <br>이름 및 주소',
+"form.client.custcoo" => '주소',
+"form.client.comment" => '코멘트 ',
+
+// miscellaneous strings
+"forward.forgot_password" => '암호를 잊으셨습니까?',
+"forward.edit" => '편집',
+"forward.delete" => '삭제',
+"forward.tocsvfile" => '데이터를 .csv 파일로 익스포트',
+"forward.toxmlfile" => '데이터를 .xml 파일로 익스포트',
+"forward.geninvoice" => '송장 만들기',
+"forward.change" => '클라이언트 구성',
+
+// strings inside contols on forms
+"controls.select.project" => '--- 프로젝트 선택 ---',
+"controls.select.activity" => '--- 활동내용 선택 ---',
+"controls.select.client" => '--- 클라이언트 선택 ---',
+"controls.project_bind" => '--- 전부 ---',
+"controls.all" => '--- 전부 ---',
+"controls.notbind" => '--- 아니 ---',
+"controls.per_tm" => '이번달',
+"controls.per_lm" => '전번달',
+"controls.per_tw" => '이번주',
+"controls.per_lw" => '전번주',
+"controls.per_td" => '오늘',
+"controls.per_at" => '전시간',
+"controls.per_ty" => '올해',
+"controls.sel_period" => '--- 시간 기간을 선택 ---',
+"controls.sel_groupby" => '--- 그룹화되지 않음 ---',
+"controls.inc_billable" => '청구 가능한',
+"controls.inc_nbillable" => '청구 가능하지 않은',
+"controls.default" => '--- 디폴트 ---',
+
+// labels
+"label.chart.title1" => '사용자별 활동내용',
+"label.chart.title2" => '사용자별 프로젝트',
+"label.chart.period" => '기간에 따른 그래프',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>%s 을 대표하여</b>',
+"label.pminfo" => ' (관리자)',
+"label.pcminfo" => ' (공동관리자)',
+"label.painfo" => ' (관리자)',
+"label.time_noentry" => '항목이 없음',
+"label.today" => '오늘',
+"label.req_fields" => '* 필수 필드',
+"label.sel_project" => '프로젝트 선택',
+"label.sel_activity" => '활동내용 선택',
+"label.sel_tp" => '시간 기간을 선택',
+"label.set_tp" => '혹은 날짜를 설정',
+"label.fields" => '필드들을 보기',
+"label.group_title" => '다음것에 의한 그룹화',
+"label.include_title" => '기록을 포함',
+"label.inv_str" => '송장',
+"label.set_empl" => '사용자들을 선택',
+"label.sel_all" => '모두 선택',
+"label.sel_none" => '모두 해제',
+"label.or" => '혹은',
+"label.disable" => '무력화',
+"label.enable" => '가능화',
+"label.filter" => '필터',
+"label.timeweek" => '주별 합계',
+"label.hrs" => '시간',
+"label.errors" => '오류',
+"label.ldap_hint" => '아래의 필드들에서 <b>Windows 로그인</b> 및 <b>암호</b> 를 입력하십시오.',
+// Note to translators: the strings below need to be translated.
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/nl.lang.php b/WEB-INF/resources/nl.lang.php
new file mode 100644 (file)
index 0000000..e0352a1
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Nederlands';
+$i18n_months = array('Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December');
+$i18n_weekdays = array('Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag');
+$i18n_weekdays_short = array('Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/09', '04/30', '05/18', '05/28', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Aanmelden',
+'menu.logout' => 'Afmelden',
+'menu.forum' => 'Forum',
+'menu.help' => 'Help',
+'menu.create_team' => 'Maak team',
+'menu.profile' => 'Profiel',
+'menu.time' => 'Tijden',
+'menu.expenses' => 'Kosten',
+'menu.reports' => 'Rapporten',
+'menu.charts' => 'Grafieken',
+'menu.projects' => 'Projecten',
+'menu.tasks' => 'Taken',
+'menu.users' => 'Medewerkers',
+'menu.teams' => 'Teams',
+'menu.export' => 'Exporteren',
+'menu.clients' => 'Klanten',
+'menu.options' => 'Opties',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker is geschikt voor gebruik met mobiele telefoons.',
+'footer.credits' => 'Medewerkers',
+'footer.license' => 'Licentie',
+
+// Error messages.
+'error.access_denied' => 'Toegang geweigerd.',
+'error.sys' => 'Systeem fout.',
+'error.db' => 'Database fout.',
+'error.field' => 'Incorrecte gegevens: "{0}".',
+'error.empty' => 'Veld "{0}" is leeg.',
+'error.not_equal' => 'Veld "{0}" is niet gelijk aan veld "{1}".',
+'error.interval' => 'Veld "{0}" moet later zijn dan "{1}".',
+'error.project' => 'Kies project.',
+'error.task' => 'Kies taak.',
+'error.client' => 'Kies klant.',
+'error.report' => 'Kies rapport.',
+'error.auth' => 'Onjuiste loginnaam of wachtwoord.',
+'error.user_exists' => 'Een gebruiker met deze loginnaam bestaat al.',
+'error.project_exists' => 'Een project met deze naam bestaat al.',
+'error.task_exists' => 'Er bestaat al een taak met deze naam.',
+'error.client_exists' => 'Een klant met deze naam bestaat al.',
+'error.invoice_exists' => 'Dit nummer is al eens toegekend aan een factuur.',
+'error.no_invoiceable_items' => 'Er zijn geen factuureerbare onderdelen.',
+'error.no_login' => 'Een medewerker met deze loginnaam bestaat niet.',
+'error.no_teams' => 'Uw database is leeg. Meld je aan als admin en maak een nieuw team.',
+'error.upload' => 'Fout bij het uploaden van het bestand.',
+'error.period_locked' => 'Kan het proces niet afmaken. Oude gegevens of sommige dagen kunnen niet worden aangemaakt of gewijzigd. Dit is in te stellen in de "Profiel" pagina van de team manager bij "Uitsluit interval in dagen". Stel dit in op 0 om het proces af te kunnen maken. <br><br>Opgeslagen gegevens (met 0 of lege inhoud) kunnen worden verwijderd.',
+'error.mail_send' => 'Fout bij het versturen van een emailbericht.',
+'error.no_email' => 'Geen emailadres bekend voor dit account.',
+'error.uncompleted_exists' => 'Niet afgeronde invoer bestaat al. Sluit of verwijder deze.',
+'error.goto_uncompleted' => 'Ga naar onvolledige invoer.',
+'error.overlap' => 'De huidige registratie overlapt een reeds bestaande registratie.',
+'error.future_date' => 'Datum ligt in de toekomst.',
+
+// Labels for buttons.
+'button.login' => 'Aanmelden',
+'button.now' => 'Nu',
+'button.save' => 'Bewaren',
+'button.copy' => 'Kopiëren',
+'button.cancel' => 'Afbreken',
+'button.submit' => 'Bewaren',
+'button.add_user' => 'Medewerker toevoegen',
+'button.add_project' => 'Project toevoegen',
+'button.add_task' => 'Taak toevoegen',
+'button.add_client' => 'Klant toevoegen',
+'button.add_invoice' => 'Factuur toevoegen',
+'button.add_option' => 'Optie toevoegen',
+'button.add' => 'Toevoegen',
+'button.generate' => 'Genereren',
+'button.reset_password' => 'Herstel het wachtwoord',
+'button.send' => 'Verzenden',
+'button.send_by_email' => 'Verzend per e-mail',
+'button.create_team' => 'Maak team',
+'button.export' => 'Team exporteren',
+'button.import' => 'Team importeren',
+'button.close' => 'Sluiten',
+'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Teamnaam',
+'label.address' => 'Adres',
+'label.currency' => 'Munteenheid',
+'label.manager_name' => 'Naam van de manager',
+'label.manager_login' => 'Loginnaam van de manager',
+'label.person_name' => 'Naam',
+'label.thing_name' => 'Naam',
+'label.login' => 'Loginnaam',
+'label.password' => 'Wachtwoord',
+'label.confirm_password' => 'Bevestig wachtwoord',
+'label.email' => 'Emailadres',
+'label.date' => 'Datum',
+'label.start_date' => 'Begindatum',
+'label.end_date' => 'Einddatum',
+'label.user' => 'Medewerker',
+'label.users' => 'Medewerkers',
+'label.client' => 'Klant',
+'label.clients' => 'Klanten',
+'label.option' => 'Optie',
+'label.invoice' => 'Factuur',
+'label.project' => 'Project',
+'label.projects' => 'Projecten',
+'label.task' => 'Taak',
+'label.tasks' => 'Taken',
+'label.description' => 'Omschrijving',
+'label.start' => 'Aanvang',
+'label.finish' => 'Einde',
+'label.duration' => 'Tijdsduur',
+'label.note' => 'Opmerking',
+'label.item' => 'Artikel',
+'label.cost' => 'Kosten',
+'label.week_total' => 'Week totaal',
+'label.day_total' => 'Dag totaal',
+'label.today' => 'Vandaag',
+'label.total_hours' => 'Uren totaal',
+'label.total_cost' => 'Totale kosten',
+'label.view' => 'Bekijk',
+'label.edit' => 'Wijzig',
+'label.delete' => 'Verwijderen',
+'label.configure' => 'Stel in',
+'label.select_all' => 'Selecteer alle',
+'label.select_none' => 'Selecteer niets',
+'label.id' => 'ID',
+'label.language' => 'Taal',
+'label.decimal_mark' => 'Decimaal teken',
+'label.lock_interval' => 'Uitsluit interval in dagen',
+'label.date_format' => 'Datum formaat',
+'label.time_format' => 'Tijdsaanduiding',
+'label.week_start' => 'Eerste dag van de week',
+'label.comment' => 'Opmerkingen',
+'label.status' => 'Status',
+'label.tax' => 'BTW',
+'label.subtotal' => 'Subtotaal',
+'label.total' => 'Totaal',
+'label.client_name' => 'Naam van de klant',
+'label.client_address' => 'Adres van de klant',
+'label.or' => 'of',
+'label.error' => 'Fout',
+'label.ldap_hint' => 'Type uw <b>Windows login</b> en <b>wachtwoord</b> in de onderstaande velden.',
+'label.required_fields' => '* - verplichte velden',
+'label.on_behalf' => 'namens',
+'label.role_manager' => '(manager)',
+'label.role_comanager' => '(co-manager)',
+'label.role_admin' => '(beheerder)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Eigen velden',
+'label.type' => 'Type',
+'label.type_dropdown' => 'uitklapbaar',
+'label.type_text' => 'tekst',
+'label.required' => 'Verplicht veld',
+'label.fav_report' => 'Standaard rapport',
+'label.cron_schedule' => 'Cron schema',
+'label.what_is_it' => 'Wat betekent dit?',
+
+// Form titles.
+'title.login' => 'Aanmelden',
+'title.teams' => 'Teams',
+'title.create_team' => 'Team maken',
+'title.edit_team' => 'Team bewerken',
+'title.delete_team' => 'Team aan het verwijderen',
+'title.reset_password' => 'Wachtwoord herstellen',
+'title.change_password' => 'Wachtwoord aan het veranderen',
+'title.time' => 'Tijdsregistraties',
+'title.edit_time_record' => 'Wijzigen tijdrecord',
+'title.delete_time_record' => 'Verwijder tijdrecord',
+'title.expenses' => 'Kosten',
+'title.edit_expense' => 'Bewerk kosten artikel',
+'title.delete_expense' => 'Verwijder kosten artikel',
+'title.reports' => 'Rapporten',
+'title.report' => 'Rapport',
+'title.send_report' => 'Rapport aan het versturen',
+'title.invoice' => 'Factuur',
+'title.send_invoice' => 'Factuur verzenden',
+'title.charts' => 'Grafieken',
+'title.projects' => 'Projecten',
+'title.add_project' => 'Project toevoegen',
+'title.edit_project' => 'Project wijzigen',
+'title.delete_project' => 'Project verwijderen',
+'title.tasks' => 'Taken',
+'title.add_task' => 'Taak toevoegen',
+'title.edit_task' => 'Taak wijzigen',
+'title.delete_task' => 'Taak verwijderen',
+'title.users' => 'Medewerkers',
+'title.add_user' => 'Medewerker toevoegen',
+'title.edit_user' => 'Medewerker wijzigen',
+'title.delete_user' => 'Medewerker verwijderen',
+'title.clients' => 'Klanten',
+'title.add_client' => 'Klant toevoegen',
+'title.edit_client' => 'Klant wijzigen',
+'title.delete_client' => 'Klant verwijderen',
+'title.invoices' => 'Facturen',
+'title.add_invoice' => 'Factuur toevoegen',
+'title.view_invoice' => 'Factuur bekijken',
+'title.delete_invoice' => 'Factuur verwijderen',
+'title.notifications' => 'Notificaties',
+'title.add_notification' => 'Notificatie toevoegen',
+'title.edit_notification' => 'Notificatie bewerken',
+'title.delete_notification' => 'Notificatie verwijderen',
+'title.export' => 'Exporteer teamgegevens',
+'title.import' => 'Importeer teamgegevens',
+'title.options' => 'Opties',
+'title.profile' => 'Profiel',
+'title.cf_custom_fields' => 'Eigen velden',
+'title.cf_add_custom_field' => 'Eigen veld toevoegen',
+'title.cf_edit_custom_field' => 'Eigen veld bewerken',
+'title.cf_delete_custom_field' => 'Eigen veld verwijderen',
+'title.cf_dropdown_options' => 'Uitvouwmogelijkheden',
+'title.cf_add_dropdown_option' => 'Uitvouwmogelijkheid toevoegen',
+'title.cf_edit_dropdown_option' => 'Uitvouwmogelijkheid bewerken',
+'title.cf_delete_dropdown_option' => 'Uitvouwmogelijkheid verwijderen',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- allemaal ---',
+'dropdown.no' => '--- geen ---',
+'dropdown.this_day' => 'vandaag',
+'dropdown.this_week' => 'deze week',
+'dropdown.last_week' => 'vorige week',
+'dropdown.this_month' => 'deze maand',
+'dropdown.last_month' => 'vorige maand',
+'dropdown.this_year' => 'dit jaar',
+'dropdown.all_time' => 'alles',
+'dropdown.projects' => 'projecten',
+'dropdown.tasks' => 'taken',
+'dropdown.clients' => 'klanten',
+'dropdown.select' => '--- kies ---',
+'dropdown.select_invoice' => '--- kies factuur ---',
+'dropdown.status_active' => 'actief',
+'dropdown.status_inactive' => 'inactief',
+'dropdown.delete'=>'verwijderen',
+'dropdown.do_not_delete'=>'niet verwijderen',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Wachtwoord vergeten?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> is een eenvoudig en gemakkelijk te gebruiken open source tijdregistratiesysteem.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Het verzoek om het wachtwoord te herstellen is verzonden per email.',
+'form.reset_password.email_subject' => 'Anuko Time Tracker wachtwoord herstel verzoek',
+'form.reset_password.email_body' => "Geachte medewerker,\n\nIemand, mogelijk uzelf, heeft verzocht uw wachtwoord in Anuko Time Tracker te herstellen. Klik op deze link als u uw wachtwoord wil wijzigen.\n\n%s\n\nAnuko Time Tracker is een eenvoudig en gemakkelijk te gebruiken open source tijdregistratiesysteem. Bezoek https://www.anuko.com voor meer informatie.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Voer het nieuwe wachtwoord in en klik op Bewaren.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(uu:mm of 0.0u)',
+'form.time.billable' => 'Factureerbaar',
+'form.time.uncompleted' => 'Onvolledig',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Dit tijdrecord is opgeslagen met alleen een starttijd. Dit is geen fout.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Bewaren als standaard',
+'form.reports.confirm_delete' => 'Weet u zeker dat u deze favoriete rapportage wilt verwijderen?',
+'form.reports.include_records' => 'Bijsluiten regels',
+'form.reports.include_billable' => 'factureerbaar',
+'form.reports.include_not_billable' => 'niet factureerbaar',
+'form.reports.include_invoiced' => 'gefactureerd',
+'form.reports.include_not_invoiced' => 'niet gefactureerd',
+'form.reports.select_period' => 'Kies periode',
+'form.reports.set_period' => 'of stel datums in',
+'form.reports.show_fields' => 'Toon velden',
+'form.reports.group_by' => 'Groeperen op',
+'form.reports.group_by_no' => '--- niet groeperen ---',
+'form.reports.group_by_date' => 'datum',
+'form.reports.group_by_user' => 'medewerker',
+'form.reports.group_by_client' => 'klant',
+'form.reports.group_by_project' => 'project',
+'form.reports.group_by_task' => 'taak',
+'form.reports.totals_only' => 'Alleen totalen',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Exporteer',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Factuur nummer',
+'form.invoice.person' => 'Medewerker',
+'form.invoice.invoice_to_delete' => 'Te verwijderen factuur',
+'form.invoice.invoice_entries' => 'Factuur gegevens',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Periode',
+'form.charts.chart' => 'Grafiek',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Actieve projecten',
+'form.projects.inactive_projects' => 'Inactieve projecten',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Actieve taken',
+'form.tasks.inactive_tasks' => 'Inactieve taken',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Actieve medewerkers',
+'form.users.inactive_users' => 'Inactieve medewerkers',
+'form.users.role' => 'Rol',
+'form.users.manager' => 'Manager',
+'form.users.comanager' => 'Co-manager',
+'form.users.rate' => 'Tarief',
+'form.users.default_rate' => 'Standaard uurtarief',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+'form.client.client_to_delete' => 'Klant die wordt verwijderd',
+'form.client.client_entries' => 'Klant gegevens',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Actieve klanten',
+'form.clients.inactive_clients' => 'Inactieve klanten',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'U kunt alle teamgegevens naar een xml bestand exporteren. Dit kan zinvol zijn als u gegevens migreert naar uw eigen server.',
+'form.export.compression' => 'Compressie',
+'form.export.compression_none' => 'geen',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Importeer teamgegevens uit een xml bestand.',
+'form.import.file' => 'Kies bestand',
+'form.import.success' => 'Importeren gelukt.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' => 'Maak een nieuw team door een team  manager account aan te maken.<br>U kunt ook teamgegevens importeren uit een xml file van een andere Anuko Time Tracker server (login namen moeten uniek zijn).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 uurs',
+'form.profile.24_hours' => '24 uurs',
+'form.profile.tracking_mode' => 'Bijhouden',
+'form.profile.mode_time' => 'tijd',
+'form.profile.mode_projects' => 'projecten',
+'form.profile.mode_projects_and_tasks' => 'projecten en taken',
+'form.profile.record_type' => 'Registratie type',
+'form.profile.type_all' => 'begin, einde en duur',
+'form.profile.type_start_finish' => 'begin en einde',
+'form.profile.type_duration' => 'duur',
+'form.profile.plugins' => 'Plugins',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'Van',
+'form.mail.to' => 'Aan',
+'form.mail.cc' => 'Cc',
+'form.mail.subject' => 'Onderwerp',
+'form.mail.report_subject' => 'Time Tracker Rapport',
+'form.mail.footer' => 'Anuko Time Tracker is een eenvoudig en gemakkelijk te gebruiken open source tijdregistratiesysteem. Bezoek <a href="https://www.anuko.com">www.anuko.com</a> voor meer informatie.',
+'form.mail.report_sent' => 'Rapport is verzonden.',
+'form.mail.invoice_sent' => 'Factuur is verzonden.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/no.lang.php b/WEB-INF/resources/no.lang.php
new file mode 100644 (file)
index 0000000..ca05af0
--- /dev/null
@@ -0,0 +1,424 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Norsk';
+$i18n_months = array('Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember');
+$i18n_weekdays = array('Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag');
+// TODO: check translation of $i18n_weekdays_short.
+$i18n_weekdays_short = array('Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/05', '04/09', '04/10', '04/12', '04/13', '05/01', '05/17', '05/21', '05/31', '06/01', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'Innlogging',
+'menu.logout' => 'Logg ut',
+'menu.feedback' => 'Tilbakemelding',
+'menu.help' => 'Hjelp',
+// Note to translators: menu.create_team needs an improved translation.
+'menu.create_team' => 'Lag ny adminkonto',
+'menu.edit_profile' => 'Endre profil',
+'menu.my_time' => 'Min tid',
+'menu.reports' => 'Rapporter',
+'menu.charts' => 'Diagrammer',
+'menu.projects' => 'Prosjekter',
+'menu.activities' => 'Aktiviteter',
+'menu.people' => 'Personer',
+// Note to translators: check translation of menu.teams. This is for a list of teams in the time tracker for admin.
+// 'menu.teams' => 'team',
+'menu.export' => 'Eksport',
+'menu.clients' => 'Klienter',
+'menu.options' => 'Opsjoner',
+'menu.admin' => 'Admin',
+
+// Error messages.
+'error.db' => 'Databasefeil.',
+'error.field' => 'Feil "{0}" data.',
+'error.empty' => 'Feltet "{0}" er tomt.',
+'error.not_equal' => 'Feltet "{0}" stemmer ikke med "{1}".',
+'error.interval' => 'Feil intervall.',
+'error.project' => 'Velg prosjekt.',
+'error.activity' => 'Velg aktivitet.',
+'error.auth' => 'Feil brukernavn eller passord.',
+'error.user_exists' => 'Bruker med et slikt brukernavn eksisterer allerede.',
+'error.project_exists' => 'Et prosjekt med dette navnet er allerede opprettet.',
+'error.activity_exists' => 'En aktivitet med dette navnet er allerede opprettet.',
+'error.client_exists' => 'En klient med dette navnet er allerede opprettet.',
+'error.no_login' => 'Det er ingen bruker med dette brukernavnet.',
+'error.upload' => 'Feil med lasting av fil.',
+// TODO: translate error.period_locked.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => 'Feil ved sending av e-post.',
+'error.no_email' => 'Det er ingen e-post knyttet til dette brukernavnet.',
+'error.uncompleted_exists' => 'Ufullført registrering finnes allerede. Lukk eller slett den.',
+'error.goto_uncompleted' => 'Gå til ufullført registrering.',
+
+// Labels for buttons.
+'button.login' => 'Innlogging',
+'button.now' => 'Nå',
+// 'button.set' => 'Sette',
+'button.save' => 'Lagre',
+'button.cancel' => 'Avbryt',
+// TODO: check translation of button.submit. It is used on several pages, including https://timetracker.anuko.com/time.php.
+// 'button.submit' => 'Registrer',
+'button.add_user' => 'Legg til bruker',
+'button.add_project' => 'Legg til prosjekt',
+'button.add_activity' => 'Legg til aktivitet',
+'button.add_client' => 'Legg til klient',
+'button.add' => 'Legg til',
+'button.generate' => 'Generer',
+'button.reset_password' => 'Resett passord',
+'button.send' => 'Send',
+'button.send_by_email' => 'Send som e-post',
+'button.save_as_new' => 'Lagre som ny',
+'button.create_team' => 'Opprett team',
+'button.export' => 'Eksport team',
+'button.import' => 'Importer team',
+// 'button.apply' => 'bruk',
+// TODO: translate button.close.
+// 'button.close' => 'Close',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+// TODO: translate label.team_name
+// 'label.team_name' => 'Team name',
+'label.currency' => 'Valuta',
+// TODO: translate these strings.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+// 'label.person_name' => 'Name',
+// 'label.thing_name' => 'Name',
+'label.password' => 'Passord',
+'label.confirm_password' => 'Bekreft passord',
+// 'label.email' => 'email',
+'label.total' => 'totalt',
+
+// TODO: Please check the translation against the current English file as many things are being refactored. For example, many labels have been added after label.email.
+
+// "form.filter.project" => 'prosjekt',
+// "form.filter.filter" => 'favorittrapport',
+// "form.filter.filter_new" => 'lagre som favoritt',
+// "form.filter.filter_confirm_delete" => 'er du sikker på at du vil slette denne favorittrapporten?',
+
+// login form attributes
+"form.login.title" => 'innlogging',
+"form.login.login" => 'innlogging',
+
+// password reminder form attributes
+// Note to translators: "form.fpass.title" => 'remind password', // the string must be translated
+"form.fpass.login" => 'innlogging',
+"form.fpass.send_pass_str" => 'passordet er sendt',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker passordet ditt',
+// Note to translators: strings below need to be translated.
+// "form.fpass.send_pass_body" => "Kjære bruker,\n\nNoen, trolig deg, bad om å få ditt Anuko Time Tracker password resatt. Vær vennlig å besøk denne lenken dersom du ønsker at passordet ditt skal resettes.\n\n%s\n\nAnuko Time Tracker er et enkelt og brukervennlig system for tidsregistrering basert på åpen kildekode. Les mer på https://www.anuko.com.\n\n",
+// "form.fpass.reset_comment" => "vennligst skriv inn passordet og klikk på lagre for å resette passsordet.",
+
+// Note to translators: the strings below must be translated
+// // administrator form
+// "form.admin.title" => 'administrator',
+// "form.admin.duty_text" => 'opprett et nytt team ved å opprette en ny team manager konto.<br>du kan også importere team data fra en xml fil fra en annen Anuko Time Tracker server (ingen login kollisjoner er tillatt).',
+
+// "form.admin.change_pass" => 'bytt passord på administratorkontoen',
+// "form.admin.profile.title" => 'team',
+// "form.admin.profile.noprofiles" => 'databasen din er tom. logg inn som admin og opprett et nytt team.',
+// "form.admin.profile.comment" => 'slett team',
+// "form.admin.profile.th.id" => 'id',
+// "form.admin.profile.th.name" => 'navn',
+// "form.admin.profile.th.edit" => 'endre',
+// "form.admin.profile.th.del" => 'slett',
+// "form.admin.profile.th.active" => 'aktiv',
+// "form.admin.lock.period" => 'lås intervallet i antall dager',
+// "form.admin.options" => 'opsjoner',
+// "form.admin.lang_default" => 'sidens språk',
+// "form.admin.custom_date_format" => "datoformat",
+// "form.admin.custom_time_format" => "tidsformat",
+// "form.admin.start_week" => "første ukedag",
+
+// my time form attributes
+// Note to translators: the 2 strings below must be translated
+// "form.mytime.title" => 'min tid',
+// "form.mytime.edit_title" => 'endre tidsoppføringen',
+"form.mytime.del_str" => 'slett tids oppføringen',
+"form.mytime.time_form" => ' (tt:mm)',
+// Note to translators: "form.mytime.date" => 'dato', // the string must be translated
+"form.mytime.project" => 'prosjekt',
+"form.mytime.activity" => 'aktivitet',
+"form.mytime.start" => 'starttid',
+"form.mytime.finish" => 'ferdig',
+"form.mytime.duration" => 'varighet',
+"form.mytime.note" => 'notat',
+"form.mytime.behalf" => 'daglig arbeide for',
+"form.mytime.daily" => 'daglig arbeide',
+"form.mytime.total" => 'totalt antall timer: ',
+"form.mytime.th.project" => 'prosjekt',
+"form.mytime.th.activity" => 'aktivitet',
+"form.mytime.th.start" => 'starttid',
+"form.mytime.th.finish" => 'ferdig',
+"form.mytime.th.duration" => 'varighet',
+"form.mytime.th.note" => 'notat',
+"form.mytime.th.edit" => 'endre',
+"form.mytime.th.delete" => 'slett',
+// Note to translators: the strings below must be translated
+// "form.mytime.del_yes" => 'tidsoppføringen er slettet', 
+// "form.mytime.no_finished_rec" => 'Denne oppføringen ble lagret kun med starttid. Det er ikke en feil. Logg ut om nødvendig.',
+// "form.mytime.billable" => 'kan faktureres',
+// "form.mytime.warn_tozero_rec" => 'Denne oppføringen må slettes fordi tidsperioden er låst',
+// "form.mytime.uncompleted" => 'uferdig',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'lag ny adminkonto',
+"form.profile.edit_title" => 'endre profil',
+"form.profile.name" => 'navn',
+"form.profile.login" => 'innlogging',
+
+// Note to translators: the strings below are missing and must be added and translated
+// "form.profile.showchart" => 'vis kakediagram',
+// "form.profile.lang" => 'språk',
+// "form.profile.custom_date_format" => "dato format",
+// "form.profile.custom_time_format" => "tims format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "første dag i uken",
+
+// people form attributes
+"form.people.ppl_str" => 'personer',
+"form.people.createu_str" => 'legg til ny bruker',
+"form.people.edit_str" => 'endre bruker',
+"form.people.del_str" => 'slett bruker',
+"form.people.th.name" => 'navn',
+"form.people.th.login" => 'innlogging',
+"form.people.th.role" => 'rolle',
+"form.people.th.edit" => 'endre',
+"form.people.th.del" => 'slett',
+"form.people.th.status" => 'status',
+// Note to translators: the 2 strings below are missing and must be added and translated
+// "form.people.th.project" => 'prosjekt',
+// "form.people.th.rate" => 'timesats',
+// Note to translators: the strings below must be correctly translated
+// "form.people.manager" => 'admin',
+// "form.people.comanager" => 'co-manager',
+"form.people.empl" => 'bruker',
+"form.people.name" => 'navn',
+"form.people.login" => 'innlogging',
+
+"form.people.rate" => 'timesats',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.people.comanager" => 'co-manager',
+// "form.people.projects" => 'prosjekter',
+
+// projects form attributes
+"form.project.proj_title" => 'prosjekter',
+"form.project.edit_str" => 'endre prosjekt',
+"form.project.add_str" => 'legg til nytt prosjekt',
+"form.project.del_str" => 'slett prosjekt',
+"form.project.th.name" => 'navn',
+"form.project.th.edit" => 'endre',
+"form.project.th.del" => 'slett',
+"form.project.name" => 'navn',
+
+// activities form attributes
+"form.activity.act_title" => 'aktiviteter',
+"form.activity.add_title" => 'legg til ny aktivitet',
+"form.activity.edit_str" => 'endre aktivitet',
+// Note to translators: "form.activity.del_str" => 'slett aktivitet', // the string is incompletely translated
+"form.activity.name" => 'navn',
+"form.activity.project" => 'prosjekt',
+"form.activity.th.name" => 'navn',
+"form.activity.th.project" => 'prosjekt',
+"form.activity.th.edit" => 'endre',
+"form.activity.th.del" => 'slett',
+
+// report attributes
+"form.report.title" => 'rapporter',
+"form.report.from" => 'starttid',
+"form.report.to" => 'ferdig',
+"form.report.groupby_user" => 'bruker',
+"form.report.groupby_project" => 'prosjekt',
+"form.report.groupby_activity" => 'aktivitet',
+"form.report.duration" => 'varighet',
+"form.report.start" => 'starttid',
+"form.report.activity" => 'aktivitet',
+"form.report.show_idle" => 'antall dager ikke aktiv',
+"form.report.finish" => 'ferdig',
+"form.report.note" => 'notat',
+"form.report.project" => 'prosjekt',
+// Note to translators: the strings below must be translated 
+// "form.report.totals_only" => 'kun summer',
+"form.report.total" => 'totalt antall timer',
+"form.report.th.empllist" => 'bruker',
+"form.report.th.date" => 'dato',
+"form.report.th.project" => 'prosjekt',
+"form.report.th.activity" => 'aktivitet',
+"form.report.th.start" => 'starttid',
+"form.report.th.finish" => 'ferdig',
+"form.report.th.duration" => 'varighet',
+"form.report.th.note" => 'notat',
+
+// mail form attributes
+"form.mail.from" => 'fra',
+"form.mail.to" => 'til',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'emne',
+"form.mail.comment" => 'kommentar',
+"form.mail.above" => 'send denne rapporten som e-post',
+// Note to translators: the strings below must be translated
+// "form.mail.footer_str" => 'Anuko Time Tracker is et enkelt, brukervennlig tidsregistreringssystem<br>basert på åpen kildekode. Besøk <a href="https://www.anuko.com">www.anuko.com</a> for flere opplysninger.',
+// "form.mail.sending_str" => '<b>the message has been sent</b>',
+
+// invoice attributes
+"form.invoice.title" => 'faktura',
+"form.invoice.caption" => 'faktura',
+"form.invoice.above" => 'tilleggsinformasjon for faktura',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.invoice.select_cust" => 'velg klient',
+// "form.invoice.fillform" => 'fyll inn i feltene',
+"form.invoice.date" => 'dato',
+"form.invoice.number" => 'fakturanummer',
+"form.invoice.tax" => 'MVA',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.invoice.daily_subtotals" => 'daglige delbeløp',
+"form.invoice.yourcoo" => 'ditt navn<br> og adresse',
+"form.invoice.custcoo" => 'kundens navn<br> og adresse',
+"form.invoice.comment" => 'notat',
+"form.invoice.th.username" => 'person',
+"form.invoice.th.time" => 'timer',
+"form.invoice.th.rate" => 'sats',
+"form.invoice.th.summ" => 'antall',
+"form.invoice.subtotal" => 'delsum',
+"form.invoice.customer" => 'kommentar',
+"form.invoice.mailinv_above" => 'send denne fakturaen som e-post',
+// Note to translators: "form.invoice.sending_str" => '<b>invoice has been sent</b>', // the string must be translated
+
+// Note to translators: the strings below are missing and must be added and translated
+// "form.migration.zip" => 'komprimering',
+// "form.migration.file" => 'velg fil',
+// "form.migration.import.title" => 'import data',
+// "form.migration.import.success" => 'import gjennomført vellykket',
+// "form.migration.import.text" => 'import team data fra en xml fil',
+// "form.migration.export.title" => 'export data',
+// "form.migration.export.success" => 'eksport gjennomført vellykket',
+// "form.migration.export.text" => 'du kan eksportere alle team data til en XML fil. dette kan være nyttig dersom du skal migrere data til din egen server.',
+// "form.migration.compression.none" => 'ingen',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+// "form.client.title" => 'klienter',
+// "form.client.add_title" => 'legg til klient',
+// "form.client.edit_title" => 'endre klient',
+// "form.client.del_title" => 'slett klient',
+// "form.client.th.name" => 'navn',
+// "form.client.th.edit" => 'endre',
+// "form.client.th.del" => 'slett',
+// "form.client.name" => 'navn',
+// "form.client.tax" => 'avgift',
+// "form.client.daily_subtotals" => 'daglige delbeløp',
+// "form.client.yourcoo" => 'ditt navn<br> og adresse i faktura',
+// "form.client.custcoo" => 'adresse',
+// "form.client.comment" => 'kommentar ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'glemt passordet?',
+"forward.edit" => 'endre',
+"forward.delete" => 'slett',
+"forward.tocsvfile" => 'eksporter data til en .csv fil',
+// Note to translators: the strings below are missing and must be translated and added
+// "forward.toxmlfile" => 'eksporter data til en .xml fil',
+// "forward.geninvoice" => 'lag faktura',
+// "forward.change" => 'konfigur klienter',
+
+// strings inside contols on forms
+"controls.select.project" => '--- velg prosjekt ---',
+"controls.select.activity" => '--- velg aktivitet ---',
+// Note to translators: the string below is missing and must be translated and added
+// "controls.select.client" => '--- velg klient ---',
+"controls.project_bind" => '--- alle ---',
+"controls.all" => '--- alle ---',
+// Note to translators: the strings below are missing and must be translated and added 
+// "controls.notbind" => '--- no ---',
+"controls.per_tm" => 'denne måneden',
+"controls.per_lm" => 'forrige måned',
+"controls.per_tw" => 'denne uken',
+"controls.per_lw" => 'forrige uke',
+// Note to translators: the strings below are missing and must be translated and added
+// "controls.per_td" => 'i dag',
+// "controls.per_at" => 'all tid',
+// "controls.per_ty" => 'dette årr',
+"controls.sel_period" => '--- velg tidsperiode ---',
+"controls.sel_groupby" => '--- ingen sortering ---',
+// Note to translators: the strings below are missing and must be translated and added
+// "controls.inc_billable" => 'fakturerbar',
+// "controls.inc_nbillable" => 'ikke fakturerbar',
+// "controls.default" => '--- default ---',
+
+// labels
+// Note to translators: the strings below are missing and must be translated and added
+// "label.chart.title1" => 'aktiviteter for bruker',
+// "label.chart.title2" => 'prosjekter for bruker',
+// "label.chart.period" => 'diagram for perioden',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>på vegne av %s</b>',
+// Note to translators: the strings below must be correctly translated
+// "label.pminfo" => ' (admin)',
+// "label.pcminfo" => ' (co-manager)',
+// "label.painfo" => ' (administrator)',
+"label.time_noentry" => 'ingen tilgang',
+"label.today" => 'i dag',
+"label.req_fields" => '* obligatoriske felt',
+"label.sel_project" => 'velg prosjekt',
+"label.sel_activity" => 'velg aktivitet',
+"label.sel_tp" => 'velg tidsperiode',
+"label.set_tp" => 'eller sett dato',
+"label.fields" => 'vis feltene',
+"label.group_title" => 'sorter på',
+// Note to translators: the strings below must be translated
+// "label.include_title" => 'ta med oppføringer',
+// "label.inv_str" => 'faktura',
+"label.set_empl" => 'velg brukere',
+// Note to translators: the strings below are missing and must be translated and added
+// "label.sel_all" => 'velg alle',
+// "label.sel_none" => 'velg ingen',
+// "label.or" => 'or',
+// "label.disable" => 'slå av',
+// "label.enable" => 'slå på',
+// "label.filter" => 'filter',
+// "label.timeweek" => 'uken totalt',
+// "label.hrs" => 'timer',
+// "label.errors" => 'feil',
+// "label.ldap_hint" => 'Skriv din <b>Windows login</b> og <b>passord</b> i feltene nedenfor.',
+// "label.calendar_today" => 'i dag',
+// "label.calendar_close" => 'lukk',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker er et enkelt, brukervennlig tidsregistreringssystem basert på åpen kildekode.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/pl.lang.php b/WEB-INF/resources/pl.lang.php
new file mode 100644 (file)
index 0000000..141dcae
--- /dev/null
@@ -0,0 +1,396 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Polski';
+$i18n_months = array('Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień');
+$i18n_weekdays = array('Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota');
+$i18n_weekdays_short = array('Nd', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/05', '04/05', '04/06', '05/01', '05/03', '05/24', '06/04', '08/15', '11/01', '11/11', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Zaloguj',
+'menu.logout' => 'Wyloguj',
+'menu.forum' => 'Forum',
+'menu.help' => 'Pomoc',
+'menu.create_team' => 'Stwórz zespół',
+'menu.profile' => 'Profil',
+'menu.time' => 'Czas',
+'menu.expenses' => 'Wydatki',
+'menu.reports' => 'Raporty',
+'menu.charts' => 'Statystyki', // TODO: is this correct translation for Charts?
+'menu.projects' => 'Projekty',
+'menu.tasks' => 'Zadania',
+'menu.users' => 'Użytkownicy',
+'menu.teams' => 'Zespoły',
+'menu.export' => 'Eksport',
+'menu.clients' => 'Klienci',
+'menu.options' => 'Opcje',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker jest dostępny na urządzeniach przenośnych.',
+'footer.credits' => 'Twórcy',
+'footer.license' => 'Licencja',
+
+// Error messages.
+'error.access_denied' => 'Odmowa dostępu.',
+'error.sys' => 'Błąd systemu.',
+'error.db' => 'Błąd bazy danych.',
+'error.field' => 'Niepoprawne dane: "{0}".',
+'error.empty' => 'Pole "{0}" jest puste.',
+'error.not_equal' => 'Wartość z pola "{0}" jest inna niż wartość z pola "{1}".',
+'error.interval' => 'Wartość z pola "{0}" musi być większe niż wartość z pola "{1}".',
+'error.project' => 'Wybierz projekt.',
+'error.task' => 'Wybierz zadanie.',
+'error.client' => 'Wybierz klienta.',
+'error.report' => 'Wybierz raport.',
+'error.auth' => 'Błędny login lub hasło.',
+'error.user_exists' => 'Użytkownik o takiej nazwie już istnieje.',
+'error.project_exists' => 'Projekt o takiej nazwie już istnieje.',
+'error.task_exists' => 'Zadanie o takiej nazwie już istnieje.',
+'error.client_exists' => 'Klient o takiej nazwie już istnieje.',
+'error.invoice_exists' => 'Faktura o tym numerze już istnieje.',
+'error.no_invoiceable_items' => 'Brak przedmiotów do faktury.',
+'error.no_login' => 'Użytkownik o takiej nazwie nie istnieje.',
+'error.no_teams' => 'Twoja baza danych jest pusta. Zaloguj się jako administrator i stwórz nowy zespół.',
+'error.upload' => 'Błąd podczas wysyłania pliku.',
+'error.period_locked' => 'Nie można dokończyć operacji. Wpisy starsze niż określona liczba dni nie mogą być tworzone lub modyfikowane. Manager zespołu ustala tę liczbę w polu "Blokada edycji po okresie (dni)" na stronie "Profil". Ustaw na 0, aby wyłączyć blokowanie. <br><br>Niedokończone wpisy (z czasem trwania 0 lub pustym polem) mogą zostać usunięte.',
+'error.mail_send' => 'Błąd podczas wysyłania wiadomości e-mail.',
+'error.no_email' => 'Żaden adres e-mail nie jest skojarzony z tym loginem.',
+'error.uncompleted_exists' => 'Istnieje niedokończony wpis. Zamknij go lub usuń.',
+'error.goto_uncompleted' => 'Przejdź do niedokończonego wpisu.',
+'error.overlap' => 'Okres czasowy nakłada się z istniejącymi wpisami.',
+'error.future_date' => 'Data jest w przyszłości.',
+
+// Labels for buttons
+'button.login' => 'Login',
+'button.now' => 'Teraz',
+'button.save' => 'Zapisz',
+'button.copy' => 'Kopiuj',
+'button.cancel' => 'Anuluj',
+'button.submit' => 'Zatwierdź',
+'button.add_user' => 'Dodaj użytkownika',
+'button.add_project' => 'Dodaj projekt',
+'button.add_task' => 'Dodaj zadanie',
+'button.add_client' => 'Dodaj klienta',
+'button.add_invoice' => 'Dodaj fakturę',
+'button.add_option' => 'Dodaj opcję',
+'button.add' => 'Dodaj',
+'button.generate' => 'Wygeneruj',
+'button.reset_password' => 'Resetuj hasło',
+'button.send' => 'Wyślij',
+'button.send_by_email' => 'Wyślij e-mail',
+'button.create_team' => 'Stwórz zespół',
+'button.export' => 'Eksportuj zespół',
+'button.import' => 'Importuj zespół',
+'button.close' => 'Zamknij',
+'button.stop' => 'Zatrzymaj',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Nazwa zespołu',
+'label.address' => 'Adres',
+'label.currency' => 'Waluta',
+'label.manager_name' => 'Nazwa managera',
+'label.manager_login' => 'Login managera',
+'label.person_name' => 'Nazwa',
+'label.thing_name' => 'Nazwa',
+'label.login' => 'Login',
+'label.password' => 'Hasło',
+'label.confirm_password' => 'Potwierdź hasło',
+'label.email' => 'E-mail',
+'label.date' => 'Data',
+'label.start_date' => 'Data początkowa',
+'label.end_date' => 'Data końcowa',
+'label.user' => 'Użytkownik',
+'label.users' => 'Użytkownicy',
+'label.client' => 'Klient',
+'label.clients' => 'Klienci',
+'label.option' => 'Opcja',
+'label.invoice' => 'Faktura',
+'label.project' => 'Projekt',
+'label.projects' => 'Projekty',
+'label.task' => 'Zadanie',
+'label.tasks' => 'Zadania',
+'label.description' => 'Opis',
+'label.start' => 'Początek',
+'label.finish' => 'Koniec',
+'label.duration' => 'Czas trwania',
+'label.note' => 'Uwagi',
+'label.item' => 'Pozycja',
+'label.cost' => 'Koszt',
+'label.week_total' => 'W tym tygodniu',
+'label.day_total' => 'Dziś',
+'label.today' => 'Dziś',
+'label.total_hours' => 'Całkowita liczba godzin',
+'label.total_cost' => 'Koszt całkowity',
+'label.view' => 'Widok',
+'label.edit' => 'Edycja',
+'label.delete' => 'Usuń',
+'label.configure' => 'Konfiguruj',
+'label.select_all' => 'Zaznacz wszystko',
+'label.select_none' => 'Odznacz wszystko',
+'label.id' => 'ID',
+'label.language' => 'Język',
+'label.decimal_mark' => 'Znak dziesiętny',
+'label.lock_interval' => 'Blokada edycji po okresie (dni)',
+'label.date_format' => 'Format daty',
+'label.time_format' => 'Format godziny',
+'label.week_start' => 'Początek tygodnia',
+'label.comment' => 'Komentarz',
+'label.status' => 'Status',
+'label.tax' => 'Podatek',
+'label.subtotal' => 'Suma netto',
+'label.total' => 'Suma',
+'label.client_name' => 'Nazwa klienta',
+'label.client_address' => 'Adres klienta',
+'label.or' => 'lub',
+'label.error' => 'Błąd',
+'label.ldap_hint' => 'Wpisz swoją <b> nazwę użytkownika Windows<b> i <b>hasło<b> w polach poniżej.',
+'label.required_fields' => '* - pola wymagane',
+'label.on_behalf' => 'w imieniu',
+'label.role_manager' => '(Manager)',
+'label.role_comanager' => '(Co-manager)',
+'label.role_admin' => '(Administrator)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Niestandardowe pola',
+'label.type' => 'Rodzaj',
+'label.type_dropdown' => 'lista rozwijana',
+'label.type_text' => 'tekst',
+'label.required' => 'Wymagane',
+'label.fav_report' => 'Ulubiony raport',
+'label.cron_schedule' => 'Harmonogram crona',
+'label.what_is_it' => 'Co to jest?',
+
+// Form titles.
+'title.login' => 'Logowanie',
+'title.teams' => 'Zespoły',
+'title.create_team' => 'Zakładanie zespołu',
+'title.edit_team' => 'Edytowanie zespołu',
+'title.delete_team' => 'Usuwanie zespołu',
+'title.reset_password' => 'Resetowanie hasła',
+'title.change_password' => 'Zmiana hasła',
+'title.time' => 'Wybrana data',
+'title.edit_time_record' => 'Edytowanie wpisu',
+'title.delete_time_record' => 'Usuwanie wpisu',
+'title.expenses' => 'Wydatki',
+'title.edit_expense' => 'Edytowanie wpisu',
+'title.delete_expense' => 'Usuwanie wpisu',
+'title.reports' => 'Raporty',
+'title.report' => 'Raport',
+'title.send_report' => 'Wysyłanie raportu',
+'title.invoice' => 'Faktura',
+'title.send_invoice' => 'Wysyłanie faktury',
+'title.charts' => 'Statystyki',
+'title.projects' => 'Projekty',
+'title.add_project' => 'dodawanie projektu',
+'title.edit_project' => 'Edytowanie projektu',
+'title.delete_project' => 'Usuwanie projektu',
+'title.tasks' => 'Zadania',
+'title.add_task' => 'Dodawanie zadania',
+'title.edit_task' => 'Edytowanie zadania',
+'title.delete_task' => 'Usuwanie zadania',
+'title.users' => 'Użytkownicy',
+'title.add_user' => 'Dodawanie użytkownika',
+'title.edit_user' => 'Edytowanie użytkownika',
+'title.delete_user' => 'Usuwanie użytkownika',
+'title.clients' => 'Klienci',
+'title.add_client' => 'Dodawanie klienta',
+'title.edit_client' => 'Edytowanie klienta',
+'title.delete_client' => 'Usuwanie klienta',
+'title.invoices' => 'Faktury',
+'title.add_invoice' => 'Dodawanie faktury',
+'title.view_invoice' => 'Podgląd faktury',
+'title.delete_invoice' => 'Usuwanie faktury',
+'title.notifications' => 'Powiadomienia',
+'title.add_notification' => 'Dodawanie powiadomienia',
+'title.edit_notification' => 'Edytowanie powiadomienia',
+'title.delete_notification' => 'Usuwanie powiadomienia',
+'title.export' => 'Eksport danych zespołu',
+'title.import' => 'Import danych zespołu',
+'title.options' => 'Opcje',
+'title.profile' => 'Profil',
+'title.cf_custom_fields' => 'Pola niestandardowe',
+'title.cf_add_custom_field' => 'Dodawanie pola niestandardowego',
+'title.cf_edit_custom_field' => 'Edytowanie pola niestandardowego',
+'title.cf_delete_custom_field' => 'Usuwanie pola niestandardowego',
+'title.cf_dropdown_options' => 'Opcje listy rozwijanej',
+'title.cf_add_dropdown_option' => 'Dodawanie opcji',
+'title.cf_edit_dropdown_option' => 'Edytowanie opcji',
+'title.cf_delete_dropdown_option' => 'Usuwanie opcji',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- wszystkie ---',
+'dropdown.no' => '--- żaden ---',
+// NOTE TO TRANSLATORS: dropdown.this_day does not necessarily means "today". It means a specific ("this") day selected on calendar. See Charts.
+'dropdown.this_day' => 'wybrany dzień',
+'dropdown.this_week' => 'ten tydzień',
+'dropdown.last_week' => 'poprzedni tydzień',
+'dropdown.this_month' => 'ten miesiąc',
+'dropdown.last_month' => 'poprzedni miesiąc',
+'dropdown.this_year' => 'ten rok',
+'dropdown.all_time' => 'od początku',
+'dropdown.projects' => 'projekty',
+'dropdown.tasks' => 'zadania',
+'dropdown.clients' => 'klienci',
+'dropdown.select' => '--- wybierz ---',
+'dropdown.select_invoice' => '--- wybierz fakturę ---',
+'dropdown.status_active' => 'aktywny',
+'dropdown.status_inactive' => 'nieaktywny',
+'dropdown.delete'=>'usuń',
+'dropdown.do_not_delete'=>'nie usuwaj',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' =>  'Nie pamiętasz hasła?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> jest prostym, łatwym w użyciu, otwartoźródłowym systemem śledzenia czasu.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Instrukcje zmiany hasła zostały wysłane na adres e-mail połączony z kontem.',
+'form.reset_password.email_subject' => 'Anuko Time Tracker - żądanie zmiany hasła',
+'form.reset_password.email_body' => "Drogi Użytkowniku,\n\nktoś, prawdopodobnie Ty, poprosił o zmianę hasła w aplikacji Anuko Time Tracker. Aby ustawić nowe hasło, proszę kliknąć na poniższy link lub go skopiować i otworzyć w oknie przeglądarki WWW.\n\n%s\n\nJeśli to nie Ty poprosiłeś o zmianę hasła, zignoruj tą wiadomość.\n\nAnuko Time Tracker jest prostym, łatwym w użyciu, otwartoźródłowym systemem do śledzenia czasu. Odwiedź https://www.anuko.com aby uzyskać więcej informacji.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Wpisz nowe hasło i kliknij Zapisz.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm or 0.0h)',
+'form.time.billable' => 'Płatne dla klienta',
+'form.time.uncompleted' => 'Nieukończone',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Ten wpis ma określony jedynie czas rozpoczęcia. To nie jest błąd.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Zapisz jako ulubiony',
+'form.reports.confirm_delete' => 'Czy na pewno chcesz usunąć ten ulubiony raport?',
+'form.reports.include_records' => 'Zawrzyj wpisy',
+'form.reports.include_billable' => 'płatne',
+'form.reports.include_not_billable' => 'bezpłatne',
+'form.reports.include_invoiced' => 'fakturowane',
+'form.reports.include_not_invoiced' => 'nie fakturowane',
+'form.reports.select_period' => 'Wybierz okres',
+'form.reports.set_period' => 'lub ustaw daty',
+'form.reports.show_fields' => 'Pokaż pola',
+'form.reports.group_by' => 'Grupowanie wg',
+'form.reports.group_by_no' => '--- bez grupowania ---',
+'form.reports.group_by_date' => 'daty',
+'form.reports.group_by_user' => 'użytkowników',
+'form.reports.group_by_client' => 'klientów',
+'form.reports.group_by_project' => 'projektów',
+'form.reports.group_by_task' => 'zadań',
+'form.reports.totals_only' => 'Tylko sumy',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Eksport',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Numer faktury',
+'form.invoice.person' => 'Osoba',
+'form.invoice.invoice_to_delete' => 'Faktura do usunięcia',
+'form.invoice.invoice_entries' => 'Wpisy dot. faktury',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.phpp
+'form.charts.interval' => 'Okres',
+'form.charts.chart' => 'Wykres',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Aktywne projekty',
+'form.projects.inactive_projects' => 'Nieaktywne projekty',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Aktywne zadania',
+'form.tasks.inactive_tasks' => 'Nieaktywne zadania',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Aktywni użytkownicy',
+'form.users.inactive_users' => 'Nieaktywni użytkownicy',
+'form.users.role' => 'Rola',
+'form.users.manager' => 'Manager',
+'form.users.comanager' => 'Co-manager',
+'form.users.rate' => 'Stawka',
+'form.users.default_rate' => 'Domyślna stawka godzinowa',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php.
+'form.client.client_to_delete' => 'Klient do usunięcia',
+'form.client.client_entries' => 'Wpisy dot. klienta',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.phpp
+'form.clients.active_clients' => 'Aktywni klienci',
+'form.clients.inactive_clients' => 'Nieaktywni klienci',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Możesz wyeksportować wszystkie dane zespołu do pliku xml. Przydatne przy migracji danych na własny serwer.',
+'form.export.compression' => 'Kompresja',
+'form.export.compression_none' => 'brak',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Import danych zespołu z pliku xml.',
+'form.import.file' => 'Wybierz plik',
+'form.import.success' => 'Import zakończony powodzeniem.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' =>  'Załóż nowy zespół najpierw tworząc konto managera.<br>Możesz także zaimportować plik xml z danymi zespołu z innego serwera Anuko Time Tracker (nazwy loginów nie mogą się powtarzać).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 godzin',
+'form.profile.24_hours' => '24 godziny',
+'form.profile.tracking_mode' => 'Tryb śledzenia',
+'form.profile.mode_time' => 'czas',
+'form.profile.mode_projects' => 'projekty',
+'form.profile.mode_projects_and_tasks' => 'projekty i zadania',
+'form.profile.record_type' => 'Rejestrowanie czasu',
+'form.profile.type_all' => 'wszystko',
+'form.profile.type_start_finish' => 'początek i koniec',
+'form.profile.type_duration' => 'czas trwania',
+'form.profile.plugins' => 'Dodatkowe moduły',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'Od',
+'form.mail.to' => 'Do',
+'form.mail.cc' => 'DW',
+'form.mail.subject' => 'Temat',
+'form.mail.report_subject' => 'Raport Time Tracker',
+'form.mail.footer' => 'Anuko Time Tracker jest prostym, łatwym w użyciu, otwartoźródłowym<br>systemem śledzenia czasu. Odwiedź <a href="https://www.anuko.com">www.anuko.com</a>, aby uzyskać więcej informacji.',
+'form.mail.report_sent' => 'Wysłano raport',
+'form.mail.invoice_sent' => 'Wysłano fakturę',
+);
+?>
diff --git a/WEB-INF/resources/pt.lang.php b/WEB-INF/resources/pt.lang.php
new file mode 100644 (file)
index 0000000..be9fc01
--- /dev/null
@@ -0,0 +1,427 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Português';
+$i18n_months = array('janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'augosto', 'setembro', 'outubro', 'novembro', 'dezembro');
+$i18n_weekdays = array('domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado');
+// Note to translators: $i18n_weekdays_short needs to be translated. These are shortened days of week.
+// $i18n_weekdays_short = array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
+// format mm/dd
+$i18n_holidays = array('01/01', '02/24', '04/10', '04/12', '04/25', '05/01', '06/10', '06/11', '08/15', '10/05', '01/11', '12/01', '12/08', '12/25');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'login',
+'menu.logout' => 'logout',
+'menu.feedback' => 'feedback',
+'menu.help' => 'ajuda',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'criar nova conta de gerente',
+'menu.edit_profile' => 'editar perfil',
+'menu.my_time' => 'meu tempo',
+'menu.reports' => 'relatórios',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projetos',
+'menu.activities' => 'atividades',
+'menu.people' => 'pessoas',
+// Note to translators: the strings below are missing and must be added and translated 
+// 'menu.teams' => 'teams',
+// 'menu.export' => 'export',
+// 'menu.clients' => 'clients',
+'menu.options' => 'opções',
+// 'menu.admin' => 'admin',
+
+// error strings
+// Note to translators: the strings below must be translated
+// 'error.db' => 'database error',
+// 'error.field' => 'incorrect "{0}" data',
+// 'error.empty' => 'field "{0}" is empty',
+// 'error.not_equal' => 'field "{0}" is not equaled to field "{1}"',
+// 'error.interval' => 'incorrect interval',
+// 'error.project' => 'select project',
+// 'error.activity' => 'select activity',
+// 'error.auth' => 'incorrect login or password',
+// 'error.user_exists' => 'user with this login already exists',
+// 'error.project_exists' => 'project with this name already exists',
+// 'error.activity_exists' => 'activity with this name already exists',
+// 'error.client_exists' => 'client with this name already exists',
+// 'error.no_login' => 'no user with this e-mail',
+// 'error.upload' => 'file upload error',
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'login',
+'button.now' => 'hoje',
+// 'button.set' => 'set',
+'button.save' => 'salvar',
+'button.delete' => 'apagar',
+'button.cancel' => 'cancelar',
+'button.submit' => 'submeter',
+// Note to translators: 'button.add_user' => 'add user', // the string must be translated
+'button.add_project' => 'adicionar projeto',
+'button.add_activity' => 'adicionar atividade',
+// 'button.add_client' => 'add client', // TODO: translate this.
+'button.add' => 'adicionar',
+// Note to translators: strings below need to be translated.
+// 'button.generate' => 'generate',
+// 'button.reset_password' => 'reset password',
+'button.send' => 'enviar',
+'button.send_by_email' => 'enviar por e-mail',
+// Note to translators: the strings below are missing and must be added and translated
+// 'button.save_as_new' => 'save as new',
+// 'button.create_team' => 'create team',
+// 'button.export' => 'export team',
+// 'button.import' => 'import team',
+// 'button.apply' => 'apply',
+
+// labels for controls on various forms
+// TODO: translate label.team_name and strings below.
+// 'label.team_name' => 'team name',
+// 'label.currency' => 'currency',
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'senha',
+'label.confirm_password' => 'confirme a senha',
+// 'label.email' => 'email',
+
+// "form.filter.project" => 'project',
+// "form.filter.filter" => 'favorite report',
+// "form.filter.filter_new" => 'save as favorite',
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'login como usuário de debug',
+// Note to translators: "form.login.login" => 'e-mail', // email has been changed to login
+
+// password reminder form attributes
+"form.fpass.title" => 'enviar senha',
+// Note to translators: "form.fpass.login" => 'e-mail', // email has been changed to login
+"form.fpass.send_pass_str" => 'senha foi enviada',
+"form.fpass.send_pass_subj" => 'Sua senha do Anuko Time Tracker',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.fpass.send_pass_body" => "Dear User,\n\nSomeone, possibly you, requested your Anuko Time Tracker password reset. Please visit this link if you want to reset your password.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+// "form.fpass.reset_comment" => "to reset your password please type it in and click on save",
+
+// administrator form
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.admin.title" => 'administrator',
+// "form.admin.duty_text" => 'create a new team by creating a new team manager account.<br>you can also import team data from an xml file from another Anuko Time Tracker server (no login collisions are allowed).',
+
+// "form.admin.change_pass" => 'change password of administrator account',
+// "form.admin.profile.title" => 'teams',
+// "form.admin.profile.noprofiles" => 'your database is empty. login as admin and create a new team.',
+// "form.admin.profile.comment" => 'delete team',
+// "form.admin.profile.th.id" => 'id',
+// "form.admin.profile.th.name" => 'name',
+// "form.admin.profile.th.edit" => 'edit',
+// "form.admin.profile.th.del" => 'delete',
+// "form.admin.profile.th.active" => 'active',
+// "form.admin.lock.period" => 'lock interval in days',
+"form.admin.options" => 'opções',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'adicionar período',
+// Note to translators: the strings below must be translated
+// "form.mytime.edit_title" => 'editing time record',
+// "form.mytime.del_str" => 'deleting time record',
+// "form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'data',
+"form.mytime.project" => 'projeto',
+"form.mytime.activity" => 'atividade',
+"form.mytime.start" => 'início',
+"form.mytime.finish" => 'fim',
+"form.mytime.duration" => 'duração',
+"form.mytime.note" => 'anotação',
+// Note to translators: the string below must be translated
+// "form.mytime.behalf" => 'daily work for',
+"form.mytime.daily" => 'trabalho diário',
+"form.mytime.total" => 'horas totais: ',
+"form.mytime.th.project" => 'projeto',
+"form.mytime.th.activity" => 'actividade',
+"form.mytime.th.start" => 'início',
+"form.mytime.th.finish" => 'finish',
+"form.mytime.th.duration" => 'duração',
+"form.mytime.th.note" => 'fim',
+"form.mytime.th.edit" => 'editar',
+"form.mytime.th.delete" => 'apagar',
+"form.mytime.del_yes" => 'o período registrado foi apagado com sucesso',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.mytime.no_finished_rec" => 'this record was saved with only start time. it is not an error. logout if you need to.',
+// "form.mytime.billable" => 'billable',
+// "form.mytime.warn_tozero_rec" => 'this time record must be deleted because this time period is locked',
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'criar nova conta de gerência',
+"form.profile.edit_title" => 'editando perfil',
+"form.profile.name" => 'nome',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.profile.login" => 'login',
+
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'pessoas',
+"form.people.createu_str" => 'adicionar novo usuário',
+"form.people.edit_str" => 'editando usuário',
+"form.people.del_str" => 'apagando usuário',
+"form.people.th.name" => 'nome',
+// Note to translators: "form.people.th.login" => 'e-mail', // email has been changed to login
+"form.people.th.role" => 'regra',
+"form.people.th.edit" => 'editar',
+"form.people.th.del" => 'apagar',
+"form.people.th.status" => 'status',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.people.th.project" => 'project',
+// "form.people.th.rate" => 'rate',
+"form.people.manager" => 'gerente',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.people.comanager" => 'comanager',
+"form.people.empl" => 'usuário',
+"form.people.name" => 'nome',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.people.login" => 'login',
+
+"form.people.rate" => 'hourly rate',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.people.comanager" => 'co-manager',
+// "form.people.projects" => 'projects',
+
+// projects form attributes
+"form.project.proj_title" => 'projetos',
+"form.project.edit_str" => 'editando projeto',
+"form.project.add_str" => 'adicionando novo projeto',
+"form.project.del_str" => 'apagando projeto',
+"form.project.th.name" => 'nome',
+"form.project.th.edit" => 'editar',
+"form.project.th.del" => 'apagar',
+"form.project.name" => 'nome',
+
+// activities form attributes
+"form.activity.act_title" => 'atividades',
+"form.activity.add_title" => 'adicionando nova atividade',
+"form.activity.edit_str" => 'editando atividade',
+// Note to translators: the string below must be translated
+// "form.activity.del_str" => 'deleting activity',
+"form.activity.name" => 'nome',
+"form.activity.project" => 'project',
+"form.activity.th.name" => 'nome',
+"form.activity.th.project" => 'project',
+"form.activity.th.edit" => 'editar',
+"form.activity.th.del" => 'apagar',
+
+// report attributes
+"form.report.title" => 'relatórios',
+"form.report.from" => 'data inicial',
+"form.report.to" => 'data final',
+// Note to translators: the strings below must be translated
+// "form.report.groupby_user" => 'user',
+// "form.report.groupby_project" => 'project',
+// "form.report.groupby_activity" => 'activity',
+"form.report.duration" => 'duração',
+"form.report.start" => 'início',
+"form.report.activity" => 'atividade',
+// Note to translators: the string below must be translated
+// "form.report.show_idle" => 'show idle',
+"form.report.finish" => 'fim',
+"form.report.note" => 'anotação',
+"form.report.project" => 'projeto',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.report.totals_only" => 'totals only',
+"form.report.total" => 'horas totais',
+"form.report.th.empllist" => 'usuário',
+// Note to translators: the strings below must be translated
+// "form.report.th.date" => 'data',
+// "form.report.th.project" => 'project',
+// "form.report.th.activity" => 'activity',
+// "form.report.th.start" => 'start',
+// "form.report.th.finish" => 'finish',
+// "form.report.th.duration" => 'duration',
+// "form.report.th.note" => 'note',
+
+// mail form attributes
+"form.mail.from" => 'de',
+"form.mail.to" => 'para',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'assunto',
+"form.mail.comment" => 'comentário',
+"form.mail.above" => 'enviar este relatório por e-mail',
+// Note to translators: the strings below must be translated
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+// "form.mail.sending_str" => '<b>the message has been sent</b>',
+
+// invoice attributes
+// Note to translators: the strings below must be translated
+// "form.invoice.title" => 'invoice',
+// "form.invoice.caption" => 'invoice',
+// "form.invoice.above" => 'additional information for invoice',
+// "form.invoice.select_cust" => 'select client',
+// "form.invoice.fillform" => 'fill the fields',
+// "form.invoice.date" => 'invoice date',
+// "form.invoice.number" => 'invoice number',
+// "form.invoice.tax" => 'tax',
+// "form.invoice.daily_subtotals" => 'daily subtotals'
+// "form.invoice.yourcoo" => 'your name<br> and address',
+// "form.invoice.custcoo" => 'client name<br> and address',
+// "form.invoice.comment" => 'comment ',
+// "form.invoice.th.username" => 'person',
+// "form.invoice.th.time" => 'hours',
+// "form.invoice.th.rate" => 'rate',
+// "form.invoice.th.summ" => 'amount',
+// "form.invoice.subtotal" => 'subtotal',
+// "form.invoice.customer" =>'customer',
+// Note to translators: the strings below must be translated
+// "form.invoice.mailinv_above" => 'send this invoice by e-mail',
+// "form.invoice.sending_str" => '<b>invoice has been sent</b>',
+
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.migration.zip" => 'compression',
+// "form.migration.file" => 'select file',
+// "form.migration.import.title" => 'import data',
+// "form.migration.import.success" => 'import completed successfully',
+// "form.migration.import.text" => 'import team data from an xml file',
+// "form.migration.export.title" => 'export data',
+// "form.migration.export.success" => 'export completed successfully',
+// "form.migration.export.text" => 'you can export all team data into an xml file. this could be useful if you are migrating data to your own server.',
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+// "form.client.title" => 'clients',
+// "form.client.add_title" => 'add client',
+// "form.client.edit_title" => 'edit client',
+// "form.client.del_title" => 'delete client',
+// "form.client.th.name" => 'name',
+// "form.client.th.edit" => 'edit',
+// "form.client.th.del" => 'delete',
+// "form.client.name" => 'name',
+// "form.client.tax" => 'tax',
+// "form.client.daily_subtotals" => 'daily subtotals',
+// "form.client.yourcoo" => 'your name<br> and address in invoice',
+// "form.client.custcoo" => 'address',
+// "form.client.comment" => 'comment ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'esqueceu a senha?',
+// Note to translators: the strings below must be translated 
+// "forward.edit" => 'edit',
+// "forward.delete" => 'delete',
+// Note to translators: the string below must be translated 
+// "forward.tocsvfile" => 'export data to .csv file',
+// Note to translators: the strings below are missing and must be added and translated 
+// "forward.toxmlfile" => 'export data to .xml file',
+// "forward.geninvoice" => 'generate invoice',
+// "forward.change" => 'configure clients',
+
+// strings inside contols on forms
+"controls.select.project" => '--- selecione projeto ---',
+"controls.select.activity" => '--- selecione atividade ---',
+// Note to translators: the strings below are missing and must be added and translated 
+// "controls.select.client" => '--- select client ---',
+// "controls.project_bind" => '--- all ---',
+// "controls.all" => '--- all ---',
+// "controls.notbind" => '--- no ---',
+"controls.per_tm" => 'este mês',
+"controls.per_lm" => 'último mês',
+"controls.per_tw" => 'esta semana',
+"controls.per_lw" => 'última semana',
+// Note to translators: the strings below are missing and must be added and translated 
+// "controls.per_td" => 'this day',
+// "controls.per_at" => 'all time',
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- selecione o período de tempo ---',
+// Note to translators: the strings below must be translated 
+// "controls.sel_groupby" => '--- no grouping ---',
+// "controls.inc_billable" => 'billable',
+// "controls.inc_nbillable" => 'not billable',
+// "controls.default" => '--- default ---',
+
+// labels
+// Note to translators: the strings below are missing and must be added and translated 
+// "label.chart.title1" => 'activities for user',
+// "label.chart.title2" => 'projects for user',
+// "label.chart.period" => 'chart for period',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>on behalf of %s</b>',
+"label.pminfo" => ' (gerente)',
+// Note to translators: the strings below are missing and must be added and translated 
+// "label.pcminfo" => ' (co-manager)',
+// "label.painfo" => ' (administrator)',
+"label.time_noentry" => 'sem registro',
+"label.today" => 'today',
+"label.req_fields" => '* campos obrigatórios',
+// Note to translators: the strings below must be translated 
+// "label.sel_project" => 'select project',
+// "label.sel_activity" => 'select activity',
+"label.sel_tp" => 'selecione o período de tempo',
+"label.set_tp" => 'ou selecionar datas',
+"label.fields" => 'exibir campos',
+// Note to translators: the strings below must be translated
+// "label.group_title" => 'group by',
+// "label.include_title" => 'include records',
+// "label.inv_str" => 'invoice',
+// "label.set_empl" => 'select users'
+//" label.sel_all" => 'select all',
+// "label.sel_none" => 'deselect all',
+// "label.or" => 'or',
+// "label.disable" => 'disable',
+// "label.enable" => 'enable',
+// "label.filter" => 'filter',
+// "label.timeweek" => 'weekly total',
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/ro.lang.php b/WEB-INF/resources/ro.lang.php
new file mode 100644 (file)
index 0000000..b1671cf
--- /dev/null
@@ -0,0 +1,417 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Română';
+$i18n_months = array('ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie');
+$i18n_weekdays = array('duminica', 'luni', 'marti', 'miercuri', 'joi', 'vineri', 'sambata');
+$i18n_weekdays_short = array('du', 'lu', 'ma', 'mi', 'jo', 'vi', 'sa');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/02', '04/19', '04/20', '05/01', '06/07', '06/08', '08/15', '12/01', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'autentificare',
+'menu.logout' => 'iesire',
+'menu.feedback' => 'feedback',
+'menu.help' => 'ajutor',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'creaza cont manager',
+'menu.edit_profile' => 'editeaza profil',
+'menu.my_time' => 'timpul meu',
+'menu.reports' => 'rapoarte',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'proiecte',
+'menu.activities' => 'activitati',
+'menu.people' => 'persoane',
+'menu.teams' => 'echipe',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'clienti',
+// Note to translators: menu.options and menu.admin need to be translated.
+// 'menu.options' => 'options',
+// 'menu.admin' => 'admin',
+
+// error strings
+'error.db' => 'eroare baza de date',
+// Note to translators: the string below must be translated 
+// 'error.field' => 'incorrect "{0}" data',
+'error.empty' => 'campul "{0}" este gol',
+'error.not_equal' => 'campul "{0}" nu este egal cu campul "{1}"',
+'error.interval' => 'interval incorect',
+'error.project' => 'selecteaza proiect',
+'error.activity' => 'selecteaza activitate',
+'error.auth' => 'nume de utilizator sau parola incorecta',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'proiectul cu acest nume exista deja',
+'error.activity_exists' => 'activitatea cu acest nume exista deja',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'nu exista utilizator cu acest email',
+'error.upload' => 'eroare la upload-ul fisierului',
+// Note to translators: the strings below are missing and must be added and translated 
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'autentifica',
+'button.now' => 'acum',
+// 'button.set' => 'set',
+'button.save' => 'salveaza',
+'button.delete' => 'sterge',
+'button.cancel' => 'renunta',
+'button.submit' => 'trimite',
+'button.add_user' => 'adauga utilizator',
+'button.add_project' => 'adauga proiect',
+'button.add_activity' => 'adauga activitate',
+'button.add_client' => 'adauga client',
+'button.add' => 'adauga',
+'button.generate' => 'genereaza',
+// Note to translators: button.reset_password needs an improved translation.
+// 'button.reset_password' => 'reset password',
+'button.send' => 'trimite',
+'button.send_by_email' => 'trimite pe e-mail',
+// TODO: button.create_team needs an improved translation.
+'button.create_team' => 'adauga echipa noua',
+'button.export' => 'exporta echipa',
+'button.import' => 'importa echipa',
+'button.apply' => 'aplica',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'moneda',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'parola',
+'label.confirm_password' => 'confirma parola',
+// 'label.email' => 'email',
+'label.total' => 'total',
+
+"form.filter.project" => 'proiect',
+"form.filter.filter" => 'rapoarte favorite',
+"form.filter.filter_new" => 'salveaza ca favorit',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'autentificare',
+"form.login.login" => 'autentifica', 
+
+// password reminder form attributes
+"form.fpass.title" => 'reseteaza parola',
+"form.fpass.login" => 'autentifica', 
+"form.fpass.send_pass_str" => 'cererea de resetare a parolei a fost trimisa',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker - cerere de resetare a parolei',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "Draga Utilizator,\n\nCineva, posibil tu, a cerut resetarea parolei pentru contul Anuko Time Tracker. Te rog, viziteaza acesta legatura daca doresti sa iti resetezi parola.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "pentru resetarea parolei introdu-o si da click pe salveaza",
+
+// administrator form
+"form.admin.title" => 'administrator',
+"form.admin.duty_text" => 'adauga o noua echipa prin adaugarea unui nou cont de tip manager.<br>deasemeni poti importa datele despre echipa dintr-un fisier xml generat de un alt server Anuko Time Tracker  (nu sunt permise duplicate pentru emailuri).',
+
+"form.admin.change_pass" => 'schimba parola contului de administrator',
+"form.admin.profile.title" => 'echipe',
+"form.admin.profile.noprofiles" => 'baza de date este goala. intra ca admin si adauga o noua echipa.',
+"form.admin.profile.comment" => 'sterge echipa',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'nunme',
+"form.admin.profile.th.edit" => 'editeaza',
+"form.admin.profile.th.del" => 'sterge',
+"form.admin.profile.th.active" => 'activ',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.admin.lock.period" => 'lock interval in days',
+// "form.admin.options" => 'options',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'timpul meu',
+"form.mytime.edit_title" => 'editarea inregistrarii timpului',
+"form.mytime.del_str" => 'stergerea inregistrarii timpului',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'data',
+"form.mytime.project" => 'proiect',
+"form.mytime.activity" => 'activitate',
+"form.mytime.start" => 'inceput',
+"form.mytime.finish" => 'sfarsit',
+"form.mytime.duration" => 'durata',
+"form.mytime.note" => 'nota',
+"form.mytime.behalf" => 'activitatea zilnica pentru',
+"form.mytime.daily" => 'activitatea zilnica',
+"form.mytime.total" => 'ore total: ',
+"form.mytime.th.project" => 'proiect',
+"form.mytime.th.activity" => 'activitate',
+"form.mytime.th.start" => 'inceput',
+"form.mytime.th.finish" => 'sfarsit',
+"form.mytime.th.duration" => 'durata',
+"form.mytime.th.note" => 'nota',
+"form.mytime.th.edit" => 'editeaza',
+"form.mytime.th.delete" => 'sterge',
+"form.mytime.del_yes" => 'inregistrarea timului a fost stearsa cu succes',
+"form.mytime.no_finished_rec" => 'aceasta inregistrare a fost salvata numei cu timpul de inceput. nu este o eroare. poti parasi aplicatia daca este nevoie.',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.mytime.billable" => 'billable',
+// "form.mytime.warn_tozero_rec" => 'this time record must be deleted because this time period is locked',
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'creazaun nou cont de tip manager',
+"form.profile.edit_title" => 'editeaza profilul',
+"form.profile.name" => 'nume',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.profile.login" => 'login',
+
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'persoane',
+"form.people.createu_str" => 'adaugare untilizator nou',
+"form.people.edit_str" => 'editare utilizator',
+"form.people.del_str" => 'stergee utilizator',
+"form.people.th.name" => 'nume',
+// Note to translators: the string below is missing and must be added and translated 
+// "form.people.th.login" => 'login',
+"form.people.th.role" => 'functie',
+"form.people.th.edit" => 'editeaza',
+"form.people.th.del" => 'sterge',
+"form.people.th.status" => 'stare',
+"form.people.th.project" => 'proiect',
+"form.people.th.rate" => 'rata',
+"form.people.manager" => 'manager',
+"form.people.comanager" => 'comanager',
+"form.people.empl" => 'utilizator',
+"form.people.name" => 'nume',
+// Note to translators: "form.people.login" => 'e-mail', // email has been changed to login
+
+"form.people.rate" => 'pret pe ora implicit',
+"form.people.comanager" => 'co-manager',
+"form.people.projects" => 'proiecte',
+
+// projects form attributes
+"form.project.proj_title" => 'proiecte',
+"form.project.edit_str" => 'editare proiect',
+"form.project.add_str" => 'adauagre proiect nou',
+"form.project.del_str" => 'stergere proiect',
+"form.project.th.name" => 'nume',
+"form.project.th.edit" => 'editeaza',
+"form.project.th.del" => 'sterge',
+"form.project.name" => 'nume',
+
+// activities form attributes
+"form.activity.act_title" => 'activitati',
+"form.activity.add_title" => 'adaugare activitate noua',
+"form.activity.edit_str" => 'editare activitate',
+"form.activity.del_str" => 'stergere activitate',
+"form.activity.name" => 'nume',
+"form.activity.project" => 'proiect',
+"form.activity.th.name" => 'nume',
+"form.activity.th.project" => 'proiect',
+"form.activity.th.edit" => 'editare',
+"form.activity.th.del" => 'stergere',
+
+// report attributes
+"form.report.title" => 'rapoarte',
+"form.report.from" => 'data inceput',
+"form.report.to" => 'data sfarsit',
+"form.report.groupby_user" => 'utilizator',
+"form.report.groupby_project" => 'proiect',
+"form.report.groupby_activity" => 'activitate',
+"form.report.duration" => 'durata',
+"form.report.start" => 'inceput',
+"form.report.activity" => 'activitate',
+"form.report.show_idle" => 'arata liber',
+"form.report.finish" => 'sfarsit',
+"form.report.note" => 'nota',
+"form.report.project" => 'proiect',
+"form.report.totals_only" => 'numai totaluri',
+"form.report.total" => 'ore total',
+"form.report.th.empllist" => 'utilizator',
+"form.report.th.date" => 'data',
+"form.report.th.project" => 'proiect',
+"form.report.th.activity" => 'activitate',
+"form.report.th.start" => 'inceput',
+"form.report.th.finish" => 'sfarsit',
+"form.report.th.duration" => 'durata',
+"form.report.th.note" => 'nota',
+
+// mail form attributes
+"form.mail.from" => 'de la',
+"form.mail.to" => 'catre',
+"form.mail.cc" => 'copie',
+"form.mail.subject" => 'subiect',
+"form.mail.comment" => 'comentariu',
+"form.mail.above" => 'trimite acest raport pe e-mail',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>mesaj trimis</b>',
+
+// invoice attributes
+"form.invoice.title" => 'factura',
+"form.invoice.caption" => 'factura',
+"form.invoice.above" => 'informatii aditionale pentru factura',
+"form.invoice.select_cust" => 'alege client',
+"form.invoice.fillform" => 'comleteaza campurile',
+"form.invoice.date" => 'data',
+"form.invoice.number" => 'numar factura',
+"form.invoice.tax" => 'taxa',
+"form.invoice.daily_subtotals" => 'subtotaluri zilnice',
+"form.invoice.yourcoo" => 'numele tau<br> si adresa',
+"form.invoice.custcoo" => 'numele clientului<br> si adresa',
+"form.invoice.comment" => 'comentariu ',
+"form.invoice.th.username" => 'persoana',
+"form.invoice.th.time" => 'ore',
+"form.invoice.th.rate" => 'rata',
+"form.invoice.th.summ" => 'valoare',
+"form.invoice.subtotal" => 'subtotal',
+"form.invoice.customer" => 'client',
+"form.invoice.mailinv_above" => 'trimite aceasta factura pe email',
+"form.invoice.sending_str" => '<b>factura trimisa</b>',
+
+"form.migration.zip" => 'compresie',
+"form.migration.file" => 'alege fisier',
+"form.migration.import.title" => 'importa date',
+"form.migration.import.success" => 'importul s-a incheiat cu succes',
+"form.migration.import.text" => 'importa date echipa dintr-un fisier xml',
+"form.migration.export.title" => 'exporta date',
+"form.migration.export.success" => 'exportul s-a inchieat cu succes',
+"form.migration.export.text" => 'poti exporta toate datele despre echipa intr-un fisier xml. acesta poate fi folositor daca transferi datele pe alt server',
+// Note to translators: the strings below are missing and must be added and translated 
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'clienti',
+"form.client.add_title" => 'adauga client',
+"form.client.edit_title" => 'editeaza client',
+"form.client.del_title" => 'sterge client',
+"form.client.th.name" => 'nume',
+"form.client.th.edit" => 'editeaza',
+"form.client.th.del" => 'sterge',
+"form.client.name" => 'nume',
+"form.client.tax" => 'taxa',
+"form.client.daily_subtotals" => 'subtotaluri zilnice',
+"form.client.yourcoo" => 'numele tau<br> si adresa din factura',
+"form.client.custcoo" => 'adresa',
+"form.client.comment" => 'comentariu ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'parola pierduta?',
+"forward.edit" => 'editeaza',
+"forward.delete" => 'sterge',
+"forward.tocsvfile" => 'exporta date in fisier .csv',
+// Note to translators: the string below is missing and must be added and translated 
+// "forward.toxmlfile" => 'export data to .xml file',
+"forward.geninvoice" => 'genereaza factura',
+"forward.change" => 'configureaza clienti',
+
+// strings inside contols on forms
+"controls.select.project" => '--- alege proiect    ---',
+"controls.select.activity" => '--- alege activitate ---',
+"controls.select.client" => '--- alege client     ---',
+"controls.project_bind" => '--- toate ---',
+"controls.all" => '--- toate ---',
+"controls.notbind" => '--- nu ---',
+"controls.per_tm" => 'luna curenta',
+"controls.per_lm" => 'luna trecuta',
+"controls.per_tw" => 'saptamana curenta',
+"controls.per_lw" => 'saptamana trecuta',
+// Note to translators: the strings below must be translated
+// "controls.per_td" => 'this day',
+// "controls.per_at" => 'all time',
+// "controls.per_ty" => 'this year'
+"controls.sel_period" => '--- alege perioada ---',
+"controls.sel_groupby" => '--- fara grupare   ---',
+// Note to translators: the strings below must be translated
+// "controls.inc_billable" => 'billable',
+// "controls.inc_nbillable" => 'not billable',
+// "controls.default" => '--- default ---',
+
+// labels
+// Note to translators: the strings below are missing and must be added and translated 
+// "label.chart.title1" => 'activities for user',
+// "label.chart.title2" => 'projects for user',
+// "label.chart.period" => 'chart for period',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>in numele %s</b>',
+"label.pminfo" => ' (manager)',
+"label.pcminfo" => ' (co-manager)',
+"label.painfo" => ' (administrator)',
+"label.time_noentry" => 'nu exista inregistrari',
+"label.today" => 'astazi',
+"label.req_fields" => '* date obligatorii',
+"label.sel_project" => 'alege proiect',
+"label.sel_activity" => 'alege activitate',
+"label.sel_tp" => 'alege perioada',
+"label.set_tp" => 'sau introdu intervalul de date',
+"label.fields" => 'arata campuri',
+"label.group_title" => 'grupat dupa',
+// Note to translators: the string below is missing and must be added and translated 
+// "label.include_title" => 'include records',
+"label.inv_str" => 'factura',
+"label.set_empl" => 'alege utilizatori',
+"label.sel_all" => 'selecteaza   tot',
+"label.sel_none" => 'deselecteaza tot',
+"label.or" => 'sau',
+"label.disable" => 'inactiv',
+"label.enable" => 'activ',
+"label.filter" => 'filtru',
+// Note to translators: the strings below are missing and must be added and translated 
+// "label.timeweek" => 'weekly total',
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/ru.lang.php b/WEB-INF/resources/ru.lang.php
new file mode 100644 (file)
index 0000000..af55936
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Русский';
+$i18n_months = array('Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь');
+$i18n_weekdays = array('Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота');
+$i18n_weekdays_short = array('Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/07', '02/23', '03/08', '05/01', '05/09', '06/12', '11/04');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Войти в систему',
+'menu.logout' => 'Выйти',
+'menu.forum' => 'Форум',
+'menu.help' => 'Справка',
+'menu.create_team' => 'Создать команду',
+'menu.profile' => 'Профиль',
+'menu.time' => 'Время',
+'menu.expenses' => 'Расходы',
+'menu.reports' => 'Отчёты',
+'menu.charts' => 'Диаграммы',
+'menu.projects' => 'Проекты',
+'menu.tasks' => 'Задачи',
+'menu.users' => 'Люди',
+'menu.teams' => 'Команды',
+'menu.export' => 'Экспорт',
+'menu.clients' => 'Клиенты',
+'menu.options' => 'Опции',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker доступен на мобильных телефонах.',
+'footer.credits' => 'Авторы',
+'footer.license' => 'Лицензия',
+
+// Error messages.
+'error.access_denied' => 'Доступ запрещён.',
+'error.sys' => 'Системная ошибка.',
+'error.db' => 'Ошибка базы данных.',
+'error.field' => 'Некорректные данные в поле "{0}".',
+'error.empty' => 'Пустое поле "{0}".',
+'error.not_equal' => 'Значение "{0}" не соответствует "{1}".',
+'error.interval' => 'Поле "{0}" должно быть больше "{1}".',
+'error.project' => 'Выберите проект.',
+'error.task' => 'Выберите задачу.',
+'error.client' => 'Выберите клиента.',
+'error.report' => 'Выберите отчёт.',
+'error.auth' => 'Неправильно введен логин или пароль.',
+'error.user_exists' => 'Пользователь с таким логином уже существует.',
+'error.project_exists' => 'Проект с таким именем уже есть.',
+'error.task_exists' => 'Задача с таким названием уже есть.',
+'error.client_exists' => 'Клиент с таким именем уже есть.',
+'error.invoice_exists' => 'Счёт с таким номером уже есть.',
+'error.no_invoiceable_items' => 'Нет записей для включения в счёт.',
+'error.no_login' => 'Нет пользователя с таким логином.',
+'error.no_teams' => 'Ваша база данных пуста. Войдите в систему как администратор и создайте новую команду.',
+'error.upload' => 'Ошибка загрузки файла.',
+'error.period_locked' => 'Невозможно завершить операцию. Записи, старее чем определённое количество дней, не могут быть созданы или изменены. Менеджер команды определяет интервал блокировки величиной "Интервал блокировки в днях" на странице "Профиль". Установите её в 0, чтобы удалить блокировку.<br><br>Неоконченные записи (с 0 или пустой длительностью) могут быть удалены.',
+'error.mail_send' => 'Ошибка отправки почты.',
+'error.no_email' => 'Для данного логина не предоставлен e-mail.',
+'error.uncompleted_exists' => 'Неоконченная запись уже существует. Закройте или удалите её.',
+'error.goto_uncompleted' => 'Посмотреть неоконченную запись.',
+'error.overlap' => 'Интервал времени перекрывается с существующими записями.',
+'error.future_date' => 'Дата в будущем.',
+
+// Labels for buttons.
+'button.login' => 'Войти',
+'button.now' => 'Сейчас',
+'button.save' => 'Сохранить',
+'button.copy' => 'Скопировать',
+'button.cancel' => 'Отменить',
+'button.submit' => 'Подтвердить',
+'button.add_user' => 'Добавить пользователя',
+'button.add_project' => 'Добавить проект',
+'button.add_task' => 'Добавить задачу',
+'button.add_client' => 'Добавить клиента',
+'button.add_invoice' => 'Добавить счёт',
+'button.add_option' => 'Добавить опцию',
+'button.add' => 'Добавить',
+'button.generate' => 'Сгенерировать',
+'button.reset_password' => 'Сбросить пароль',
+'button.send' => 'Отправить',
+'button.send_by_email' => 'Отправить по e-mail',
+'button.create_team' => 'Создать команду',
+'button.export' => 'Экспортировать команду',
+'button.import' => 'Импортировать команду',
+'button.close' => 'Закрыть',
+'button.stop' => 'Завершить',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Название команды',
+'label.address' => 'Адрес',
+'label.currency' => 'Валюта',
+'label.manager_name' => 'Имя менеджера',
+'label.manager_login' => 'Логин менеджера',
+'label.person_name' => 'Имя',
+'label.thing_name' => 'Название',
+'label.login' => 'Логин',
+'label.password' => 'Пароль',
+'label.confirm_password' => 'Подтверждение пароля',
+'label.email' => 'Адрес e-mail',
+'label.date' => 'Дата',
+'label.start_date' => 'Начальная дата',
+'label.end_date' => 'Конечная дата',
+'label.user' => 'Пользователь',
+'label.users' => 'Сотрудники',
+'label.client' => 'Клиент',
+'label.clients' => 'Клиенты',
+'label.option' => 'Опция',
+'label.invoice' => 'Счёт',
+'label.project' => 'Проект',
+'label.projects' => 'Проекты',
+'label.task' => 'Задача',
+'label.tasks' => 'Задачи',
+'label.description' => 'Описание',
+'label.start' => 'Начало',
+'label.finish' => 'Окончание',
+'label.duration' => 'Длительность',
+'label.note' => 'Комментарий',
+'label.item' => 'Предмет',
+'label.cost' => 'Стоимость',
+'label.week_total' => 'Итог за неделю',
+'label.day_total' => 'Итог за день',
+'label.today' => 'Сегодня',
+'label.total_hours' => 'Итого часов',
+'label.total_cost' => 'Итоговая стоимость',
+'label.view' => 'Посмотреть',
+'label.edit' => 'Редактировать',
+'label.delete' => 'Удалить',
+'label.configure' => 'Настроить',
+'label.select_all' => 'Отметить все',
+'label.select_none' => 'Снять все отметки',
+'label.id' => 'ID',
+'label.language' => 'Язык',
+'label.decimal_mark' => 'Десятичный знак',
+'label.lock_interval' => 'Интервал блокировки в днях',
+'label.date_format' => 'Формат даты',
+'label.time_format' => 'Формат времени',
+'label.week_start' => 'День начала недели',
+'label.comment' => 'Комментарий',
+'label.status' => 'Статус',
+'label.tax' => 'Налог',
+'label.subtotal' => 'Сумма',
+'label.total' => 'Итого',
+'label.client_name' => 'Имя клиента',
+'label.client_address' => 'Адрес клиента',
+'label.or' => 'или',
+'label.error' => 'Ошибка',
+'label.ldap_hint' => 'Введите свои <b>Windows логин</b> и <b>пароль</b> в поля ниже.',
+'label.required_fields' => '* - обязательные для заполнения поля',
+'label.on_behalf' => 'от имени',
+'label.role_manager' => '(менеджер)',
+'label.role_comanager' => '(ассистент менеджера)',
+'label.role_admin' => '(администратор)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Дополнительные поля',
+'label.type' => 'Тип',
+'label.type_dropdown' => 'комбо',
+'label.type_text' => 'текст',
+'label.required' => 'Обязательное',
+'label.fav_report' => 'Стандартный отчёт',
+'label.cron_schedule' => 'Расписание cron',
+'label.what_is_it' => 'Что это?',
+
+// Form titles.
+'title.login' => 'Вход в систему',
+'title.teams' => 'Команды',
+'title.create_team' => 'Создание команды',
+'title.edit_team' => 'Редактирование команды',
+'title.delete_team' => 'Удаление команды',
+'title.reset_password' => 'Cброс пароля',
+'title.change_password' => 'Смена пароля',
+'title.time' => 'Время',
+'title.edit_time_record' => 'Редактирование записи о времени',
+'title.delete_time_record' => 'Удаление записи о времени',
+'title.expenses' => 'Расходы',
+'title.edit_expense' => 'Редактирование предмета расхода',
+'title.delete_expense' => 'Удаление предмета расхода',
+'title.reports' => 'Отчёты',
+'title.report' => 'Отчёт',
+'title.send_report' => 'Отсылка отчёта',
+'title.invoice' => 'Счёт',
+'title.send_invoice' => 'Отсылка счёта',
+'title.charts' => 'Диаграммы',
+'title.projects' => 'Проекты',
+'title.add_project' => 'Создание проекта',
+'title.edit_project' => 'Редактирование проекта',
+'title.delete_project' => 'Удаление проекта',
+'title.tasks' => 'Задачи',
+'title.add_task' => 'Добавление задачи',
+'title.edit_task' => 'Редактирование задачи',
+'title.delete_task' => 'Удаление задачи',
+'title.users' => 'Сотрудники',
+'title.add_user' => 'Создание пользователя',
+'title.edit_user' => 'Редактирование пользователя',
+'title.delete_user' => 'Удаление пользователя',
+'title.clients' => 'Клиенты',
+'title.add_client' => 'Добавление клиента',
+'title.edit_client' => 'Редактирование клиента',
+'title.delete_client' => 'Удаление клиента',
+'title.invoices' => 'Счета',
+'title.add_invoice' => 'Добавление счёта',
+'title.view_invoice' => 'Просматривание счёта',
+'title.delete_invoice' => 'Удаление счёта',
+'title.notifications' => 'Уведомления',
+'title.add_notification' => 'Добавление уведомления',
+'title.edit_notification' => 'Редактирование уведомления',
+'title.delete_notification' => 'Удаление уведомления',
+'title.export' => 'Экспортирование данных команды',
+'title.import' => 'Импортирование данных команды',
+'title.options' => 'Опции',
+'title.profile' => 'Профиль',
+'title.cf_custom_fields' => 'Дополнительные поля',
+'title.cf_add_custom_field' => 'Добавление поля',
+'title.cf_edit_custom_field' => 'Редактирование поля',
+'title.cf_delete_custom_field' => 'Удаление поля',
+'title.cf_dropdown_options' => 'Опции для выпадающего поля',
+'title.cf_add_dropdown_option' => 'Добавление опции',
+'title.cf_edit_dropdown_option' => 'Редактирование опции',
+'title.cf_delete_dropdown_option' => 'Удаление опции',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- все ---',
+'dropdown.no' => '--- нет ---',
+'dropdown.this_day' => 'этот день',
+'dropdown.this_week' => 'эта неделя',
+'dropdown.last_week' => 'прошлая неделя',
+'dropdown.this_month' => 'этот месяц',
+'dropdown.last_month' => 'прошлый месяц',
+'dropdown.this_year' => 'этот год',
+'dropdown.all_time' => 'всё время',
+'dropdown.projects' => 'проекты',
+'dropdown.tasks' => 'задачи',
+'dropdown.clients' => 'клиенты',
+'dropdown.select' => '--- выберите ---',
+'dropdown.select_invoice' => '--- выберите счёт ---',
+'dropdown.status_active' => 'активный',
+'dropdown.status_inactive' => 'неактивный',
+'dropdown.delete'=>'удалить',
+'dropdown.do_not_delete'=>'не удалять',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Забыли пароль?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> - это открытая (open source), простая и лёгкая в использовании система трекинга рабочего времени.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Запрос на сброс пароля отослан по e-mail.',
+'form.reset_password.email_subject' => 'Сброс пароля к Anuko Time Tracker',
+'form.reset_password.email_body' => "Уважаемый пользователь,\n\nКто-то, возможно вы, попросил сбросить ваш пароль к системе Anuko Time Tracker. Пройдите по данной ссылке для сброса пароля.\n\n%s\n\nAnuko Time Tracker - это открытая (open source), простая и лёгкая в использовании система трекинга рабочего времени. Подробности - на сайте https://www.anuko.com.",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Впечатайте новый пароль и нажмите Cохранить.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(чч:мм или 0.0ч)',
+'form.time.billable' => 'Включается в счёт',
+'form.time.uncompleted' => 'Не завершено',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Эта запись сохранена только со временем начала. Это не ошибка.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Сохранить как стандартный отчёт',
+'form.reports.confirm_delete' => 'Удалить стандартный отчёт?',
+'form.reports.include_records' => 'Включать записи',
+'form.reports.include_billable' => 'включаемые в счёт',
+'form.reports.include_not_billable' => 'не включаемые в счёт',
+'form.reports.include_invoiced' => 'внесённые в счёт',
+'form.reports.include_not_invoiced' => 'не внесённые в счёт',
+'form.reports.select_period' => 'Выберите интервал времени',
+'form.reports.set_period' => 'или укажите даты',
+'form.reports.show_fields' => 'Показывать поля',
+'form.reports.group_by' => 'Группировать по',
+'form.reports.group_by_no' => '--- без группировки ---',
+'form.reports.group_by_date' => 'дате',
+'form.reports.group_by_user' => 'пользователю',
+'form.reports.group_by_client' => 'клиенту',
+'form.reports.group_by_project' => 'проекту',
+'form.reports.group_by_task' => 'задаче',
+'form.reports.totals_only' => 'Только итоги',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Экспортировать',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Номер счёта',
+'form.invoice.person' => 'Работник',
+'form.invoice.invoice_to_delete' => 'Счёт для удаления',
+'form.invoice.invoice_entries' => 'Записи счёта',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Интервал',
+'form.charts.chart' => 'Диаграмма',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Активные проекты',
+'form.projects.inactive_projects' => 'Неактивные проекты',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Активные задачи',
+'form.tasks.inactive_tasks' => 'Неактивные задачи',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Активные пользователи',
+'form.users.inactive_users' => 'Неактивные пользователи',
+'form.users.role' => 'Роль',
+'form.users.manager' => 'Менеджер',
+'form.users.comanager' => 'Ассистент менеджера',
+'form.users.rate' => 'Ставка',
+'form.users.default_rate' => 'Почасовая ставка',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+'form.client.client_to_delete' => 'Клиент для удаления',
+'form.client.client_entries' => 'Записи клиента',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Активные клиенты',
+'form.clients.inactive_clients' => 'Неактивные клиенты',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Вы можете экспортировать все данные команды в xml файл. Это может быть полезно если вы переносите данные на свой собственный сервер.',
+'form.export.compression' => 'Сжатие',
+'form.export.compression_none' => 'нет',
+'form.export.compression_bzip' => 'в формате bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Импортируйте данные команды из xml файла.',
+'form.import.file' => 'Укажите файл',
+'form.import.success' => 'Импорт успешно выполнен.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' => 'Cоздайте новую команду, сделав новый аккаунт для её менеджера.<br>Также вы можете импортировать данные команды через xml файл из другого Anuko Time Tracker сервера (запрещено дублирование логинов).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12 часов',
+'form.profile.24_hours' => '24 часа',
+'form.profile.tracking_mode' => 'Режим работы',
+'form.profile.mode_time' => 'время',
+'form.profile.mode_projects' => 'проекты',
+'form.profile.mode_projects_and_tasks' => 'проекты и задачи',
+'form.profile.record_type' => 'Тип записи',
+'form.profile.type_all' => 'все',
+'form.profile.type_start_finish' => 'начало и конец',
+'form.profile.type_duration' => 'длительность',
+'form.profile.plugins' => 'Плагины',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'От',
+'form.mail.to' => 'Кому',
+'form.mail.cc' => 'Копия',
+'form.mail.subject' => 'Тема',
+'form.mail.report_subject' => 'Time Tracker отчёт',
+'form.mail.footer' => 'Anuko Time Tracker - это открытая (open source), простая и лёгкая в использовании система трекинга рабочего времени. Подробности на сайте <a href="https://www.anuko.com">www.anuko.com</a>.',
+'form.mail.report_sent' => 'Отчёт отправлен.',
+'form.mail.invoice_sent' => 'Счёт отправлен.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/sk.lang.php b/WEB-INF/resources/sk.lang.php
new file mode 100644 (file)
index 0000000..25da529
--- /dev/null
@@ -0,0 +1,416 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Slovenčina';
+$i18n_months = array('Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December');
+$i18n_weekdays = array('Nedeľa', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota');
+$i18n_weekdays_short = array('Ne', 'Po', 'Ut', 'St', 'Št', 'Pi', 'So');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/06', '03/29', '04/01', '05/01', '05/08', '06/05', '08/29', '09/01', '09/15', '11/01', '11/17', '12/24', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// Menus - short selection strings that are displayed on the top of application web pages.
+// Example: https://timetracker.anuko.com (black menu on top).
+'menu.login' => 'Príhlásenie',
+'menu.logout' => 'Odhlásenie',
+'menu.forum' => 'Fórum',
+'menu.help' => 'Pomoc',
+'menu.create_team' => 'Vytvoriť tím',
+'menu.profile' => 'Profil',
+'menu.time' => 'Časový záznam',
+// TODO: translate the following string.
+// 'menu.expenses' => 'Expenses',
+'menu.reports' => 'Zostavy',
+'menu.charts' => 'Grafy',
+'menu.projects' => 'Projekty',
+'menu.tasks' => 'Úlohy',
+'menu.users' => 'Používatelia',
+'menu.teams' => 'Tímy',
+'menu.export' => 'Export',
+'menu.clients' => 'Klienti',
+'menu.options' => 'Nastavenia',
+
+// Footer - strings on the bottom of most pages.
+'footer.mobile_phones' => 'Time Tracker je dostupný aj pre mobilné telefóny.',
+'footer.credits' => 'Vývojový tím',
+'footer.license' => 'Licencia',
+
+// Error messages.
+// TODO: translate the following string.
+// 'error.access_denied' => 'Access denied.',
+'error.sys' => 'Systémová chyba.',
+'error.db' => 'Databázová chyba.',
+'error.field' => 'Nesprávne "{0}" údaje.',
+'error.empty' => 'Pole "{0}" je prázdne.',
+'error.not_equal' => 'Pole "{0}" nie je zhodné s poľom "{1}".',
+'error.interval' => 'Hodnota v poli "{0}" musí byť väčšia než "{1}".',
+'error.project' => 'Vyberte projekt.',
+'error.task' => 'Vyberte úlohy.',
+'error.client' => 'Vyberte klienta.',
+// TODO: translate the following string.
+// 'error.report' => 'Select report.',
+'error.auth' => 'Nesprávne prihlasovacie meno alebo heslo.',
+'error.user_exists' => 'Používateľ s týmto prihlasovacím menom už existuje.',
+'error.project_exists' => 'Projekt s týmto názvom už existuje.',
+'error.task_exists' => 'Úloha s týmto názvom už existuje.',
+'error.client_exists' => 'Klient s týmto menom už existuje.',
+'error.invoice_exists' => 'Faktúra s týmto číslom už existuje.',
+'error.no_invoiceable_items' => 'Neexistujú položky, ktoré by bolo možné fakturovať.',
+'error.no_login' => 'Neexistuje používateľ s týmto prihlasovacím menom.',
+'error.no_teams' => 'Vaša databáza je prázdna. Prihláste sa ako admin a vytvorte nový tím.',
+'error.upload' => 'Prenos súboru bol neúspešný.',
+'error.period_locked' => 'Operáciu nie je možné dokončiť. Záznamy staršie ako určitý počet dní nie je možné vytvoriť alebo upraviť. Tímový manažér definuje hodnotu "Interval uzamknutia záznamov v dňoch" na stránke "Profil". Pre vypnutie zámku nastavte hodnotu na 0. <br><br>Nekompletné záznamy (s nulovou alebo prázdnou dĺžkou) je možné vymazať.',
+'error.mail_send' => 'Chyba v odosielaní e-mailu.',
+'error.no_email' => 'K tomuto prihlasovaciemu menu nie je priradený žiadny e-mail.',
+'error.uncompleted_exists' => 'Nekompletný záznam už existuje. Zatvorte ho alebo ho vymažte.',
+'error.goto_uncompleted' => 'Ísť na nekompletný záznam.',
+// TODO: translate the following strings.
+// 'error.overlap' => 'Time interval overlaps with existing records.',
+// 'error.future_date' => 'Date is in future.',
+
+// Labels for buttons.
+'button.login' => 'Prihlásiť',
+'button.now' => 'Teraz',
+'button.save' => 'Uložiť',
+// TODO: translate the following string.
+// 'button.copy' => 'Copy',
+'button.cancel' => 'Zrušiť',
+'button.submit' => 'Odoslať',
+'button.add_user' => 'Pridať používateľa',
+'button.add_project' => 'Pridať projekt',
+'button.add_task' => 'Pridať úlohy',
+'button.add_client' => 'Pridať klienta',
+'button.add_invoice' => 'Pridať faktúru',
+'button.add_option' => 'Pridať vlastné pole',
+'button.add' => 'Pridať',
+'button.generate' => 'Generovať',
+'button.reset_password' => 'Obnoviť heslo',
+'button.send' => 'Odoslať',
+'button.send_by_email' => 'Odoslať na e-mail',
+'button.create_team' => 'Vytvoriť tím',
+'button.export' => 'Exportovať tím',
+'button.import' => 'Importovať tím',
+'button.close' => 'Zatvoriť',
+// TODO: translate the following string. 
+// 'button.stop' => 'Stop',
+
+// Labels for controls on forms. Labels in this section are used on multiple forms.
+'label.team_name' => 'Názov tímu',
+'label.address' => 'Adresa',
+'label.currency' => 'Mena',
+'label.manager_name' => 'Meno manažéra',
+'label.manager_login' => 'Prihlasovacie meno manažéra',
+'label.person_name' => 'Meno',
+'label.thing_name' => 'Meno',
+'label.login' => 'Prihlasovacie meno',
+'label.password' => 'Heslo',
+'label.confirm_password' => 'Potvrdenie hesla',
+'label.email' => 'E-mail',
+'label.date' => 'Dátum',
+'label.start_date' => 'Dátum začiatku',
+'label.end_date' => 'Dátum konca',
+'label.user' => 'Používateľ',
+'label.users' => 'Používatelia',
+'label.client' => 'Klient',
+'label.clients' => 'Klienti',
+'label.option' => 'Možnosť',
+'label.invoice' => 'Fakttúra',
+'label.project' => 'Projekt',
+'label.projects' => 'Projekty',
+'label.task' => 'Úloha',
+'label.tasks' => 'Úlohy',
+'label.description' => 'Popis',
+'label.start' => 'Začiatok',
+'label.finish' => 'Koniec',
+'label.duration' => 'Dĺžka',
+'label.note' => 'Poznámka',
+// TODO: translate label.item
+// 'label.item' => 'Item',
+'label.cost' => 'Náklady',
+'label.week_total' => 'Týždeň celkom',
+// TODO: translate the following string.
+// 'label.day_total' => 'Day total',
+'label.today' => 'Dnes',
+'label.total_hours' => 'Hodín celkom',
+'label.total_cost' => 'Náklady celkom',
+'label.view' => 'Zobraziť',
+'label.edit' => 'Upraviť',
+'label.delete' => 'Vymazať',
+'label.configure' => 'Nastaviť',
+'label.select_all' => 'Označiť všetky',
+'label.select_none' => 'Odznačiť všetky',
+'label.id' => 'ID',
+'label.language' => 'Jazyk',
+// TODO: translate the following string.
+// 'label.decimal_mark' => 'Decimal mark',
+'label.lock_interval' => 'Interval uzamknutia záznamov v dňoch',
+'label.date_format' => 'Formát dátumu',
+'label.time_format' => 'Formát času',
+'label.week_start' => 'Prvý deň v týždni',
+'label.comment' => 'Komentáre',
+'label.status' => 'Stav',
+'label.tax' => 'Daň',
+'label.subtotal' => 'Medzisúčet',
+'label.total' => 'Celkovo',
+'label.client_name' => 'Názov klienta',
+'label.client_address' => 'Adresa klienta',
+'label.or' => 'alebo',
+'label.error' => 'Chyba',
+'label.ldap_hint' => 'Zadajte <b>prihlasovacie meno do Windowsu</b> a <b>heslo</b> do polí nižšie.',
+'label.required_fields' => '* - povinné polia',
+'label.on_behalf' => 'v zastúpení',
+'label.role_manager' => '(manažér)',
+'label.role_comanager' => '(spolu-manažér)',
+'label.role_admin' => '(administrátor)',
+// Labels for plugins (extensions to Time Tracker that provide additional features).
+'label.custom_fields' => 'Vlastné polia',
+'label.type' => 'Typ',
+'label.type_dropdown' => 'rozbaľovacie pole',
+'label.type_text' => 'text',
+'label.required' => 'Povinné',
+'label.fav_report' => 'Obľúbená zostava',
+// TODO: translate the following strings.
+// 'label.cron_schedule' => 'Cron schedule',
+// 'label.what_is_it' => 'What is it?',
+
+// Form titles.
+'title.login' => 'Prihlásenie',
+'title.teams' => 'Tímy',
+'title.create_team' => 'Vytváranie tímu',
+// TODO: translate the following string.
+// 'title.edit_team' => 'Editing Team',
+'title.delete_team' => 'Vymazávanie tímu',
+'title.reset_password' => 'Obnovovanie hesla',
+'title.change_password' => 'Menenie hesla',
+'title.time' => 'Časový záznam',
+'title.edit_time_record' => 'Upravovanie časového záznamu',
+'title.delete_time_record' => 'Vymazávanie časového záznamu',
+// TODO: translate the following strings.
+// 'title.expenses' => 'Expenses',
+// 'title.edit_expense' => 'Editing Expense Item',
+// 'title.delete_expense' => 'Deleting Expense Item',
+'title.reports' => 'Zostavy',
+'title.report' => 'Zostava',
+'title.send_report' => 'Odosielanie zostavy',
+'title.invoice' => 'Faktúra',
+'title.send_invoice' => 'Odosielanie faktúry',
+'title.charts' => 'Grafy',
+'title.projects' => 'Projekty',
+'title.add_project' => 'Pridávanie projektu',
+'title.edit_project' => 'Upravovanie projektu',
+'title.delete_project' => 'Vymazávanie projektu',
+'title.tasks' => 'Úlohy',
+'title.add_task' => 'Pridávanie úlohy',
+'title.edit_task' => 'Upravovanie úlohy',
+'title.delete_task' => 'Vymazávanie úlohy',
+'title.users' => 'Používatelia',
+'title.add_user' => 'Pridávanie používateľa',
+'title.edit_user' => 'Upravovanie používateľa',
+'title.delete_user' => 'Vymazávanie používateľa',
+'title.clients' => 'Klienti',
+'title.add_client' => 'Pridávanie klienta',
+'title.edit_client' => 'Upravovanie klienta',
+'title.delete_client' => 'Vymazávanie klienta',
+'title.invoices' => 'Faktúry',
+'title.add_invoice' => 'Pridávanie faktúry',
+'title.view_invoice' => 'Priehliadanie faktúry',
+'title.delete_invoice' => 'Vymazávanie faktúry',
+// TODO: translate the following strings.
+// 'title.notifications' => 'Notifications',
+// 'title.add_notification' => 'Adding Notification',
+// 'title.edit_notification' => 'Editing Notification',
+// 'title.delete_notification' => 'Deleting Notification',
+'title.export' => 'Exportovanie údajov o tíme',
+'title.import' => 'Importovanie údajov o tíme',
+'title.options' => 'Nastavenia',
+'title.profile' => 'Profil',
+'title.cf_custom_fields' => 'Vlastné polia',
+'title.cf_add_custom_field' => 'Pridávanie vlastného poľa',
+'title.cf_edit_custom_field' => 'Upravovanie vlastného poľa',
+'title.cf_delete_custom_field' => 'Vymazávanie vlastného poľa',
+'title.cf_dropdown_options' => 'Nastavenia rozbaľovacieho poľa',
+'title.cf_add_dropdown_option' => 'Pridávanie možností',
+'title.cf_edit_dropdown_option' => 'Upravovanie možností',
+'title.cf_delete_dropdown_option' => 'Vymazávanie možností',
+
+// Section for common strings inside combo boxes on forms. Strings shared between forms shall be placed here.
+// Strings that are used in a single form must go to the specific form section.
+'dropdown.all' => '--- všetky ---',
+'dropdown.no' => '--- žiadne ---',
+'dropdown.this_day' => 'tento deň',
+'dropdown.this_week' => 'tento týždeň',
+'dropdown.last_week' => 'minulý týždeň',
+'dropdown.this_month' => 'tento mesiac',
+'dropdown.last_month' => 'minulý mesiac',
+'dropdown.this_year' => 'tento rok',
+'dropdown.all_time' => 'celý čas',
+'dropdown.projects' => 'projekty',
+'dropdown.tasks' => 'úlohy',
+// TODO: translate the following string.
+'dropdown.clients' => 'clients',
+'dropdown.select' => '--- vyberte ---',
+// TODO: translate the following string.
+// 'dropdown.select_invoice' => '--- select invoice ---',
+'dropdown.status_active' => 'aktívny',
+'dropdown.status_inactive' => 'neaktívny',
+// TODO: translate the following strings.
+// 'dropdown.delete'=>'delete',
+// 'dropdown.do_not_delete'=>'do not delete',
+
+// Below is a section for strings that are used on individual forms. When a string is used only on one form it should be placed here.
+// One exception is for closely related forms such as "Time" and "Editing Time Record" with similar controls. In such cases
+// a string can be defined on the main form and used on related forms. The reasoning for this is to make translation effort easier.
+// Strings that are used on multiple unrelated forms should be placed in shared sections such as label.<stringname>, etc.
+
+// Login form. See example at https://timetracker.anuko.com/login.php.
+'form.login.forgot_password' => 'Zabudnuté heslo?',
+'form.login.about' =>'Anuko <a href="https://www.anuko.com/lp/tt_2.htm" target="_blank">Time Tracker</a> je jednoduchý a ľahko použiteľný systém na sledovanie času s otvoreným zdrojovým kódom.',
+
+// Resetting Password form. See example at https://timetracker.anuko.com/password_reset.php.
+'form.reset_password.message' => 'Žiadosť o obnovenie hesla bola odoslaná e-mailom.',
+'form.reset_password.email_subject' => 'Žiadosť o obnovenie hesla do Anuko Time Tracker',
+'form.reset_password.email_body' => "Vážený používateľ,\n\nniekto, pravdepodobne vy, si vyžiadal obnovenie vášho hesla do Anuko Time Tracker. Prosím kliknite na nasledujúcu linku, ak si prajete obnoviť heslo.\n\n%s\n\nAnuko Time Tracker je jednoduchý a ľahko použiteľný systém na sledovanie času s otvoreným zdrojovým kódom. Navštívte https://www.anuko.com pre viac informácií.\n\n",
+
+// Changing Password form. See example at https://timetracker.anuko.com/password_change.php?ref=1.
+'form.change_password.tip' => 'Zadajte nové heslo a kliknite na Uložiť.',
+
+// Time form. See example at https://timetracker.anuko.com/time.php.
+'form.time.duration_format' => '(hh:mm alebo 0,0h)',
+'form.time.billable' => 'Faktúrovateľných',
+'form.time.no_records' => 'Neexistujú časové záznamy.',
+'form.time.uncompleted' => 'Neukončené',
+
+// Editing Time Record form. See example at https://timetracker.anuko.com/time_edit.php (get there by editing an uncompleted time record).
+'form.time_edit.uncompleted' => 'Tento záznam bol uložený iba s časom začiatku. Nie je to chyba.',
+
+// Reports form. See example at https://timetracker.anuko.com/reports.php
+'form.reports.save_as_favorite' => 'Uložiť ako obľúbenú zostavu',
+'form.reports.confirm_delete' => 'Ste si istý, že chcete vymazať túto obľúbenú zostavu?',
+'form.reports.include_records' => 'Zahrnúť záznamy',
+'form.reports.include_billable' => 'faktúrovateĺné',
+'form.reports.include_not_billable' => 'nefaktúrovateľné',
+// TODO: translate the following strings.
+// 'form.reports.include_invoiced' => 'invoiced',
+// 'form.reports.include_not_invoiced' => 'not invoiced',
+'form.reports.select_period' => 'Vyberte časový rozsah',
+'form.reports.set_period' => 'alebo nastavte dátumy',
+'form.reports.show_fields' => 'Zobraziť polia',
+// TODO: translate the following strings.
+// 'form.reports.group_by' => 'Group by',
+// 'form.reports.group_by_no' => '--- no grouping ---',
+'form.reports.group_by_date' => 'dátum',
+'form.reports.group_by_user' => 'používateľ',
+'form.reports.group_by_client' => 'klient',
+'form.reports.group_by_project' => 'projekt',
+'form.reports.group_by_task' => 'úloha',
+'form.reports.totals_only' => 'Iba celkové',
+
+// Report form. See example at https://timetracker.anuko.com/report.php
+// (after generating a report at https://timetracker.anuko.com/reports.php).
+'form.report.export' => 'Exportovať',
+
+// Invoice form. See example at https://timetracker.anuko.com/invoice.php
+// (you can get to this form after generating a report).
+'form.invoice.number' => 'Číslo faktúry',
+'form.invoice.person' => 'Osoba',
+// TODO: translate the following stings.
+// 'form.invoice.invoice_to_delete' => 'Invoice to delete',
+// 'form.invoice.invoice_entries' => 'Invoice entries',
+
+// Charts form. See example at https://timetracker.anuko.com/charts.php
+'form.charts.interval' => 'Interval',
+'form.charts.chart' => 'Graf',
+
+// Projects form. See example at https://timetracker.anuko.com/projects.php
+'form.projects.active_projects' => 'Aktívne projekty',
+'form.projects.inactive_projects' => 'Neaktívne projekty',
+
+// Tasks form. See example at https://timetracker.anuko.com/tasks.php
+'form.tasks.active_tasks' => 'Aktívne úlohy',
+'form.tasks.inactive_tasks' => 'Neaktívne úlohy',
+
+// Users form. See example at https://timetracker.anuko.com/users.php
+'form.users.active_users' => 'Aktívny používatelia',
+'form.users.inactive_users' => 'Neaktívny používatelia',
+'form.users.role' => 'Rola',
+'form.users.manager' => 'Manažér',
+'form.users.comanager' => 'Spolumanažér',
+'form.users.rate' => 'Sadzba',
+'form.users.default_rate' => 'Predvolená hodinová sadzba',
+
+// Client delete form. See example at https://timetracker.anuko.com/client_delete.php
+// TODO: translate the following strings.
+// 'form.client.client_to_delete' => 'Client to delete',
+// 'form.client.client_entries' => 'Client entries',
+
+// Clients form. See example at https://timetracker.anuko.com/clients.php
+'form.clients.active_clients' => 'Aktívny klienti',
+'form.clients.inactive_clients' => 'Neaktívny klienti',
+
+// Strings for Exporting Team Data form. See example at https://timetracker.anuko.com/export.php
+'form.export.hint' => 'Môžete exportovať všetky údaje o tíme do xml súboru. Toto môže byť užitočné pri prenose údajov na iný server.',
+'form.export.compression' => 'Kompresia',
+'form.export.compression_none' => 'žiadna',
+'form.export.compression_bzip' => 'bzip',
+
+// Strings for Importing Team Data form. See example at https://timetracker.anuko.com/imort.php (login as admin first).
+'form.import.hint' => 'Importovať dáta o tíme z xml súboru.',
+'form.import.file' => 'Vyberte súbor',
+'form.import.success' => 'Import úspešne dokončený.',
+
+// Teams form. See example at https://timetracker.anuko.com/admin_teams.php (login as admin first).
+'form.teams.hint' =>  'Pomocou vytvorenia nového účtu tímového manažéra vytvorte nový tím.<br>Taktiež môžete importovať údaje o tíme z xml súboru z iného Anuko Time Tracker serveru (nie sú povolené kolízie v prihlasovacom mene).',
+
+// Profile form. See example at https://timetracker.anuko.com/profile_edit.php.
+'form.profile.12_hours' => '12-hodinový',
+'form.profile.24_hours' => '24-hodinový',
+'form.profile.tracking_mode' => 'Režim sledovania',
+'form.profile.mode_time' => 'čas',
+'form.profile.mode_projects' => 'projekty',
+'form.profile.mode_projects_and_tasks' => 'projekty a úlohy',
+'form.profile.record_type' => 'Typ záznamu',
+'form.profile.type_all' => 'všetky',
+'form.profile.type_start_finish' => 'začiatok a koniec',
+'form.profile.type_duration' => 'trvanie',
+'form.profile.plugins' => 'Doplnkové moduly',
+
+// Mail form. See example at https://timetracker.anuko.com/report_send.php when emailing a report.
+'form.mail.from' => 'Od',
+'form.mail.to' => 'Komu',
+'form.mail.cc' => 'Kópia',
+'form.mail.subject' => 'Predmet',
+'form.mail.report_subject' => 'Time Tracker zostava',
+'form.mail.footer' => 'Anuko Time Tracker je jednoduchý a ľahko použiteľný systém na sledovanie času s otvoreným zdrojovým kódom.<br> Navštívte <a href="https://www.anuko.com">www.anuko.com</a> pre viac informácií.',
+'form.mail.report_sent' => 'Zostava odoslaná.',
+'form.mail.invoice_sent' => 'Faktúra odoslaná.',
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/sl.lang.php b/WEB-INF/resources/sl.lang.php
new file mode 100644 (file)
index 0000000..389f817
--- /dev/null
@@ -0,0 +1,399 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Slovenščina';
+$i18n_months = array('januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december');
+$i18n_weekdays = array('nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota');
+$i18n_weekdays_short = array('ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/02', '02/08', '04/12', '04/13', '04/27', '05/01', '05/02', '06/25', '10/31', '11/01', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'prijava',
+'menu.logout' => 'odjava',
+'menu.feedback' => 'povratna informacija',
+'menu.help' => 'pomoč',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'ustvari nov manager račun',
+'menu.edit_profile' => 'uredi profil',
+'menu.my_time' => 'moj čas',
+'menu.reports' => 'poročila',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projekti',
+'menu.activities' => 'aktivnosti',
+'menu.people' => 'ljudje',
+'menu.teams' => 'timi',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'stranke',
+'menu.options' => 'možnosti',
+'menu.admin' => 'admin',
+
+// Note to translators: these strings need to be translated.
+// error strings
+// 'error.db' => 'database error',
+// 'error.field' => 'incorrect "{0}" data',
+// 'error.empty' => 'field "{0}" is empty',
+// 'error.not_equal' => 'field "{0}" is not equal to field "{1}"',
+// 'error.interval' => 'incorrect interval',
+// 'error.project' => 'select project',
+// 'error.activity' => 'select activity',
+// 'error.auth' => 'incorrect login or password',
+// 'error.user_exists' => 'user with this login already exists',
+// 'error.project_exists' => 'project with this name already exists',
+// 'error.activity_exists' => 'activity with this name already exists',
+// 'error.client_exists' => 'client with this name already exists',
+// 'error.no_login' => 'no user with this login',
+// 'error.upload' => 'file upload error',
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'prijava',
+'button.now' => 'zdaj',
+// 'button.set' => 'nastavi',
+'button.save' => 'shrani',
+'button.delete' => 'izbriši',
+'button.cancel' => 'prekliči',
+'button.submit' => 'potrdi',
+'button.add_user' => 'dodaj uporabnika',
+'button.add_project' => 'dodaj projekt',
+'button.add_activity' => 'dodaj aktivnost',
+'button.add_client' => 'dodaj stranko',
+'button.add' => 'dodaj',
+'button.generate' => 'ustvari',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'pojdi',
+'button.send' => 'pošlji',
+'button.send_by_email' => 'pošlji preko elektronske pošte',
+'button.save_as_new' => 'shrani kot novo',
+'button.create_team' => 'ustvari tim',
+'button.export' => 'izvozi tim',
+'button.import' => 'uvozi tim',
+'button.apply' => 'uporabi',
+
+// labels for controls on various forms
+// TODO: translate label.team_name and the strings below.
+// 'label.team_name' => 'team name',
+// 'label.currency' => 'currency',
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'geslo',
+// 'label.confirm_password' => 'confirm password',
+'label.email' => 'email',
+'label.total' => 'total',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'favorite report',
+"form.filter.filter_new" => 'save as favorite',
+"form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'prijava',
+"form.login.login" => 'prijava',
+
+// password reminder form attributes
+"form.fpass.title" => 'razveljavi geslo',
+"form.fpass.login" => 'prijava',
+"form.fpass.send_pass_str" => 'zahteva za razveljavitev gesla je bila poslana',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker zahteva za razveljavitev gesla',
+// Note to translators: the ending of this tring below needs to be translated.
+"form.fpass.send_pass_body" => "Spoštovani uporabnik,\n\nNekdo, najverjetneje vi, je zahteval razveljavitev vašega Anuko Time Tracker gesla. Prosimo obiščite to povezavo, če želite razveljaviti vaše geslo.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "za razveljavitev gesla, prosimo vtipkajte geslo in kliknite gumb shrani",
+
+// administrator form
+"form.admin.title" => 'administrator',
+"form.admin.duty_text" => 'create a new team by creating a new team manager account.<br>you can also import team data from an xml file from another Anuko Time Tracker server (no login collisions are allowed).',
+
+"form.admin.change_pass" => 'change password of administrator account',
+"form.admin.profile.title" => 'teams',
+"form.admin.profile.noprofiles" => 'your database is empty. login as admin and create a new team.',
+"form.admin.profile.comment" => 'delete team',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'name',
+"form.admin.profile.th.edit" => 'edit',
+"form.admin.profile.th.del" => 'delete',
+"form.admin.profile.th.active" => 'active',
+"form.admin.lock.period" => 'lock interval in days',
+"form.admin.options" => 'options',
+"form.admin.lang_default" => 'site default language',
+"form.admin.lang_browser_default" => '(browser default)',
+"form.admin.custom_date_format" => "date format",
+"form.admin.custom_time_format" => "time format",
+"form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'my time',
+"form.mytime.edit_title" => 'editing time record',
+"form.mytime.del_str" => 'deleting time record',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'date',
+"form.mytime.project" => 'project',
+"form.mytime.activity" => 'activity',
+"form.mytime.start" => 'start',
+"form.mytime.finish" => 'finish',
+"form.mytime.duration" => 'duration',
+"form.mytime.note" => 'note',
+"form.mytime.behalf" => 'daily work for',
+"form.mytime.daily" => 'daily work',
+"form.mytime.total" => 'hours total: ',
+"form.mytime.th.project" => 'project',
+"form.mytime.th.activity" => 'activity',
+"form.mytime.th.start" => 'start',
+"form.mytime.th.finish" => 'finish',
+"form.mytime.th.duration" => 'duration',
+"form.mytime.th.note" => 'note',
+"form.mytime.th.edit" => 'edit',
+"form.mytime.th.delete" => 'delete',
+"form.mytime.del_yes" => 'time record deleted successfully',
+"form.mytime.no_finished_rec" => 'this record was saved with only start time. it is not an error. logout if you need to.',
+"form.mytime.billable" => 'billable',
+"form.mytime.warn_tozero_rec" => 'this time record must be deleted because this time period is locked',
+"form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+"form.profile.create_title" => 'creating team',
+"form.profile.edit_title" => 'editing profile',
+"form.profile.name" => 'name',
+"form.profile.login" => 'login',
+
+"form.profile.showchart" => 'show pie charts',
+"form.profile.lang" => 'language',
+"form.profile.lang_browser_default" => '(browser default)',
+"form.profile.custom_date_format" => "date format",
+"form.profile.custom_time_format" => "time format",
+"form.profile.default_format" => "(default)",
+"form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'people',
+"form.people.createu_str" => 'creating new user',
+"form.people.edit_str" => 'editing user',
+"form.people.del_str" => 'deleting user',
+"form.people.th.name" => 'name',
+"form.people.th.login" => 'login',
+"form.people.th.role" => 'role',
+"form.people.th.edit" => 'edit',
+"form.people.th.del" => 'delete',
+"form.people.th.status" => 'status',
+"form.people.th.project" => 'project',
+"form.people.th.rate" => 'rate',
+"form.people.manager" => 'manager',
+"form.people.comanager" => 'comanager',
+"form.people.empl" => 'user',
+"form.people.name" => 'name',
+"form.people.login" => 'login',
+
+"form.people.rate" => 'default hourly rate',
+"form.people.comanager" => 'co-manager',
+"form.people.projects" => 'projects',
+
+// projects form attributes
+"form.project.proj_title" => 'projekti',
+"form.project.edit_str" => 'urejanje projektov',
+"form.project.add_str" => 'dodajanje novega projekta',
+"form.project.del_str" => 'brisanje projekta',
+"form.project.th.name" => 'ime',
+"form.project.th.edit" => 'uredi',
+"form.project.th.del" => 'izbriši',
+"form.project.name" => 'ime',
+
+// activities form attributes
+"form.activity.act_title" => 'aktivnosti',
+"form.activity.add_title" => 'dodajanje novih aktivnosti',
+"form.activity.edit_str" => 'urejanje aktivnosti',
+"form.activity.del_str" => 'brisanje aktivnosti',
+"form.activity.name" => 'ime',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'ime',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'uredi',
+"form.activity.th.del" => 'izbriši',
+
+// report attributes
+"form.report.title" => 'reports',
+"form.report.from" => 'start date',
+"form.report.to" => 'end date',
+"form.report.groupby_user" => 'user',
+"form.report.groupby_project" => 'project',
+"form.report.groupby_activity" => 'activity',
+"form.report.duration" => 'duration',
+"form.report.start" => 'start',
+"form.report.activity" => 'activity',
+"form.report.show_idle" => 'show idle',
+"form.report.finish" => 'finish',
+"form.report.note" => 'note',
+"form.report.project" => 'project',
+"form.report.totals_only" => 'totals only',
+"form.report.total" => 'hours total',
+"form.report.th.empllist" => 'user',
+"form.report.th.date" => 'date',
+"form.report.th.project" => 'project',
+"form.report.th.activity" => 'activity',
+"form.report.th.start" => 'start',
+"form.report.th.finish" => 'finish',
+"form.report.th.duration" => 'duration',
+"form.report.th.note" => 'note',
+
+// mail form attributes
+"form.mail.from" => 'od',
+"form.mail.to" => 'za',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'predmet',
+"form.mail.comment" => 'komentar',
+"form.mail.above" => 'pošlji to poročilo preko elektronske pošte',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>sporočilo poslano</b>',
+
+// invoice attributes
+"form.invoice.title" => 'invoice',
+"form.invoice.caption" => 'invoice',
+"form.invoice.above" => 'additional information for invoice',
+"form.invoice.select_cust" => 'select client',
+"form.invoice.fillform" => 'fill the fields',
+"form.invoice.date" => 'date',
+"form.invoice.number" => 'invoice number',
+"form.invoice.tax" => 'tax',
+"form.invoice.daily_subtotals" => 'daily subtotals',
+"form.invoice.yourcoo" => 'your name<br> and address',
+"form.invoice.custcoo" => 'client name<br> and address',
+"form.invoice.comment" => 'comment ',
+"form.invoice.th.username" => 'person',
+"form.invoice.th.time" => 'hours',
+"form.invoice.th.rate" => 'rate',
+"form.invoice.th.summ" => 'amount',
+"form.invoice.subtotal" => 'subtotal',
+"form.invoice.customer" => 'client',
+"form.invoice.mailinv_above" => 'send this invoice by e-mail',
+"form.invoice.sending_str" => '<b>invoice sent</b>',
+
+"form.migration.zip" => 'compression',
+"form.migration.file" => 'select file',
+"form.migration.import.title" => 'import data',
+"form.migration.import.success" => 'import completed successfully',
+"form.migration.import.text" => 'import team data from an xml file',
+"form.migration.export.title" => 'export data',
+"form.migration.export.success" => 'export completed successfully',
+"form.migration.export.text" => 'you can export all team data into an xml file. this could be useful if you are migrating data to your own server.',
+"form.migration.compression.none" => 'none',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'clients',
+"form.client.add_title" => 'add client',
+"form.client.edit_title" => 'edit client',
+"form.client.del_title" => 'delete client',
+"form.client.th.name" => 'name',
+"form.client.th.edit" => 'edit',
+"form.client.th.del" => 'delete',
+"form.client.name" => 'name',
+"form.client.tax" => 'tax',
+"form.client.daily_subtotals" => 'daily subtotals',
+"form.client.yourcoo" => 'your name<br> and address in invoice',
+"form.client.custcoo" => 'address',
+"form.client.comment" => 'comment ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'forgot password?',
+"forward.edit" => 'edit',
+"forward.delete" => 'delete',
+"forward.tocsvfile" => 'export data to .csv file',
+"forward.toxmlfile" => 'export data to .xml file',
+"forward.geninvoice" => 'generate invoice',
+"forward.change" => 'configure clients',
+
+// strings inside contols on forms
+"controls.select.project" => '--- select project ---',
+"controls.select.activity" => '--- select activity ---',
+"controls.select.client" => '--- select client ---',
+"controls.project_bind" => '--- all ---',
+"controls.all" => '--- all ---',
+"controls.notbind" => '--- no ---',
+"controls.per_tm" => 'this month',
+"controls.per_lm" => 'last month',
+"controls.per_tw" => 'this week',
+"controls.per_lw" => 'last week',
+"controls.per_td" => 'this day',
+"controls.per_at" => 'all time',
+"controls.per_ty" => 'this year',
+"controls.sel_period" => '--- select time period ---',
+"controls.sel_groupby" => '--- no grouping ---',
+"controls.inc_billable" => 'billable',
+"controls.inc_nbillable" => 'not billable',
+"controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'aktivnosti uporabnika',
+"label.chart.title2" => 'projekti uporabnika',
+"label.chart.period" => 'graf za obdobje',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>on behalf of %s</b>',
+"label.pminfo" => ' (manager)',
+"label.pcminfo" => ' (co-manager)',
+"label.painfo" => ' (administrator)',
+"label.time_noentry" => 'no entry',
+"label.today" => 'today',
+"label.req_fields" => '* required fields',
+"label.sel_project" => 'select project',
+"label.sel_activity" => 'select activity',
+"label.sel_tp" => 'select time period',
+"label.set_tp" => 'or set dates',
+"label.fields" => 'show fields',
+"label.group_title" => 'group by',
+"label.include_title" => 'include records',
+"label.inv_str" => 'invoice',
+"label.set_empl" => 'select users',
+"label.sel_all" => 'select all',
+"label.sel_none" => 'deselect all',
+"label.or" => 'or',
+"label.disable" => 'disable',
+"label.enable" => 'enable',
+"label.filter" => 'filter',
+"label.timeweek" => 'weekly total',
+"label.hrs" => 'hrs',
+"label.errors" => 'errors',
+"label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+"label.calendar_today" => 'today',
+"label.calendar_close" => 'close',
+
+// login hello text
+"login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/sv.lang.php b/WEB-INF/resources/sv.lang.php
new file mode 100644 (file)
index 0000000..18a4f79
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Svenska';
+$i18n_months = array('Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December');
+$i18n_weekdays = array('Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag');
+$i18n_weekdays_short = array('sön', 'mån', 'tis', 'ons', 'tor', 'fr', 'lör');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/06', '04/10', '04/12', '04/13', '05/01', '05/21', '05/31', '06/06', '06/20', '10/31', '11/01', '12/25', '12/26');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'Logga in',
+'menu.logout' => 'Logga ut',
+'menu.feedback' => 'Feedback',
+'menu.help' => 'Hjälp',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'Skapa huvudkonto',
+'menu.edit_profile' => 'Ändra profil',
+'menu.my_time' => 'Mina tider',
+'menu.reports' => 'Rapporter',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'Projekt',
+'menu.activities' => 'Aktiviteter',
+'menu.people' => 'Personal',
+// Note to translators: is menu.teams translated correctly? It's a list of teams in the system.
+'menu.teams' => 'Team',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'Kunder',
+'menu.options' => 'Inställningar',
+'menu.admin' => 'Admin',
+
+// error strings
+'error.db' => 'Fel i databasen',
+'error.field' => 'felaktig "{0}" data',
+'error.empty' => 'fält "{0}" är tomt',
+'error.not_equal' => 'fält "{0}" är inte lika med fält "{1}"',
+'error.interval' => 'felaktig intervall',
+'error.project' => 'välj projekt',
+'error.activity' => 'välj aktivitet',
+'error.auth' => 'felaktig login eller lösenord',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'projektnamnet finns redan i databasen',
+'error.activity_exists' => 'aktivitetsnamnet finns redan i databasen',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'det finns ingen användare med denna e-postadress',
+'error.upload' => 'felaktig filuppladdning',
+// Note to translators: the strings below are missing and must be added and translated
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'Logga in',
+'button.now' => 'nu',
+// 'button.set' => 'använd',
+'button.save' => 'spara',
+'button.delete' => 'ta bort',
+'button.cancel' => 'avbryt',
+'button.submit' => 'skicka',
+'button.add_user' => 'skapa användare',
+'button.add_project' => 'skapa projekt',
+'button.add_activity' => 'skapa aktivitet',
+'button.add_client' => 'skapa kund',
+'button.add' => 'lägg till',
+'button.generate' => 'skapa',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'gå vidare',
+'button.send' => 'skicka',
+'button.send_by_email' => 'skicka e-post',
+'button.save_as_new' => 'spara som nytt',
+// TODO: improve translation of button.create_team.
+'button.create_team' => 'skapa nytt team',
+'button.export' => 'exportera team',
+'button.import' => 'importera team',
+'button.apply' => 'spara',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'valuta',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'lösenord',
+'label.confirm_password' => 'bekräfta lösenord',
+'label.email' => 'e-post',
+'label.total' => 'total',
+
+"form.filter.project" => 'projekt',
+"form.filter.filter" => 'favorit rapport',
+"form.filter.filter_new" => 'spara som favorit',
+// Note to translators: the string below is missing and must be added and translated
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'logga in',
+"form.login.login" => 'logga in',
+
+// password reminder form attributes
+"form.fpass.title" => 'återställ lösenord',
+"form.fpass.login" => 'logga in',
+"form.fpass.send_pass_str" => 'begäran för återställning av lösenord har skickats',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker lösenordsåterställning',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "Kära användare,\n\nSomeone, antagligen har du begärt återställning av lösenord för Anuko Time Tracker. Vänligen besök denna länk om du vill återställa ditt lösenord.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "skriv in ditt lösenord och tryck på spara för att ändra ditt lösenord",
+
+// administrator form
+"form.admin.title" => 'administratör',
+"form.admin.duty_text" => 'skapa ny team genom att skapa en teamledar konto.<br>du kan även importera team information från en xmlfil eller annan anuko time tracker server (inga e-post dubletter tillåts).',
+
+"form.admin.change_pass" => 'ändra lösenord för administratörs kontot',
+"form.admin.profile.title" => 'team',
+"form.admin.profile.noprofiles" => 'databasen är tom. logga in som admin och skapa ny team.',
+"form.admin.profile.comment" => 'ta bort team',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'namn',
+"form.admin.profile.th.edit" => 'redigera',
+"form.admin.profile.th.del" => 'ta bort',
+"form.admin.profile.th.active" => 'aktiverad',
+"form.admin.lock.period" => 'låsintervall i antal dagar',
+"form.admin.options" => 'inställningar',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'mina tider',
+"form.mytime.edit_title" => 'ändra tidstämpel',
+"form.mytime.del_str" => 'ta bort tidsstämpel',
+"form.mytime.time_form" => ' (hh:mm)',
+"form.mytime.date" => 'datum',
+"form.mytime.project" => 'projekt',
+"form.mytime.activity" => 'aktivitet',
+"form.mytime.start" => 'start',
+"form.mytime.finish" => 'slut',
+"form.mytime.duration" => 'varaktighet',
+"form.mytime.note" => 'beskrivning',
+"form.mytime.behalf" => 'daglig arbete för',
+"form.mytime.daily" => 'daglig arbete',
+"form.mytime.total" => 'total antal timmar: ',
+"form.mytime.th.project" => 'projekt',
+"form.mytime.th.activity" => 'aktivitet',
+"form.mytime.th.start" => 'start',
+"form.mytime.th.finish" => 'slut',
+"form.mytime.th.duration" => 'längd',
+"form.mytime.th.note" => 'beskrivning',
+"form.mytime.th.edit" => 'ändra',
+"form.mytime.th.delete" => 'ta bort',
+"form.mytime.del_yes" => 'borttagning av tiddstämpel lyckades',
+"form.mytime.no_finished_rec" => 'denna tidsstämpel var sparad med endast starttid. Det är inget fel. logga ut om du vill.',
+"form.mytime.billable" => 'debiterbar',
+"form.mytime.warn_tozero_rec" => 'denna tidsstämpel måste tas bort för att tidsperiden är låst',
+// Note to translators: the string below is missing and must be added and translated
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'skapa huvudkonto',
+"form.profile.edit_title" => 'ändra profilen',
+"form.profile.name" => 'namn',
+"form.profile.login" => 'logga in',
+
+// Note to translators: the strings below are missing and must be added and translated
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'personal',
+"form.people.createu_str" => 'skapa ny användare',
+"form.people.edit_str" => 'ändra användare',
+"form.people.del_str" => 'ta bort användare',
+"form.people.th.name" => 'namn',
+"form.people.th.login" => 'logga in',
+"form.people.th.role" => 'roll',
+"form.people.th.edit" => 'redigera',
+"form.people.th.del" => 'ta bort',
+"form.people.th.status" => 'status',
+"form.people.th.project" => 'projekt',
+"form.people.th.rate" => 'timpris',
+"form.people.manager" => 'chef',
+"form.people.comanager" => 'vicechef',
+"form.people.empl" => 'användare',
+"form.people.name" => 'namn',
+"form.people.login" => 'logga in',
+
+"form.people.rate" => 'standard timpris',
+"form.people.comanager" => 'vice-chef',
+"form.people.projects" => 'projekt',
+
+// projects form attributes
+"form.project.proj_title" => 'projekt',
+"form.project.edit_str" => 'ändra projekt',
+"form.project.add_str" => 'lägg till nytt projekt',
+"form.project.del_str" => 'ta bort projekt',
+"form.project.th.name" => 'namn',
+"form.project.th.edit" => 'redigera',
+"form.project.th.del" => 'ta bort',
+"form.project.name" => 'namn',
+
+// activities form attributes
+"form.activity.act_title" => 'aktiviteter',
+"form.activity.add_title" => 'lägg till aktivitet',
+"form.activity.edit_str" => 'ändra aktivitet',
+"form.activity.del_str" => 'ta bort aktivitet',
+"form.activity.name" => 'namn',
+"form.activity.project" => 'projekt',
+"form.activity.th.name" => 'namn',
+"form.activity.th.project" => 'projekt',
+"form.activity.th.edit" => 'redigera',
+"form.activity.th.del" => 'ta bort',
+
+// report attributes
+"form.report.title" => 'rapporter',
+"form.report.from" => 'startdatum',
+"form.report.to" => 'slutdatum',
+"form.report.groupby_user" => 'användare',
+"form.report.groupby_project" => 'projekt',
+"form.report.groupby_activity" => 'aktivitet',
+"form.report.duration" => 'varktighet',
+"form.report.start" => 'start',
+"form.report.activity" => 'aktivitet',
+"form.report.show_idle" => 'visa passivitet',
+"form.report.finish" => 'avsluta',
+"form.report.note" => 'beskrivning',
+"form.report.project" => 'projekt',
+"form.report.totals_only" => 'endast totala',
+"form.report.total" => 'totala timmar',
+"form.report.th.empllist" => 'användare',
+"form.report.th.date" => 'datum',
+"form.report.th.project" => 'projekt',
+"form.report.th.activity" => 'aktivitet',
+"form.report.th.start" => 'start',
+"form.report.th.finish" => 'slut',
+"form.report.th.duration" => 'varaktiget',
+"form.report.th.note" => 'beskrivning',
+
+// mail form attributes
+"form.mail.from" => 'från',
+"form.mail.to" => 'till',
+"form.mail.cc" => 'cc',
+"form.mail.subject" => 'ämne',
+"form.mail.comment" => 'kommentarer',
+"form.mail.above" => 'skicka rapporten med e-post',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>meddelandet skickat</b>',
+
+// invoice attributes
+"form.invoice.title" => 'faktura',
+"form.invoice.caption" => 'faktura',
+"form.invoice.above" => 'ytterligare information för fakturan',
+"form.invoice.select_cust" => 'välj kund',
+"form.invoice.fillform" => 'fyll i fälten',
+"form.invoice.date" => 'datum',
+"form.invoice.number" => 'fakturanummer',
+"form.invoice.tax" => 'moms',
+"form.invoice.daily_subtotals" => 'daglig subtotal',
+"form.invoice.yourcoo" => 'ditt namn<br> och adress',
+"form.invoice.custcoo" => 'kundens namn<br> och adress',
+"form.invoice.comment" => 'kommentarer ',
+"form.invoice.th.username" => 'person',
+"form.invoice.th.time" => 'timmar',
+"form.invoice.th.rate" => 'timpris',
+"form.invoice.th.summ" => 'antal',
+"form.invoice.subtotal" => 'subtotal',
+"form.invoice.customer" => 'kund',
+"form.invoice.mailinv_above" => 'skicka fakturan med e-post',
+"form.invoice.sending_str" => '<b>fakturan skickades</b>',
+
+"form.migration.zip" => 'komprimering',
+"form.migration.file" => 'välj fil',
+"form.migration.import.title" => 'importera information',
+"form.migration.import.success" => 'importen lyckades',
+"form.migration.import.text" => 'importera team information från en xml fil',
+"form.migration.export.title" => 'exportera information',
+"form.migration.export.success" => 'exporten lyckades',
+"form.migration.export.text" => 'du kan exportera all team data i en xmlfil. detta kan vara användbar om du migrerar till en egen server.',
+// Note to translators: the string below is missing and must be added and translated
+// "form.migration.compression.none" => 'none',
+"form.migration.compression.gzip" => 'gzip',
+"form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'kunder',
+"form.client.add_title" => 'lägg till kund',
+"form.client.edit_title" => 'ändra kund',
+"form.client.del_title" => 'ta bort kund',
+"form.client.th.name" => 'namn',
+"form.client.th.edit" => 'redigera',
+"form.client.th.del" => 'ta bort',
+"form.client.name" => 'namn',
+"form.client.tax" => 'moms',
+"form.client.daily_subtotals" => 'daglig subtotal',
+"form.client.yourcoo" => 'ditt namn<br> och adress på fakturan',
+"form.client.custcoo" => 'adress',
+"form.client.comment" => 'kommentarer ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'glömt lösenord?',
+"forward.edit" => 'redigera',
+"forward.delete" => 'ta bort',
+"forward.tocsvfile" => 'exportera data till .csv fil',
+"forward.toxmlfile" => 'exportera data till .xml fil',
+"forward.geninvoice" => 'skapa faktura',
+"forward.change" => 'justera kunder',
+
+// strings inside contols on forms
+"controls.select.project" => '--- välj projekt ---',
+"controls.select.activity" => '--- välj aktivitet ---',
+"controls.select.client" => '--- välj kund ---',
+"controls.project_bind" => '--- alla ---',
+"controls.all" => '--- alla ---',
+"controls.notbind" => '--- nej ---',
+"controls.per_tm" => 'denna månad',
+"controls.per_lm" => 'förra månad',
+"controls.per_tw" => 'denna vecka',
+"controls.per_lw" => 'förra vecka',
+"controls.per_td" => 'denna dag',
+"controls.per_at" => 'hela perioden',
+// Note to translators: the string below is missing and must be translated and added
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- välj tidsperiod ---',
+"controls.sel_groupby" => '--- ingen gruppering ---',
+"controls.inc_billable" => 'debiterbar',
+"controls.inc_nbillable" => 'icke debiterbar',
+// Note to translators: the string below is missing and must be translated and added
+// "controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'användarens aktiviteter',
+// Note to translators: the string below is missing and must be translated and added
+// "label.chart.title2" => 'projects for user',
+"label.chart.period" => 'graf för perioden',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>genom %s</b>',
+"label.pminfo" => ' (chef)',
+"label.pcminfo" => ' (vice-chef)',
+"label.painfo" => ' (administratör)',
+"label.time_noentry" => 'tomt',
+"label.today" => 'idag',
+"label.req_fields" => '* obligatoriska fält',
+"label.sel_project" => 'välj projekt',
+"label.sel_activity" => 'välj aktivitet',
+"label.sel_tp" => 'välj tidsperiod',
+"label.set_tp" => 'eller ange datum',
+"label.fields" => 'visa fält',
+"label.group_title" => 'grupperad som',
+"label.include_title" => 'inkludera information',
+"label.inv_str" => 'faktura',
+"label.set_empl" => 'välj användare',
+"label.sel_all" => 'markera alla',
+"label.sel_none" => 'avmarkera alla',
+"label.or" => 'eller',
+"label.disable" => 'deaktiverad',
+"label.enable" => 'aktiverad',
+"label.filter" => 'filter',
+"label.timeweek" => 'veckovis total',
+// Note to translators: the strings below are missing and must be added and translated
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/tr.lang.php b/WEB-INF/resources/tr.lang.php
new file mode 100644 (file)
index 0000000..5b45df0
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = 'Türkçe';
+$i18n_months = array('ocak', 'şubat', 'mart', 'nisan', 'mayis', 'haziran', 'temmuz', 'ağustos', 'eylük', 'ekim', 'kasım', 'aralık');
+$i18n_weekdays = array('pazar', 'pazartesi', 'salı', 'çarşamba', 'perşembe', 'cuma', 'cumartesi');
+$i18n_weekdays_short = array('p', 'pt', 's', 'ç', 'p', 'c', 'ct');
+// format mm/dd
+$i18n_holidays = array('01/01', '04/23', '05/01', '05/19', '08/30', '09/20', '09/21', '09/22', '10/29', '11/27', '11/28', '11/29', '11/30');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => 'giriş',
+'menu.logout' => 'çıkış',
+'menu.feedback' => 'geri bildirim',
+'menu.help' => 'yardım',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => 'yeni yönetici hesabı yarat',
+'menu.edit_profile' => 'profili düzenle',
+'menu.my_time' => 'zamanım',
+'menu.reports' => 'raporlar',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => 'projeler',
+'menu.activities' => 'faaliyetler',
+'menu.people' => 'insanlar',
+'menu.teams' => 'ekipler',
+// Note to translators: menu.export needs to be translated.
+// 'menu.export' => 'export',
+'menu.clients' => 'müşteriler',
+// Note to translators: menu.options needs to be translated.
+// 'menu.options' => 'options',
+'menu.admin' => 'yönetici',
+
+// error strings
+'error.db' => 'veritabanı hatası',
+'error.field' => 'hatalı veri "{0}"',
+'error.empty' => 'alan "{0}" boştur',
+'error.not_equal' => 'alan "{0}" "{1}" alanıyla aynı değildir',
+'error.interval' => 'hatalı aralık',
+'error.project' => 'proje seç',
+'error.activity' => 'faaliyet seç',
+'error.auth' => 'hatalı kullanıcı adı veya parola',
+// Note to translators: this string needs to be translated.
+// 'error.user_exists' => 'user with this login already exists',
+'error.project_exists' => 'bu isimde proje zaten vardır',
+'error.activity_exists' => 'bu isimli faaliyet zaten vardır',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+// Note to translators: this string needs to be properly translated (e-mail replaced with login).
+// 'error.no_login' => 'bu e-posta adresini kullanan kullanıcı yoktur',
+'error.upload' => 'dosya yükleme hatası',
+// Note to translators: the strings below are missing and must be translated and added
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+// 'error.mail_send' => 'error sending mail',
+// 'error.no_email' => 'no email associated with this login',
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => 'giriş',
+'button.now' => 'şimdi',
+// 'button.set' => 'ayarla',
+'button.save' => 'kaydet',
+'button.delete' => 'sil',
+'button.cancel' => 'iptal',
+'button.submit' => 'gönder',
+'button.add_user' => 'kullanıcı ekle',
+'button.add_project' => 'proje ekle',
+'button.add_activity' => 'faaliyet ekle',
+'button.add_client' => 'müşteri ekle',
+'button.add' => 'ekle',
+'button.generate' => 'yarat',
+// Note to translators: button.reset_password needs an improved translation.
+'button.reset_password' => 'git',
+'button.send' => 'gönder',
+'button.send_by_email' => 'e-posta ile gönder',
+'button.save_as_new' => 'yeni olarak kaydet',
+'button.create_team' => 'ekip yarat',
+// TODO: improve translation of button.export
+// 'button.export' => 'ekibi dılarıya aktar',
+'button.import' => 'ekibi içeri aktar',
+'button.apply' => 'uygula',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => 'para birimi',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => 'parola',
+'label.confirm_password' => 'parolayı tekrala',
+'label.email' => 'e-posta',
+'label.total' => 'toplam',
+
+"form.filter.project" => 'proje',
+"form.filter.filter" => 'sık kullanılan rapor',
+"form.filter.filter_new" => 'sık kullanılan olarak kaydet',
+// Note to translators: the string below is missing and must be translated and added
+// "form.filter.filter_confirm_delete" => 'are you sure you want to delete this favorite report?',
+
+// login form attributes
+"form.login.title" => 'giriş',
+// Note to translators: "form.login.login" => 'e-posta', // e-mail has been changed to login
+
+// password reminder form attributes
+"form.fpass.title" => 'parolayı sıfırla',
+// Note to translators: "form.fpass.login" => 'e-posta', // e-mail has been changed to login
+"form.fpass.send_pass_str" => 'parola sıfırlama talebi yollandı',
+"form.fpass.send_pass_subj" => 'Anuko Time Tracker parola sıfırlama talebi',
+// Note to translators: the ending of this string needs to be translated.
+"form.fpass.send_pass_body" => "Sayın Kullanıcı,\n\nBirisi, muhtemelen siz, Anuko Time Tracker parolanızın sıfırlanmasını istedi. Parolanızı sıfırlamak isterseniz lütfen bu bağlantıyı takip edin.\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "parolanızı sıfırlamak için lütfen parolanızı yazın ve kaydedin",
+
+// administrator form
+"form.admin.title" => 'yönetici',
+"form.admin.duty_text" => 'yeni bir ekip yönetimi hesabı yaratarak yeni bir ekibi yaratın.<br>ayrıca başka bir Anuko Time Tracker sunucusundan ekip bilgilerini bir xml dosyasından aktarabilirsiniz (e-posta çakışmalarına izin verilmemekte).',
+
+"form.admin.change_pass" => 'yönetici hesabın parolasını değiştir',
+"form.admin.profile.title" => 'ekipler',
+"form.admin.profile.noprofiles" => 'veritabanınız boş. yeni bir ekip yaratmak için yönetici olarak giriş yapın.',
+"form.admin.profile.comment" => 'ekibi sil',
+"form.admin.profile.th.id" => 'id',
+"form.admin.profile.th.name" => 'isim',
+"form.admin.profile.th.edit" => 'düzenle',
+"form.admin.profile.th.del" => 'sil',
+"form.admin.profile.th.active" => 'aktif',
+"form.admin.lock.period" => 'günler olarak kilit aralığı',
+// Note to translators: the strings below are missing and must be translated and added
+// "form.admin.options" => 'options',
+// "form.admin.lang_default" => 'site default language',
+// "form.admin.custom_date_format" => "date format",
+// "form.admin.custom_time_format" => "time format",
+// "form.admin.start_week" => "first day of week",
+
+// my time form attributes
+"form.mytime.title" => 'zamanım',
+"form.mytime.edit_title" => 'zaman kaydını düzenliyor',
+"form.mytime.del_str" => 'zaman kaydını siliyor',
+"form.mytime.time_form" => ' (ss:dd)',
+"form.mytime.date" => 'tarih',
+"form.mytime.project" => 'proje',
+"form.mytime.activity" => 'faaliyet',
+"form.mytime.start" => 'başlat',
+"form.mytime.finish" => 'tamamla',
+"form.mytime.duration" => 'süre',
+"form.mytime.note" => 'not',
+"form.mytime.behalf" => 'kişiye yönelik günlük çalışma',
+"form.mytime.daily" => 'günlük çalışma',
+"form.mytime.total" => 'toplam saat: ',
+"form.mytime.th.project" => 'proje',
+"form.mytime.th.activity" => 'faaliyet',
+"form.mytime.th.start" => 'başlat',
+"form.mytime.th.finish" => 'tamamla',
+"form.mytime.th.duration" => 'süre',
+"form.mytime.th.note" => 'not',
+"form.mytime.th.edit" => 'düzenle',
+"form.mytime.th.delete" => 'sil',
+"form.mytime.del_yes" => 'zaman kaydı başarıyla silindi',
+"form.mytime.no_finished_rec" => 'bu kayıt sadece başlangıç zamanıyla silindi. bu hata değildir. gerekirse çıkış yapın.',
+"form.mytime.billable" => 'faturalandırılabilir',
+"form.mytime.warn_tozero_rec" => 'bu zaman kaydı silinmeli çünkü zaman aralığı kilitli',
+// Note to translators: the string below is missing and must be translated and added
+// "form.mytime.uncompleted" => 'uncompleted',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => 'yeni yönetici hesabı yarat',
+"form.profile.edit_title" => 'profili düzenliyor',
+"form.profile.name" => 'isim',
+// Note to translators: "form.profile.login" => 'e-posta', // email has been changed to login
+
+// Note to translators: the string below is missing and must be translated and added
+// "form.profile.showchart" => 'show pie charts',
+// "form.profile.lang" => 'language',
+// "form.profile.custom_date_format" => "date format",
+// "form.profile.custom_time_format" => "time format",
+// "form.profile.default_format" => "(default)",
+// "form.profile.start_week" => "first day of week",
+
+// people form attributes
+"form.people.ppl_str" => 'insanlar',
+"form.people.createu_str" => 'yeni kullanıcı yarat',
+"form.people.edit_str" => 'kullanıcı düzenleniyor',
+"form.people.del_str" => 'kullanıcı siliniyor',
+"form.people.th.name" => 'isim',
+// Note to translators: "form.people.th.login" => 'e-posta', // email has been changed to login
+"form.people.th.role" => 'rol',
+"form.people.th.edit" => 'düzenle',
+"form.people.th.del" => 'sil',
+"form.people.th.status" => 'durum',
+"form.people.th.project" => 'proje',
+"form.people.th.rate" => 'tarife',
+"form.people.manager" => 'yönetici',
+"form.people.comanager" => 'yardımcı yönetici',
+"form.people.empl" => 'kullanıcı',
+"form.people.name" => 'isim',
+// Note to translators: "form.people.login" => 'e-posta', // email has been changed to login
+
+"form.people.rate" => 'varsayılan saat ücreti',
+"form.people.comanager" => 'yardımcı yönetici',
+"form.people.projects" => 'projeler',
+
+// projects form attributes
+"form.project.proj_title" => 'projeler',
+"form.project.edit_str" => 'proje düzenleniyor',
+"form.project.add_str" => 'yeni proje ekleniyor',
+"form.project.del_str" => 'proje siliniyor',
+"form.project.th.name" => 'isim',
+"form.project.th.edit" => 'düzenle',
+"form.project.th.del" => 'sil',
+"form.project.name" => 'isim',
+
+// activities form attributes
+"form.activity.act_title" => 'faaliyetler',
+"form.activity.add_title" => 'yeni faaliyetler ekleniyor',
+"form.activity.edit_str" => 'faaliyetler düzenleniyor',
+"form.activity.del_str" => 'faaliyetler siliniyor',
+"form.activity.name" => 'isim',
+"form.activity.project" => 'proje',
+"form.activity.th.name" => 'isim',
+"form.activity.th.project" => 'proje',
+"form.activity.th.edit" => 'düzenle',
+"form.activity.th.del" => 'sil',
+
+// report attributes
+"form.report.title" => 'raporlar',
+"form.report.from" => 'başlangıç tarihi',
+"form.report.to" => 'son tarihi',
+"form.report.groupby_user" => 'kullanıcı',
+"form.report.groupby_project" => 'proje',
+"form.report.groupby_activity" => 'faaliyet',
+"form.report.duration" => 'süre',
+"form.report.start" => 'başlangıç',
+"form.report.activity" => 'faaliyet',
+"form.report.show_idle" => 'durağanı göster',
+"form.report.finish" => 'son',
+"form.report.note" => 'not',
+"form.report.project" => 'proje',
+"form.report.totals_only" => 'sadece toplamlar',
+"form.report.total" => 'saat toplamı',
+"form.report.th.empllist" => 'kullanıcı',
+"form.report.th.date" => 'tarih',
+"form.report.th.project" => 'proje',
+"form.report.th.activity" => 'faaliyet',
+"form.report.th.start" => 'başlangıç',
+"form.report.th.finish" => 'son',
+"form.report.th.duration" => 'süre',
+"form.report.th.note" => 'not',
+
+// mail form attributes
+"form.mail.from" => 'kimden',
+"form.mail.to" => 'kime',
+"form.mail.cc" => 'bilgi',
+"form.mail.subject" => 'konu',
+"form.mail.comment" => 'yorum',
+"form.mail.above" => 'bu raporu e-posta ile yolla',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>ileti yollandı</b>',
+
+// invoice attributes
+"form.invoice.title" => 'fatura',
+"form.invoice.caption" => 'fatura',
+"form.invoice.above" => 'fatura için ek bilgi',
+"form.invoice.select_cust" => 'müşteri seç',
+"form.invoice.fillform" => 'alanları doldur',
+"form.invoice.date" => 'tarih',
+"form.invoice.number" => 'fatura numarası',
+"form.invoice.tax" => 'vergi',
+"form.invoice.daily_subtotals" => 'günlük alt toplamları',
+"form.invoice.yourcoo" => 'isminiz<br> ve adresiniz',
+"form.invoice.custcoo" => 'müşterinin ismi<br> ve adresi',
+"form.invoice.comment" => 'yorum ',
+"form.invoice.th.username" => 'kişi',
+"form.invoice.th.time" => 'saatler',
+"form.invoice.th.rate" => 'tarife',
+"form.invoice.th.summ" => 'tutar',
+"form.invoice.subtotal" => 'alt toplamı',
+"form.invoice.customer" => 'müşteri',
+"form.invoice.mailinv_above" => 'bu faturayı e-posta ile yolla',
+"form.invoice.sending_str" => '<b>fatura yollandı</b>',
+
+"form.migration.zip" => 'sıkıştırma',
+"form.migration.file" => 'dosya seç',
+"form.migration.import.title" => 'veri içe aktar',
+"form.migration.import.success" => 'içe aktarma başarıyla tamamlandı',
+"form.migration.import.text" => 'ekip bilgileri bir xml dosyasından içe aktar',
+"form.migration.export.title" => 'dışarı aktar',
+"form.migration.export.success" => 'dışarı aktarma başarıyla tamamlandı',
+"form.migration.export.text" => 'tüm ekip bilgilerinizi bir xml dosyasına aktarabilirsiniz. bu, kendi sunucunuza bilgi aktarmak istediğinizde faydalı olabilir.',
+// Note to translators: the strings below are missing and must be added and translated
+// "form.migration.compression.none" => 'none',
+// "form.migration.compression.gzip" => 'gzip',
+// "form.migration.compression.bzip" => 'bzip',
+
+"form.client.title" => 'müşteriler',
+"form.client.add_title" => 'müşteri ekle',
+"form.client.edit_title" => 'müşteriyi düzenle',
+"form.client.del_title" => 'müşteriyi sil',
+"form.client.th.name" => 'isim',
+"form.client.th.edit" => 'düzenle',
+"form.client.th.del" => 'sil',
+"form.client.name" => 'isim',
+"form.client.tax" => 'vergi',
+"form.client.daily_subtotals" => 'günlük alt toplamları',
+"form.client.yourcoo" => 'faturada isminiz<br> ve adresiniz',
+"form.client.custcoo" => 'adres',
+"form.client.comment" => 'yorum ',
+
+// miscellaneous strings
+"forward.forgot_password" => 'parolanızı unuttunuz mu?',
+"forward.edit" => 'düzenle',
+"forward.delete" => 'sil',
+"forward.tocsvfile" => 'bilgileri .csv dosyasına aktar',
+"forward.toxmlfile" => 'bilgileri .xml dosyasına aktar',
+"forward.geninvoice" => 'fatura yarat',
+"forward.change" => 'müşterileri düzenle',
+
+// strings inside contols on forms
+"controls.select.project" => '--- proje seç ---',
+"controls.select.activity" => '--- faaliyet seç ---',
+"controls.select.client" => '--- müşteri seç ---',
+"controls.project_bind" => '--- tümü ---',
+"controls.all" => '--- tümü ---',
+"controls.notbind" => '--- hiç ---',
+"controls.per_tm" => 'bu ay',
+"controls.per_lm" => 'geçen ay',
+"controls.per_tw" => 'bu hafta',
+"controls.per_lw" => 'geçen hafta',
+"controls.per_td" => 'bugün',
+"controls.per_at" => 'tüm zamanlar',
+// Note to translators: the string below is missing and must be added and translated
+// "controls.per_ty" => 'this year',
+"controls.sel_period" => '--- zaman dönemi seç ---',
+"controls.sel_groupby" => '--- gruplama yok ---',
+"controls.inc_billable" => 'faturalandırılabilir',
+"controls.inc_nbillable" => 'faturalandırılamaz',
+// Note to translators: the string below is missing and must be added and translated
+// "controls.default" => '--- default ---',
+
+// labels
+"label.chart.title1" => 'kullanıcı için faaliyetler',
+// Note to translators: the string below is missing and must be added and translated
+// "label.chart.title2" => 'projects for user',
+"label.chart.period" => 'dönem için grafik',
+
+"label.pinfo" => '%, %',
+"label.pinfo2" => '%',
+"label.pbehalf_info" => '% % <b>% adına</b>',
+"label.pminfo" => ' (yönetici)',
+"label.pcminfo" => ' (yardımcı yönetici)',
+"label.painfo" => ' (sistem yönetici)',
+"label.time_noentry" => 'giriş yok',
+"label.today" => 'bugün',
+"label.req_fields" => '* zorunlu bilgi',
+"label.sel_project" => 'proje seç',
+"label.sel_activity" => 'faaliyet seç',
+"label.sel_tp" => 'zaman aralığını seç',
+"label.set_tp" => 'ya da tarihleri belirle',
+"label.fields" => 'alanları göster',
+"label.group_title" => 'gruplandırma kıstası',
+"label.include_title" => 'kayıtları dahil et',
+"label.inv_str" => 'fatura',
+"label.set_empl" => 'kullanıcıları seç',
+"label.sel_all" => 'tümünü seç',
+"label.sel_none" => 'hiçbirini seçme',
+"label.or" => 'ya da',
+"label.disable" => 'devre dışı bırak',
+"label.enable" => 'devreye sok',
+"label.filter" => 'filtre',
+"label.timeweek" => 'haftalık toplam',
+// Note to translators: the strings below are missing and must be added and translated
+// "label.hrs" => 'hrs',
+// "label.errors" => 'errors',
+// "label.ldap_hint" => 'Type your <b>Windows login</b> and <b>password</b> in the fields below.',
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/zh-cn.lang.php b/WEB-INF/resources/zh-cn.lang.php
new file mode 100644 (file)
index 0000000..ae604cb
--- /dev/null
@@ -0,0 +1,405 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = '简体中文';
+$i18n_months = array('一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月');
+$i18n_weekdays = array('星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六');
+$i18n_weekdays_short = array('周日', '周一', '周二', '周三', '周四', '周五', '周六');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/02', '01/25', '01/26', '01/27', '01/28', '01/29', '01/30', '01/31', '05/01', '05/28', '05/29');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => '登录',
+'menu.logout' => '注销',
+'menu.feedback' => '反馈',
+'menu.help' => '帮助',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => '创建新管理账号',
+'menu.edit_profile' => '编辑简介',
+'menu.time' => '我的时间记录',
+'menu.reports' => '报告',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => '项目',
+'menu.activities' => '活动',
+'menu.people' => '人员',
+'menu.teams' => '团队',
+'menu.export' => '导出数据',
+'menu.clients' => '客户',
+'menu.options' => '选项',
+'menu.admin' => '管理',
+
+// error strings
+'error.db' => '数据库错误',
+'error.field' => '不正确的"{0}"数据',
+'error.empty' => '栏目"{0}"为空',
+'error.not_equal' => '栏目"{0}"不等于栏目"{1}"',
+'error.interval' => '不正确的间隔',
+'error.project' => '选择项目',
+'error.activity' => '选择活动',
+'error.auth' => '不正确的用户名或密码',
+'error.user_exists' => '该用户登录信息已经存在',
+'error.project_exists' => '该项目名称已经存在',
+'error.activity_exists' => '该活动名称已经存在',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+'error.no_login' => '没有该登录信息的用户',
+'error.upload' => '上传文件出错',
+// Note to translators: string below must be translated.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => '发送邮件时出错',
+'error.no_email' => '没有电子邮件与该用户名关联',
+// Note to translators: strings below must be translated.
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => '登录',
+'button.now' => '当前时间',
+// 'button.set' => '设置',
+'button.save' => '保存',
+'button.delete' => '删除',
+'button.cancel' => '取消',
+'button.submit' => '提交',
+// TODO: check / improve translation of all button.add... strings.
+'button.add_user' => '添加新用户',
+'button.add_project' => '添加新项目',
+'button.add_activity' => '添加新活动',
+'button.add_client' => '添加新客户',
+'button.add' => '添加',
+'button.generate' => '创建',
+// Note to translators: button.reset_password needs to be translated.
+// "button.reset_password" => 'reset password',
+'button.send' => '发送',
+'button.send_by_email' => '通过邮件发送',
+'button.save_as_new' => '另存为',
+// TODO: improve translation of button.create_team
+'button.create_team' => '创建新团队',
+'button.export' => '导出团队信息',
+'button.import' => '导入团队信息',
+'button.apply' => '应用',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => '货币',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => '密码',
+'label.confirm_password' => '确认密码',
+'label.email' => '电子邮件',
+'label.total' => '总计',
+
+"form.filter.project" => '项目',
+"form.filter.filter" => '收藏的报告',
+"form.filter.filter_new" => '保存到我的收藏夹',
+"form.filter.filter_confirm_delete" => '您确认要删除收藏的这个报告吗?',
+
+// login form attributes
+"form.login.title" => '登录',
+"form.login.login" => '登录',
+
+// password reminder form attributes
+"form.fpass.title" => '重设密码',
+"form.fpass.login" => '登录',
+"form.fpass.send_pass_str" => '密码重设请求已经发送',
+"form.fpass.send_pass_subj" => 'Anuko时间追踪器密码重设请求',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "亲爱的用户,\n\n有人,也可能是您自己,请求重新设置您的Anuko时间追踪器密码。如果您希望重设您的密码,请访问下面的连结:\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "要重设密码,请输入新密码并点击保存按钮",
+
+// administrator form
+"form.admin.title" => '管理员',
+"form.admin.duty_text" => '通过创建新的团队经理账号来创建新团队。<br>您也可以从其它的Anuko时间追踪器服务器的xml文件导入团队数据(登录信息不能发生冲突)。',
+
+"form.admin.change_pass" => '修改管理员账号的密码',
+"form.admin.profile.title" => '团队',
+"form.admin.profile.noprofiles" => '您的数据库没有任何记录。请以管理员身份登录并创建一个新团队。',
+"form.admin.profile.comment" => '删除团队',
+"form.admin.profile.th.id" => 'ID号',
+"form.admin.profile.th.name" => '姓名',
+"form.admin.profile.th.edit" => '编辑',
+"form.admin.profile.th.del" => '删除',
+"form.admin.profile.th.active" => '启用',
+"form.admin.lock.period" => '锁定天数间隔',
+"form.admin.options" => '选项',
+"form.admin.lang_default" => '网站默认语言',
+"form.admin.lang_browser_default" => '(默认浏览器)',
+"form.admin.custom_date_format" => "日期格式",
+"form.admin.custom_time_format" => "时间格式",
+"form.admin.start_week" => "每周的第一天",
+
+// my time form attributes
+"form.mytime.title" => '我的时间记录',
+"form.mytime.edit_title" => '编辑时间记录',
+"form.mytime.del_str" => '删除时间记录',
+"form.mytime.time_form" => ' (时:分)',
+"form.mytime.date" => '日期',
+"form.mytime.project" => '项目',
+"form.mytime.activity" => '活动',
+"form.mytime.start" => '开始',
+"form.mytime.finish" => '结束',
+"form.mytime.duration" => '持续时间',
+"form.mytime.note" => '备注',
+"form.mytime.behalf" => '每日工作,人员:',
+"form.mytime.daily" => '每日工作',
+"form.mytime.total" => '总小时数: ',
+"form.mytime.th.project" => '项目',
+"form.mytime.th.activity" => '活动',
+"form.mytime.th.start" => '开始',
+"form.mytime.th.finish" => '结束',
+"form.mytime.th.duration" => '持续时间',
+"form.mytime.th.note" => '备注',
+"form.mytime.th.edit" => '编辑',
+"form.mytime.th.delete" => '删除',
+"form.mytime.del_yes" => '成功删除时间记录',
+"form.mytime.no_finished_rec" => '该记录只保存了开始时间。这不是错误。如果需要,请注销。',
+"form.mytime.billable" => '计费时间',
+"form.mytime.warn_tozero_rec" => '由于这段时间是锁定的,该时间记录必须删除',
+"form.mytime.uncompleted" => '未完成',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => '新建管理账号',
+"form.profile.edit_title" => '编辑简介',
+"form.profile.name" => '名字',
+"form.profile.login" => '登录',
+
+"form.profile.showchart" => '显示饼状图',
+"form.profile.lang" => '语言',
+"form.profile.lang_browser_default" => '(默认浏览器)',
+"form.profile.custom_date_format" => "日期格式",
+"form.profile.custom_time_format" => "时间格式",
+"form.profile.default_format" => "(默认)",
+"form.profile.start_week" => "每周的第一天",
+
+// people form attributes
+"form.people.ppl_str" => '人员',
+"form.people.createu_str" => '新建用户',
+"form.people.edit_str" => '编辑用户',
+"form.people.del_str" => '删除用户',
+"form.people.th.name" => '姓名',
+"form.people.th.login" => '登录',
+"form.people.th.role" => '角色',
+"form.people.th.edit" => '编辑',
+"form.people.th.del" => '删除',
+"form.people.th.status" => '状态',
+"form.people.th.project" => '项目',
+"form.people.th.rate" => '费率',
+"form.people.manager" => '经理',
+"form.people.comanager" => '合作经理人',
+"form.people.empl" => '用户',
+"form.people.name" => '姓名',
+"form.people.login" => '登录',
+
+"form.people.rate" => '默认小时收费',
+"form.people.comanager" => '合作经理人',
+"form.people.projects" => '项目',
+
+// projects form attributes
+"form.project.proj_title" => '项目',
+"form.project.edit_str" => '编辑项目',
+"form.project.add_str" => '添加新项目',
+"form.project.del_str" => '删除项目',
+"form.project.th.name" => '名称',
+"form.project.th.edit" => '编辑',
+"form.project.th.del" => '删除',
+"form.project.name" => '名称',
+
+// activities form attributes
+"form.activity.act_title" => '活动',
+"form.activity.add_title" => '新建活动',
+"form.activity.edit_str" => '编辑活动',
+"form.activity.del_str" => '删除活动',
+"form.activity.name" => '名称',
+"form.activity.project" => '项目',
+"form.activity.th.name" => '名称',
+"form.activity.th.project" => '项目',
+"form.activity.th.edit" => '编辑',
+"form.activity.th.del" => '删除',
+
+// report attributes
+"form.report.title" => '报告',
+"form.report.from" => '开始日期',
+"form.report.to" => '结束日期',
+"form.report.groupby_user" => '用户',
+"form.report.groupby_project" => '项目',
+"form.report.groupby_activity" => '活动',
+"form.report.duration" => '持续时间',
+"form.report.start" => '开始',
+"form.report.activity" => '活动',
+"form.report.show_idle" => '显示空闲',
+"form.report.finish" => '结束',
+"form.report.note" => '备注',
+"form.report.project" => '项目',
+"form.report.totals_only" => '仅仅今天',
+"form.report.total" => '总计时间',
+"form.report.th.empllist" => '用户',
+"form.report.th.date" => '日期',
+"form.report.th.project" => '项目',
+"form.report.th.activity" => '活动',
+"form.report.th.start" => '开始',
+"form.report.th.finish" => '结束',
+"form.report.th.duration" => '持续时间',
+"form.report.th.note" => '备注',
+
+// mail form attributes
+"form.mail.from" => '从',
+"form.mail.to" => '到',
+"form.mail.cc" => '抄送',
+"form.mail.subject" => '主题',
+"form.mail.comment" => '留言',
+"form.mail.above" => '通过电子邮件发送该报告',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>消息已发送</b>',
+
+// invoice attributes
+"form.invoice.title" => '发票',
+"form.invoice.caption" => '发票',
+"form.invoice.above" => '发票附加信息',
+"form.invoice.select_cust" => '选择客户',
+"form.invoice.fillform" => '填写该栏目',
+"form.invoice.date" => '日期',
+"form.invoice.number" => '发票号码',
+"form.invoice.tax" => '税',
+"form.invoice.daily_subtotals" => '每日总计',
+"form.invoice.yourcoo" => '您的姓名<br>地址',
+"form.invoice.custcoo" => '客户姓名<br>地址',
+"form.invoice.comment" => '留言',
+"form.invoice.th.username" => '收费人',
+"form.invoice.th.time" => '小时数',
+"form.invoice.th.rate" => '费率',
+"form.invoice.th.summ" => '账号',
+"form.invoice.subtotal" => '共计',
+"form.invoice.customer" => '客户',
+"form.invoice.mailinv_above" => '通过电子邮件发送此发票',
+"form.invoice.sending_str" => '<b>发票已送出</b>',
+
+"form.migration.zip" => '压缩',
+"form.migration.file" => '选择档',
+"form.migration.import.title" => '导入数据',
+"form.migration.import.success" => '成功完成导入',
+"form.migration.import.text" => '从xml文件导入团队数据',
+"form.migration.export.title" => '导出数据',
+"form.migration.export.success" => '导出成功',
+"form.migration.export.text" => '您可以将所有团队数据导出到xml文件。如果您要将数据转移到您自己的服务器,这项操作很有用。',
+"form.migration.compression.none" => '不压缩',
+"form.migration.compression.gzip" => 'gzip格式',
+"form.migration.compression.bzip" => 'bzip格式',
+
+"form.client.title" => '客户',
+"form.client.add_title" => '添加客户',
+"form.client.edit_title" => '编辑客户',
+"form.client.del_title" => '删除客户',
+"form.client.th.name" => '姓名',
+"form.client.th.edit" => '编辑',
+"form.client.th.del" => '删除',
+"form.client.name" => '姓名',
+"form.client.tax" => '税',
+"form.client.daily_subtotals" => '每日总计',
+"form.client.yourcoo" => '您在发票中的名字<br>和地址',
+"form.client.custcoo" => '地址',
+"form.client.comment" => '备注',
+
+// miscellaneous strings
+"forward.forgot_password" => '忘记密码?',
+"forward.edit" => '编辑',
+"forward.delete" => '删除',
+"forward.tocsvfile" => '将数据导出到.csv文件',
+"forward.toxmlfile" => '将数据导出到.xml文件',
+"forward.geninvoice" => '生成发票',
+"forward.change" => '客户设置',
+
+// strings inside contols on forms
+"controls.select.project" => '--- 选择项目 ---',
+"controls.select.activity" => '--- 选择活动 ---',
+"controls.select.client" => '--- 选择客户 ---',
+"controls.project_bind" => '--- 全部 ---',
+"controls.all" => '--- 全部 ---',
+"controls.notbind" => '--- 无 ---',
+"controls.per_tm" => '本月',
+"controls.per_lm" => '上个月',
+"controls.per_tw" => '本周',
+"controls.per_lw" => '上周',
+"controls.per_td" => '今天',
+"controls.per_at" => '全部时间',
+"controls.per_ty" => '今年',
+"controls.sel_period" => '--- 选择时间段 ---',
+"controls.sel_groupby" => '--- 没有分组 ---',
+"controls.inc_billable" => '计费时间',
+"controls.inc_nbillable" => '非计费时间',
+"controls.default" => '--- 默认 ---',
+
+// labels
+"label.chart.title1" => '活动用户',
+"label.chart.title2" => '项目用户',
+"label.chart.period" => '图表期限',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>代表%s</b>',
+"label.pminfo" => ' (经理)',
+"label.pcminfo" => ' (合作经理人)',
+"label.painfo" => ' (管理员)',
+"label.time_noentry" => '没有条目',
+"label.today" => '今天',
+"label.req_fields" => '* 必填栏目',
+"label.sel_project" => '选择项目',
+"label.sel_activity" => '选择活动',
+"label.sel_tp" => '选择时间段',
+"label.set_tp" => '或设定日期',
+"label.fields" => '显示栏目',
+"label.group_title" => '分组方式:',
+"label.include_title" => '包含记录',
+"label.inv_str" => '发票',
+"label.set_empl" => '选择用户',
+"label.sel_all" => '全部选择',
+"label.sel_none" => '全部不选',
+"label.or" => '或',
+"label.disable" => '禁用',
+"label.enable" => '启用',
+"label.filter" => '过滤器',
+"label.timeweek" => '一周总计',
+"label.hrs" => '小时',
+"label.errors" => '错误',
+"label.ldap_hint" => '在下面的栏目输入您的<b>Windows用户名</b>和<b>密码</b>。',
+// Note to translators: strings below must be translated.
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/resources/zh-tw.lang.php b/WEB-INF/resources/zh-tw.lang.php
new file mode 100644 (file)
index 0000000..ddc96d0
--- /dev/null
@@ -0,0 +1,405 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+// Note: escape apostrophes with THREE backslashes, like here:  choisir l\\\'option 
+// Other characters (such as double-quotes in http links, etc.) do not have to be escaped.
+
+$i18n_language = '簡體中文';
+$i18n_months = array('一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月');
+$i18n_weekdays = array('星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六');
+$i18n_weekdays_short = array('周日', '週一', '週二', '週三', '週四', '週五', '週六');
+// format mm/dd
+$i18n_holidays = array('01/01', '01/02', '01/25', '01/26', '01/27', '01/28', '01/29', '01/30', '04/04', '09/03');
+
+$i18n_key_words = array(
+
+// menu entries
+'menu.login' => '登錄',
+'menu.logout' => '登出',
+'menu.feedback' => '回饋',
+'menu.help' => '幫助',
+// Note to translators: menu.create_team needs a more accurate translation.
+'menu.create_team' => '創建新管理帳號',
+'menu.edit_profile' => '編輯簡介',
+'menu.time' => '我的時間記錄',
+'menu.reports' => '報告',
+// Note to translators: menu.charts needs to be translated.
+// 'menu.charts' => 'charts',
+'menu.projects' => '項目',
+'menu.activities' => '活動',
+'menu.people' => '人員',
+'menu.teams' => '團隊',
+'menu.export' => '輸出資料',
+'menu.clients' => '客戶',
+'menu.options' => '選項',
+'menu.admin' => '管理',
+
+// error strings
+'error.db' => '資料庫錯誤',
+'error.field' => '不正確的"{0}"資料',
+'error.empty' => '欄目"{0}"為空',
+'error.not_equal' => '欄目"{0}"不等於欄目"{1}"',
+'error.interval' => '不正確的間隔',
+'error.project' => '選擇項目',
+'error.activity' => '選擇活動',
+'error.auth' => '不正確的用戶名或密碼',
+'error.user_exists' => '該使用者登錄資訊已經存在',
+'error.project_exists' => '該專案名稱已經存在',
+'error.activity_exists' => '該活動名稱已經存在',
+// TODO: translate error.client_exists.
+// 'error.client_exists' => 'client with this name already exists',
+'error.no_login' => '沒有該登錄資訊的使用者',
+'error.upload' => '上傳文件出錯',
+// Note to translators: string below must be translated.
+// 'error.period_locked' => 'can\\\'t complete the operation. records older than a certain number of days cannot be created or modified. team manager defines this in the "Lock interval in days" value on the "Profile" page. set it to 0 to remove locking. <br><br>uncompleted records (with 0 or empty duration) can be deleted.',
+'error.mail_send' => '發送郵件時出錯',
+'error.no_email' => '沒有電子郵件與該用戶名關聯',
+// Note to translators: strings below must be translated.
+// 'error.uncompleted_exists' => 'uncompleted entry already exists. close or delete it.',
+// 'error.goto_uncompleted' => 'go to uncompleted entry.',
+
+// labels for various buttons
+'button.login' => '登錄',
+'button.now' => '當前時間',
+// 'button.set' => '設置',
+'button.save' => '保存',
+'button.delete' => '刪除',
+'button.cancel' => '取消',
+'button.submit' => '提交',
+// TODO: check / improve translation of all button.add... strings.
+'button.add_user' => '添加新用戶',
+'button.add_project' => '添加新項目',
+'button.add_activity' => '添加新活動',
+'button.add_client' => '添加新客戶',
+'button.add' => '添加',
+'button.generate' => '創建',
+// Note to translators: button.reset_password needs to be translated.
+// 'button.reset_password' => 'reset password',
+'button.send' => '發送',
+'button.send_by_email' => '通過郵件發送',
+'button.save_as_new' => '另存為',
+// TODO: improve translation of button.create_team
+'button.create_team' => '創建新團隊',
+'button.export' => '輸出團隊資訊',
+'button.import' => '輸入團隊資訊',
+'button.apply' => '應用',
+
+// labels for controls on various forms
+// TODO: translate label.team_name
+// 'label.team_name' => 'team name',
+'label.currency' => '貨幣',
+// TODO: translate label.manager_name and label.manager_login.
+// 'label.manager_name' => 'manager name',
+// 'label.manager_login' => 'manager login',
+'label.password' => '密碼',
+'label.confirm_password' => '確認密碼',
+'label.email' => '電子郵件',
+'label.total' => '總計',
+
+"form.filter.project" => '項目',
+"form.filter.filter" => '收藏的報告',
+"form.filter.filter_new" => '保存到我的存檔',
+"form.filter.filter_confirm_delete" => '您確認要刪除收藏的這個報告嗎?',
+
+// login form attributes
+"form.login.title" => '登錄',
+"form.login.login" => '登錄',
+
+// password reminder form attributes
+"form.fpass.title" => '重設密碼',
+"form.fpass.login" => '登錄',
+"form.fpass.send_pass_str" => '密碼重設請求已經發送',
+"form.fpass.send_pass_subj" => 'Anuko時間追蹤器密碼重設請求',
+// Note to translators: the ending of this string below needs to be translated.
+"form.fpass.send_pass_body" => "親愛的用戶,\n\n有人,也可能是您自己,請求重新設置您的Anuko時間追蹤器密碼。如果您希望重設您的密碼,請訪問下麵的連結:\n\n%s\n\nAnuko Time Tracker is a simple, easy to use, open source time tracking system. Visit https://www.anuko.com for more information.\n\n",
+"form.fpass.reset_comment" => "要重設密碼,請輸入新密碼並點擊保存按鈕",
+
+// administrator form
+"form.admin.title" => '管理員',
+"form.admin.duty_text" => '通過創建新的團隊經理帳號來創建新團隊。<br>您也可以從其它的Anuko時間追蹤器伺服器的xml檔導入團隊資料(登錄資訊不能發生衝突)。',
+
+"form.admin.change_pass" => '修改管理員帳號的密碼',
+"form.admin.profile.title" => '團隊',
+"form.admin.profile.noprofiles" => '您的資料庫沒有任何記錄。請以管理員身份登錄並創建一個新團隊。',
+"form.admin.profile.comment" => '刪除團隊',
+"form.admin.profile.th.id" => 'ID號',
+"form.admin.profile.th.name" => '姓名',
+"form.admin.profile.th.edit" => '編輯',
+"form.admin.profile.th.del" => '刪除',
+"form.admin.profile.th.active" => '啟動',
+"form.admin.lock.period" => '鎖定天數間隔',
+"form.admin.options" => '選項',
+"form.admin.lang_default" => '網站預設語言',
+"form.admin.lang_browser_default" => '(默認流覽器)',
+"form.admin.custom_date_format" => "日期格式",
+"form.admin.custom_time_format" => "時間格式",
+"form.admin.start_week" => "每週的第一天",
+
+// my time form attributes
+"form.mytime.title" => '我的時間記錄',
+"form.mytime.edit_title" => '編輯時間記錄',
+"form.mytime.del_str" => '刪除時間記錄',
+"form.mytime.time_form" => ' (時:分)',
+"form.mytime.date" => '日期',
+"form.mytime.project" => '項目',
+"form.mytime.activity" => '活動',
+"form.mytime.start" => '開始',
+"form.mytime.finish" => '結束',
+"form.mytime.duration" => '持續時間',
+"form.mytime.note" => '備註',
+"form.mytime.behalf" => '每日工作,執行人員:',
+"form.mytime.daily" => '每日工作',
+"form.mytime.total" => '總小時數: ',
+"form.mytime.th.project" => '項目',
+"form.mytime.th.activity" => '活動',
+"form.mytime.th.start" => '開始',
+"form.mytime.th.finish" => '結束',
+"form.mytime.th.duration" => '持續時間',
+"form.mytime.th.note" => '備註',
+"form.mytime.th.edit" => '編輯',
+"form.mytime.th.delete" => '刪除',
+"form.mytime.del_yes" => '成功刪除時間記錄',
+"form.mytime.no_finished_rec" => '該記錄只保存了開始時間。這不是錯誤。如果需要,請登出。',
+"form.mytime.billable" => '計費時間',
+"form.mytime.warn_tozero_rec" => '由於這段時間是鎖定的,該時間記錄必須刪除',
+"form.mytime.uncompleted" => '未完成',
+
+// profile form attributes
+// Note to translators: we need a more accurate translation of form.profile.create_title
+"form.profile.create_title" => '創建新管理帳號',
+"form.profile.edit_title" => '編輯簡介',
+"form.profile.name" => '名字',
+"form.profile.login" => '登錄',
+
+"form.profile.showchart" => '顯示餅狀圖',
+"form.profile.lang" => '語言',
+"form.profile.lang_browser_default" => '(默認流覽器)',
+"form.profile.custom_date_format" => "日期格式",
+"form.profile.custom_time_format" => "時間格式",
+"form.profile.default_format" => "(默認)",
+"form.profile.start_week" => "每週的第一天",
+
+// people form attributes
+"form.people.ppl_str" => '人員',
+"form.people.createu_str" => '新建用戶',
+"form.people.edit_str" => '編輯用戶',
+"form.people.del_str" => '刪除用戶',
+"form.people.th.name" => '姓名',
+"form.people.th.login" => '登錄',
+"form.people.th.role" => '角色',
+"form.people.th.edit" => '編輯',
+"form.people.th.del" => '刪除',
+"form.people.th.status" => '狀態',
+"form.people.th.project" => '項目',
+"form.people.th.rate" => '費率',
+"form.people.manager" => '經理',
+"form.people.comanager" => '合作經理人',
+"form.people.empl" => '用戶',
+"form.people.name" => '姓名',
+"form.people.login" => '登錄',
+
+"form.people.rate" => '默認小時收費',
+"form.people.comanager" => '合作經理人',
+"form.people.projects" => '項目',
+
+// projects form attributes
+"form.project.proj_title" => '項目',
+"form.project.edit_str" => '編輯專案',
+"form.project.add_str" => '添加新項目',
+"form.project.del_str" => '刪除項目',
+"form.project.th.name" => '名稱',
+"form.project.th.edit" => '編輯',
+"form.project.th.del" => '刪除',
+"form.project.name" => '名稱',
+
+// activities form attributes
+"form.activity.act_title" => '活動',
+"form.activity.add_title" => '新建活動',
+"form.activity.edit_str" => '編輯活動',
+"form.activity.del_str" => '刪除活動',
+"form.activity.name" => '名稱',
+"form.activity.project" => '項目',
+"form.activity.th.name" => '名稱',
+"form.activity.th.project" => '項目',
+"form.activity.th.edit" => '編輯',
+"form.activity.th.del" => '刪除',
+
+// report attributes
+"form.report.title" => '報告',
+"form.report.from" => '開始日期',
+"form.report.to" => '結束日期',
+"form.report.groupby_user" => '用戶',
+"form.report.groupby_project" => '項目',
+"form.report.groupby_activity" => '活動',
+"form.report.duration" => '持續時間',
+"form.report.start" => '開始',
+"form.report.activity" => '活動',
+"form.report.show_idle" => '顯示空閒',
+"form.report.finish" => '結束',
+"form.report.note" => '備註',
+"form.report.project" => '項目',
+"form.report.totals_only" => '僅僅今天',
+"form.report.total" => '總計時間',
+"form.report.th.empllist" => '用戶',
+"form.report.th.date" => '日期',
+"form.report.th.project" => '項目',
+"form.report.th.activity" => '活動',
+"form.report.th.start" => '開始',
+"form.report.th.finish" => '結束',
+"form.report.th.duration" => '持續時間',
+"form.report.th.note" => '備註',
+
+// mail form attributes
+"form.mail.from" => '從',
+"form.mail.to" => '到',
+"form.mail.cc" => '抄送',
+"form.mail.subject" => '主題',
+"form.mail.comment" => '留言',
+"form.mail.above" => '通過電子郵件發送該報告',
+// Note to translators: this string needs to be translated.
+// "form.mail.footer_str" => 'Anuko Time Tracker is a simple, easy to use, open source<br>time tracking system. Visit <a href="https://www.anuko.com">www.anuko.com</a> for more information.',
+"form.mail.sending_str" => '<b>消息已發送</b>',
+
+// invoice attributes
+"form.invoice.title" => '發票',
+"form.invoice.caption" => '發票',
+"form.invoice.above" => '發票附加資訊',
+"form.invoice.select_cust" => '選擇客戶',
+"form.invoice.fillform" => '填寫該欄目',
+"form.invoice.date" => '日期',
+"form.invoice.number" => '發票號碼',
+"form.invoice.tax" => '稅',
+"form.invoice.daily_subtotals" => '每日總計',
+"form.invoice.yourcoo" => '您的姓名<br>和地址',
+"form.invoice.custcoo" => '客戶姓名<br>和位址',
+"form.invoice.comment" => '留言',
+"form.invoice.th.username" => '收費人',
+"form.invoice.th.time" => '小時數',
+"form.invoice.th.rate" => '費率',
+"form.invoice.th.summ" => '帳號',
+"form.invoice.subtotal" => '共計',
+"form.invoice.customer" => '客戶',
+"form.invoice.mailinv_above" => '通過電子郵件發送此發票',
+"form.invoice.sending_str" => '<b>發票已送出</b>',
+
+"form.migration.zip" => '壓縮',
+"form.migration.file" => '選擇檔',
+"form.migration.import.title" => '導入數據',
+"form.migration.import.success" => '成功完成導入',
+"form.migration.import.text" => '從xml檔導入團隊資料',
+"form.migration.export.title" => '匯出數據',
+"form.migration.export.success" => '成功完成匯出',
+"form.migration.export.text" => '您可以將所有團隊資料匯出到xml檔。如果您要將資料轉移到您自己的伺服器,這項操作很有用。',
+"form.migration.compression.none" => '不压缩',
+"form.migration.compression.gzip" => 'gzip格式',
+"form.migration.compression.bzip" => 'bzip格式',
+
+"form.client.title" => '客戶',
+"form.client.add_title" => '添加客戶',
+"form.client.edit_title" => '編輯客戶',
+"form.client.del_title" => '刪除客戶',
+"form.client.th.name" => '姓名',
+"form.client.th.edit" => '編輯',
+"form.client.th.del" => '刪除',
+"form.client.name" => '姓名',
+"form.client.tax" => '稅',
+"form.client.daily_subtotals" => '每日總計',
+"form.client.yourcoo" => '您在發票中的名字<br>和地址',
+"form.client.custcoo" => '地址',
+"form.client.comment" => '備註',
+
+// miscellaneous strings
+"forward.forgot_password" => '忘記密碼?',
+"forward.edit" => '編輯',
+"forward.delete" => '刪除',
+"forward.tocsvfile" => '將資料輸出到.csv文件',
+"forward.toxmlfile" => '將資料輸出到.xml文件',
+"forward.geninvoice" => '生成發票',
+"forward.change" => '客戶設置',
+
+// strings inside contols on forms
+"controls.select.project" => '--- 選擇項目 ---',
+"controls.select.activity" => '--- 選擇活動 ---',
+"controls.select.client" => '--- 選擇客戶 ---',
+"controls.project_bind" => '--- 全部 ---',
+"controls.all" => '--- 全部 ---',
+"controls.notbind" => '--- 無 ---',
+"controls.per_tm" => '本月',
+"controls.per_lm" => '上個月',
+"controls.per_tw" => '本周',
+"controls.per_lw" => '上周',
+"controls.per_td" => '今天',
+"controls.per_at" => '全部時間',
+"controls.per_ty" => '今年',
+"controls.sel_period" => '--- 選擇時間段 ---',
+"controls.sel_groupby" => '--- 沒有分組 ---',
+"controls.inc_billable" => '計費時間',
+"controls.inc_nbillable" => '非計費時間',
+"controls.default" => '--- 默認 ---',
+
+// labels
+"label.chart.title1" => '活動用戶',
+"label.chart.title2" => '項目用戶',
+"label.chart.period" => '圖表期限',
+
+"label.pinfo" => '%s, %s',
+"label.pinfo2" => '%s',
+"label.pbehalf_info" => '%s %s <b>代表%s</b>',
+"label.pminfo" => ' (經理)',
+"label.pcminfo" => ' (合作經理人)',
+"label.painfo" => ' (管理員)',
+"label.time_noentry" => '沒有條目',
+"label.today" => '今天',
+"label.req_fields" => '* 必填欄目',
+"label.sel_project" => '選擇項目',
+"label.sel_activity" => '選擇活動',
+"label.sel_tp" => '選擇時間段',
+"label.set_tp" => '或設定日期',
+"label.fields" => '顯示欄目',
+"label.group_title" => '分組方式:',
+"label.include_title" => '包含記錄',
+"label.inv_str" => '發票',
+"label.set_empl" => '選擇用戶',
+"label.sel_all" => '全部選擇',
+"label.sel_none" => '全部不選',
+"label.or" => '或',
+"label.disable" => '禁用',
+"label.enable" => '啟用',
+"label.filter" => '篩檢程式',
+"label.timeweek" => '一周總計',
+"label.hrs" => '小時',
+"label.errors" => '錯誤',
+"label.ldap_hint" => '在下麵的欄目輸入您的<b>Windows用戶名</b>和<b>密碼</b>。',
+// Note to translators: string below must be translated.
+// "label.calendar_today" => 'today',
+// "label.calendar_close" => 'close',
+
+// login hello text
+// "login.hello.text" => "Anuko Time Tracker is a simple, easy to use, open source time tracking system.",
+);
+?>
\ No newline at end of file
diff --git a/WEB-INF/templates/access_denied.tpl b/WEB-INF/templates/access_denied.tpl
new file mode 100644 (file)
index 0000000..fa0c45f
--- /dev/null
@@ -0,0 +1 @@
+<!-- empty -->
\ No newline at end of file
diff --git a/WEB-INF/templates/admin_options.tpl b/WEB-INF/templates/admin_options.tpl
new file mode 100644 (file)
index 0000000..b97312c
--- /dev/null
@@ -0,0 +1,21 @@
+{$forms.optionsForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.password}:</td>
+          <td>{$forms.optionsForm.password1.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.confirm_password}:</td>
+          <td>{$forms.optionsForm.password2.control}</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.optionsForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.optionsForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/admin_team_add.tpl b/WEB-INF/templates/admin_team_add.tpl
new file mode 100644 (file)
index 0000000..4f60d89
--- /dev/null
@@ -0,0 +1,45 @@
+{$forms.teamForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right" nowrap>{$i18n.label.team_name}:</td>
+          <td>{$forms.teamForm.team_name.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_name} (*):</td>
+          <td>{$forms.teamForm.manager_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_login} (*):</td>
+          <td>{$forms.teamForm.manager_login.control}</td>
+        </tr>
+{if !$auth_external}
+        <tr>
+          <td align="right" nowrap>{$i18n.label.password} (*):</td>
+          <td>{$forms.teamForm.password1.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.confirm_password} (*):</td>
+          <td>{$forms.teamForm.password2.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td align="right" nowrap>{$i18n.label.email}:</td>
+          <td>{$forms.teamForm.manager_email.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td colspan="2">&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" height="50" align="center">{$forms.teamForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.teamForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/admin_team_delete.tpl b/WEB-INF/templates/admin_team_delete.tpl
new file mode 100644 (file)
index 0000000..e3cc3fa
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.teamForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$team_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.teamForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.teamForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.teamForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/admin_team_edit.tpl b/WEB-INF/templates/admin_team_edit.tpl
new file mode 100644 (file)
index 0000000..b754eb3
--- /dev/null
@@ -0,0 +1,45 @@
+{$forms.teamForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right" nowrap>{$i18n.label.team_name}:</td>
+          <td>{$forms.teamForm.team_name.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_name} (*):</td>
+          <td>{$forms.teamForm.manager_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_login} (*):</td>
+          <td>{$forms.teamForm.manager_login.control}</td>
+        </tr>
+{if !$auth_external}
+        <tr>
+          <td align="right" nowrap>{$i18n.label.password} (*):</td>
+          <td>{$forms.teamForm.password1.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.confirm_password} (*):</td>
+          <td>{$forms.teamForm.password2.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td align="right" nowrap>{$i18n.label.email}:</td>
+          <td>{$forms.teamForm.manager_email.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td colspan="2">&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" height="50" align="center">{$forms.teamForm.btn_save.control}&nbsp;{$forms.teamForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.teamForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/admin_teams.tpl b/WEB-INF/templates/admin_teams.tpl
new file mode 100644 (file)
index 0000000..c418466
--- /dev/null
@@ -0,0 +1,42 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr><td valign="top">{$i18n.form.teams.hint}</td></tr>
+</table>
+
+<table cellspacing="1" cellpadding="3" border="0" width="720">
+  <tr>
+    <td width="3%" class="tableHeader">{$i18n.label.id}</td>
+    <td width="70%" class="tableHeader">{$i18n.label.thing_name}</td>
+    <td class="tableHeader">{$i18n.label.date}</td>
+    <td class="tableHeader">{$i18n.label.language}</td>
+    <td class="tableHeader">{$i18n.label.edit}</td>
+    <td class="tableHeader">{$i18n.label.delete}</td>
+  </tr>
+  {if $teams}
+    {foreach $teams as $team}
+  <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+    <td>{$team.id}</td>
+    <td>{$team.name|escape:'html'}</td>
+    <td nowrap>{$team.date}</td>
+    <td align="center">{$team.lang}</td>
+    <td><a href="admin_team_edit.php?id={$team.id}">{$i18n.label.edit}</a></td>
+    <td><a href="admin_team_delete.php?id={$team.id}">{$i18n.label.delete}</a></td>
+  </tr>
+    {/foreach}
+  {/if}
+</table>
+    
+<table width="100%">
+  <tr>
+    <td align="center">
+      <br>
+      <form>
+        <input type="button" onclick="chLocation('admin_team_add.php');" value="{$i18n.button.create_team}">&nbsp;{$i18n.label.or}&nbsp;
+        <input type="button" onclick="chLocation('import.php');" value="{$i18n.button.import}">
+      </form>
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_custom_field_add.tpl b/WEB-INF/templates/cf_custom_field_add.tpl
new file mode 100644 (file)
index 0000000..f1fffc2
--- /dev/null
@@ -0,0 +1,31 @@
+{$forms.fieldForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.fieldForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.type}:</td>
+          <td>{$forms.fieldForm.type.control}</td>
+        </tr>
+        <tr>
+          <td align="right"><label for="required">{$i18n.label.required}:</label></td>
+          <td>{$forms.fieldForm.required.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.fieldForm.btn_add.control}</td>
+        </tr>
+      </table>
+    {/if}
+    </td>
+  </tr>
+</table>
+{$forms.fieldForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_custom_field_delete.tpl b/WEB-INF/templates/cf_custom_field_delete.tpl
new file mode 100644 (file)
index 0000000..80fb155
--- /dev/null
@@ -0,0 +1,22 @@
+{$forms.fieldDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$field|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.fieldDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.fieldDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.fieldDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_custom_field_edit.tpl b/WEB-INF/templates/cf_custom_field_edit.tpl
new file mode 100644 (file)
index 0000000..4c312ed
--- /dev/null
@@ -0,0 +1,31 @@
+{$forms.fieldForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.fieldForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.type}:</td>
+          <td>{$forms.fieldForm.type.control}</td>
+        </tr>
+        <tr>
+          <td align="right"><label for="required">{$i18n.label.required}:</label></td>
+          <td>{$forms.fieldForm.required.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.fieldForm.btn_save.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.fieldForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_custom_fields.tpl b/WEB-INF/templates/cf_custom_fields.tpl
new file mode 100644 (file)
index 0000000..601c4fc
--- /dev/null
@@ -0,0 +1,39 @@
+{$forms.customFieldsForm.open}
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+      {if $user->canManageTeam()}
+         <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td width="50%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.type}</td>
+          <td class="tableHeader">{$i18n.menu.options}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+        {if $custom_fields}
+          {foreach $custom_fields as $field}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$field['label']|escape:'html'}</td>
+          {if CustomFields::TYPE_TEXT == $field['type']}
+          <td>{$i18n.label.type_text}</td>
+          <td></td>
+          {else if CustomFields::TYPE_DROPDOWN == $field['type']}
+          <td>{$i18n.label.type_dropdown}</td>
+          <td><a href="cf_dropdown_options.php?field_id={$field['id']}">{$i18n.label.configure}</a></td>
+          {/if}
+          <td><a href="cf_custom_field_edit.php?id={$field['id']}">{$i18n.label.edit}</a></td>
+          <td><a href="cf_custom_field_delete.php?id={$field['id']}">{$i18n.label.delete}</a></td>
+        </tr>
+          {/foreach}
+        {/if}
+      </table>
+    
+      <table width="100%">
+        <tr><td align="center"><br>{$forms.customFieldsForm.btn_add.control}</td></tr>
+         </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.customFieldsForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_dropdown_option_add.tpl b/WEB-INF/templates/cf_dropdown_option_add.tpl
new file mode 100644 (file)
index 0000000..b1deae9
--- /dev/null
@@ -0,0 +1,27 @@
+{$forms.optionAddForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.optionAddForm.name.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+           <td colspan="2" align="center" height="50">{$forms.optionAddForm.btn_add.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.optionAddForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_dropdown_option_delete.tpl b/WEB-INF/templates/cf_dropdown_option_delete.tpl
new file mode 100644 (file)
index 0000000..0e60c96
--- /dev/null
@@ -0,0 +1,22 @@
+{$forms.optionDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$option|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.optionDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.optionDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.optionDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_dropdown_option_edit.tpl b/WEB-INF/templates/cf_dropdown_option_edit.tpl
new file mode 100644 (file)
index 0000000..f0e57f9
--- /dev/null
@@ -0,0 +1,27 @@
+{$forms.optionEditForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.optionEditForm.name.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+         <td colspan="2" align="center" height="50">{$forms.optionEditForm.btn_save.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.optionEditForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/cf_dropdown_options.tpl b/WEB-INF/templates/cf_dropdown_options.tpl
new file mode 100644 (file)
index 0000000..32d1aab
--- /dev/null
@@ -0,0 +1,41 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+{$forms.dropdownOptionsForm.open}
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+      {if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td width="70%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+        {if $options}
+          {foreach $options as $key=>$val}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$val|escape:'html'}</td>
+          <td><a href="cf_dropdown_option_edit.php?id={$key}">{$i18n.label.edit}</a></td>
+          <td><a href="cf_dropdown_option_delete.php?id={$key}">{$i18n.label.delete}</a></td>
+        </tr>
+          {/foreach}
+        {/if}
+      </table>
+      
+      <table width="100%">
+        <tr>
+          <td align="center">
+            <br>
+            <form>
+              <input type="button" onclick="chLocation('cf_dropdown_option_add.php?field_id={$field_id}');" value="{$i18n.button.add_option}">
+            </form>
+          </td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.dropdownOptionsForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/charts.tpl b/WEB-INF/templates/charts.tpl
new file mode 100644 (file)
index 0000000..2223871
--- /dev/null
@@ -0,0 +1,39 @@
+<p></p>
+{$forms.chartForm.open}
+<table border="0" width="720">
+  <tr>
+{if $on_behalf_control}
+      <td width="50%" align="center">{$i18n.label.user}: {$forms.chartForm.onBehalfUser.control}</td>
+  {if $chart_selector}
+      <td width="50%" align="left">{$i18n.form.charts.chart}: {$forms.chartForm.type.control}</td>
+  {/if}
+{else}
+  {if $chart_selector}
+      <td width="100%" align="center">{$i18n.form.charts.chart}: {$forms.chartForm.type.control}</td>
+  {/if}
+{/if}
+
+  </tr>
+</table>
+<table border="0" width="720">
+  <tr>
+    <td width="50%" align="center"><img src="{$img_file_name}" border="0"/></td>
+    <td>
+      <table border="0" cellspacing="3">
+      {section name=i loop=$totals}
+      {if $smarty.section.i.index <= 12}
+        <tr><td style="width:7px;height:1em;background-color:{$totals[i].color_html};"></td><td>{$totals[i].name|escape:'html'}</td></tr>
+      {/if}
+      {/section}
+      </table>
+    </td>
+  </tr>
+</table>
+<p></p>
+<table>
+  <tr><td align="center">{$i18n.form.charts.interval}: {$forms.chartForm.interval.control}</td></tr>
+  <tr><td valign="top">{$forms.chartForm.date.control}</td></tr>
+</table>
+{$forms.chartForm.close}
+
+
diff --git a/WEB-INF/templates/client_add.tpl b/WEB-INF/templates/client_add.tpl
new file mode 100644 (file)
index 0000000..3a17dde
--- /dev/null
@@ -0,0 +1,35 @@
+{$forms.clientForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.client_name} (*):</td>
+          <td>{$forms.clientForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.client_address}:</td>
+             <td>{$forms.clientForm.address.control}</td>
+           </tr>
+           <tr>
+             <td align="right">{$i18n.label.tax}, %:</td>
+             <td>{$forms.clientForm.tax.control}&nbsp;(0{$user->decimal_mark}00)</td>
+           </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.clientForm.projects.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.clientForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/client_delete.tpl b/WEB-INF/templates/client_delete.tpl
new file mode 100644 (file)
index 0000000..8a0b877
--- /dev/null
@@ -0,0 +1,25 @@
+{$forms.clientDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="2" border="0">
+        <tr>
+          <td>{$i18n.form.client.client_to_delete}:</td>
+          <th>{$client_to_delete|escape:'html'}</th>
+        </tr>
+        <tr>
+          <td>{$i18n.form.client.client_entries}:</td>
+          <td>{$forms.clientDeleteForm.delete_client_entries.control}</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.clientDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.clientDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/client_edit.tpl b/WEB-INF/templates/client_edit.tpl
new file mode 100644 (file)
index 0000000..bd9f156
--- /dev/null
@@ -0,0 +1,41 @@
+{$forms.clientForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.client_name} (*):</td>
+          <td>{$forms.clientForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.client_address}:</td>
+             <td>{$forms.clientForm.address.control}</td>
+           </tr>
+           <tr>
+             <td align="right">{$i18n.label.tax}, %:</td>
+             <td>{$forms.clientForm.tax.control}&nbsp;(0{$user->decimal_mark}00)</td>
+           </tr>
+           <tr>
+          <td align = "right">{$i18n.label.status}:</td>
+          <td>{$forms.clientForm.status.control}</td>
+        </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.clientForm.projects.control}</td>
+        </tr>
+{/if}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.clientForm.btn_save.control} {$forms.clientForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.clientForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/clients.tpl b/WEB-INF/templates/clients.tpl
new file mode 100644 (file)
index 0000000..95e59d5
--- /dev/null
@@ -0,0 +1,59 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+{if ($user->canManageTeam())}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+  {if $inactive_clients}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.clients.active_clients}</td></tr>
+  {/if}
+        <tr>
+          <td width="40%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="40%" class="tableHeader">{$i18n.label.address}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+  {foreach $active_clients as $client}
+        <tr valign="top" bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$client.name|escape:'html'}</td>
+          <td>{$client.address|escape:'html'}</td>
+          <td><a href="client_edit.php?id={$client.id}">{$i18n.label.edit}</a></td>
+          <td><a href="client_delete.php?id={$client.id}">{$i18n.label.delete}</a></td>
+        </tr>
+  {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr><td align="center"><br><form><input type="button" onclick="chLocation('client_add.php');" value="{$i18n.button.add_client}"></form></td></tr>
+      </table>
+      
+  {if $inactive_clients}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.clients.inactive_clients}</td></tr>
+        <tr>
+          <td width="40%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="40%" class="tableHeader">{$i18n.label.address}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+    {foreach $inactive_clients as $client}
+        <tr valign="top" bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$client.name|escape:'html'}</td>
+          <td>{$client.address|escape:'html'}</td>
+          <td><a href="client_edit.php?id={$client.id}">{$i18n.label.edit}</a></td>
+          <td><a href="client_delete.php?id={$client.id}">{$i18n.label.delete}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+      
+      <table width="100%">
+        <tr><td align="center"><br><form><input type="button" onclick="chLocation('client_add.php');" value="{$i18n.button.add_client}"></form></td></tr>
+      </table>
+  {/if}      
+{/if}
+    </td>
+  </tr>
+</table>
diff --git a/WEB-INF/templates/datetime_format_preview.tpl b/WEB-INF/templates/datetime_format_preview.tpl
new file mode 100644 (file)
index 0000000..7609acc
--- /dev/null
@@ -0,0 +1,17 @@
+<script>
+function MakeFormatPreview(id, selectElement)
+{
+  var dst = document.getElementById(id);
+  if (dst) {
+    var date = new Date();
+    date.locale = "{$user->lang}";
+    var format;
+    if (selectElement.value != "") {
+      format = selectElement.value;
+    } else {
+      format = selectElement.options[0].text;
+    }
+    dst.innerHTML = "<i>" + date.strftime(format) + "</i>";
+  }
+}
+</script>
diff --git a/WEB-INF/templates/expense_delete.tpl b/WEB-INF/templates/expense_delete.tpl
new file mode 100644 (file)
index 0000000..3a3172b
--- /dev/null
@@ -0,0 +1,39 @@
+{$forms.expenseItemForm.open}
+<table cellspacing="4" cellpadding="7" border="0" width="720">
+<tr>
+  <td>
+  <table border='0' cellpadding='3' cellspacing='1' width="100%">
+  <tr>
+{if in_array('cl', explode(',', $user->plugins))}
+    <td class="tableHeader" align="center">{$i18n.label.client}</td>
+{/if}
+
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td class="tableHeader" align="center">{$i18n.label.project}</td>
+{/if}
+       <td class="tableHeader" align="center">{$i18n.label.item}</td>
+    <td class="tableHeader" align="center">{$i18n.label.cost}</td>
+  </tr>
+  <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+{if in_array('cl', explode(',', $user->plugins))}
+  <td>{$expense_item.client_name|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td>{$expense_item.project_name|escape:'html'}</td>
+{/if}
+    <td>{$expense_item.name|escape:'html'}</td>
+    <td align="right">{$expense_item.cost}</td>
+  </tr>
+  </table>
+  <table width="100%">
+  <tr>
+    <td align="center">&nbsp;</td>
+  </tr>
+  <tr>
+    <td align="center">{$forms.expenseItemForm.delete_button.control}&nbsp;&nbsp;{$forms.expenseItemForm.cancel_button.control}</td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
+{$forms.expenseItemForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/expense_edit.tpl b/WEB-INF/templates/expense_edit.tpl
new file mode 100644 (file)
index 0000000..ddf0d28
--- /dev/null
@@ -0,0 +1,117 @@
+<script>
+// We need a few arrays to populate project dropdown.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+
+// Prepare an array of project ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Mandatory top option for project dropdown.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).    
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  }
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+{$forms.expenseItemForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table width = "100%">
+  <tr>
+       <td valign="top">
+    <table border="0">
+{if in_array('cl', explode(',', $user->plugins))}
+    <tr>
+      <td align="right">{$i18n.label.client} {if in_array('cm', explode(',', $user->plugins))}(*){/if}:</td>
+      <td>{$forms.expenseItemForm.client.control}</td>
+    </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr>
+      <td align="right">{$i18n.label.project} (*):</td>
+      <td>{$forms.expenseItemForm.project.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.label.item}:</td>
+      <td>{$forms.expenseItemForm.item_name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.cost}:</td>
+      <td>{$forms.expenseItemForm.cost.control} {$user->currency|escape:'html'}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.date}:</td>
+      <td>{$forms.expenseItemForm.date.control}</td>
+    </tr>
+    <tr>
+      <td colspan="2">&nbsp;</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td align="left">{$forms.expenseItemForm.btn_save.control} {$forms.expenseItemForm.btn_copy.control} {$forms.expenseItemForm.btn_delete.control}</td>
+    </tr>
+    </table>
+    </td>
+    </tr>
+  </table>
+  </td>
+  </tr>
+</table>
+{$forms.expenseItemForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/expenses.tpl b/WEB-INF/templates/expenses.tpl
new file mode 100644 (file)
index 0000000..3c42c6d
--- /dev/null
@@ -0,0 +1,168 @@
+<script>
+// We need a few arrays to populate project dropdown.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+
+// Prepare an array of project ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Mandatory top option for project dropdown.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).    
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)
+        dropdown.options[i+1].selected = true;
+    }
+  }
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+{$forms.expensesForm.open}
+<table cellspacing="4" cellpadding="0" border="0">
+  <tr>
+    <td valign="top">
+      <table>
+{if $on_behalf_control}
+        <tr>
+          <td align="right">{$i18n.label.user}:</td>
+          <td>{$forms.expensesForm.onBehalfUser.control}</td>
+        </tr>
+{/if}
+{if in_array('cl', explode(',', $user->plugins))}
+        <tr>
+          <td align="right">{$i18n.label.client}{if in_array('cm', explode(',', $user->plugins))} (*){/if}:</td>
+          <td>{$forms.expensesForm.client.control}</td>
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.project} (*):</td>
+          <td>{$forms.expensesForm.project.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td align="right">{$i18n.label.item} (*):</td>
+          <td>{$forms.expensesForm.item_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.cost} (*):</td>
+          <td>{$forms.expensesForm.cost.control} {$user->currency|escape:'html'}</td>
+        </tr>
+      </table>
+    </td>
+    <td valign="top">
+      <table>
+        <tr><td>{$forms.expensesForm.date.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+</table>
+
+<table>
+  <tr>
+    <td align="center" colspan="2">{$forms.expensesForm.btn_submit.control}</td>
+  </tr>
+</table>
+
+<table width="720">
+<tr>
+  <td valign="top">
+    {if $expense_items}
+      <table border="0" cellpadding="3" cellspacing="1" width="100%">
+      <tr>
+{if in_array('cl', explode(',', $user->plugins))}
+        <td width="20%" class="tableHeader">{$i18n.label.client}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.project}</td>
+{/if}
+        <td class="tableHeader">{$i18n.label.item}</td>
+        <td width="5%" class="tableHeaderCentered">{$i18n.label.cost}</td>
+        <td width="5%" class="tableHeader">{$i18n.label.edit}</td>
+      </tr>
+      {foreach $expense_items as $item}
+      <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+{if in_array('cl', explode(',', $user->plugins))}
+        <td valign='top'>{$item.client|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign='top'>{$item.project|escape:'html'}</td>
+{/if}
+        <td valign='top'>{$item.item|escape:'html'}</td>
+        <td valign='top' align='right'>{$item.cost}</td>
+        <td valign='top' align='center'>
+        {if $item.invoice_id}
+          &nbsp;
+        {else}
+          <a href='expense_edit.php?id={$item.id}'>{$i18n.label.edit}</a>
+        {/if}
+        </td>
+      </tr>
+      {/foreach}
+         </table>
+         {if $expense_items}
+      <table border="0" cellpadding="3" cellspacing="1" width="100%">
+        <tr>
+          <td nowrap align="right">{$i18n.label.day_total}: {$user->currency|escape:'html'} {$day_total}</td>
+        </tr>
+      </table>
+      {/if}
+         
+    {/if}
+  </td>
+</tr>
+</table>
+{$forms.expensesForm.close}
+
+
diff --git a/WEB-INF/templates/export.tpl b/WEB-INF/templates/export.tpl
new file mode 100644 (file)
index 0000000..e1cd26b
--- /dev/null
@@ -0,0 +1,23 @@
+{$forms.exportForm.open}
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td align="center">
+{if $user->isManager()}
+      <table border="0" width="60%">
+        <colgroup>
+          <col width="50%">
+          <col width="50%">
+        </colgroup>
+        <tr><td colspan="2">{$i18n.form.export.hint}<br></td></tr>
+        <tr><td colspan="2">&nbsp;</td></tr>
+        <tr>
+          <td align='right'>{$i18n.form.export.compression}:</td>
+          <td>{$forms.exportForm.compression.control}</td>
+        </tr>
+        <tr><td height="50" align="center" colspan="2">{$forms.exportForm.btn_submit.control}</td></tr>
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
+{$forms.exportForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl
new file mode 100644 (file)
index 0000000..ee07573
--- /dev/null
@@ -0,0 +1,29 @@
+    </td>
+  </tr>
+  <tr>
+    <td valign="bottom" width="100%" align="center">
+      <p>&nbsp;</p>
+      
+      <table cellspacing="0" cellpadding="0" height="30" border="0" width="100%">
+        <tr>
+          <td width="100%" align="center" bgcolor="#eeeeee">
+                   <a href="https://www.anuko.com/forum/viewtopic.php?f=4&t=1285" target="_blank">{$i18n.footer.mobile_phones}</a>&nbsp;&nbsp;
+          </td>
+        </tr>
+      </table>
+         <br>
+      <table cellspacing="0" cellpadding="4" width="100%" border="0">
+        <tr>
+          <td align="center">&nbsp;Anuko Time Tracker 1.9.12.3384 | Copyright &copy; <a href="https://www.anuko.com/lp/tt_3.htm" target="_blank">Anuko</a> |
+            <a href="https://www.anuko.com/lp/tt_4.htm" target="_blank">{$i18n.footer.credits}</a> |
+            <a href="https://www.anuko.com/lp/tt_5.htm" target="_blank">{$i18n.footer.license}</a>
+          </td>
+        </tr>
+      </table>
+      <br>
+      
+    </td>
+  </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/WEB-INF/templates/header.tpl b/WEB-INF/templates/header.tpl
new file mode 100644 (file)
index 0000000..2d3bb20
--- /dev/null
@@ -0,0 +1,188 @@
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset={$smarty.const.CHARSET}">
+  <link rel="icon" href="favicon.ico" type="image/x-icon">
+  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
+  <link href="{$smarty.const.DEFAULT_CSS}" rel="stylesheet" type="text/css">
+{if $i18n.language.rtl}
+  <link href="{$smarty.const.RTL_CSS}" rel="stylesheet" type="text/css">
+{/if}
+  <title>Time Tracker{if $title} - {$title}{/if}</title>
+  <script src="js/strftime.js"></script>
+  <script>
+    {* Setup locale for strftime *}
+    {$js_date_locale}
+  </script>
+  <script src="js/strptime.js"></script>
+</head>
+
+<body leftmargin="0" topmargin="0" marginheight="0" marginwidth="0" {$onload}>
+
+{assign var="tab_width" value="700"}
+
+<!--  101% height here is a workaround for Firefox shifting content horizontally when scrollbar appears / disappears.
+See https://bugzilla.mozilla.org/show_bug.cgi?id=279425.
+With 101% height we essentially force the scrollbar to always appear. -->
+<table height="101%" cellspacing="0" cellpadding="0" width="100%" border="0">
+  <tr>
+    <td valign="top" align="center"> <!-- This is to centrally align all our content. -->
+
+      <!-- Top image -->
+      <table cellspacing="0" cellpadding="0" width="100%" border="0">
+        <tr>
+{if $user->custom_logo}
+          <td align="center">
+{else}
+          <td bgcolor="#a6ccf7" background="images/top_bg.gif" align="center">
+{/if}
+            <table cellspacing="0" cellpadding="0" width="{$tab_width}" border="0">
+              <tr>
+                <td valign="top">
+                  <table cellspacing="0" cellpadding="0" width="100%" border="0">
+                    <tr><td height="6" colspan="2"><img width="1" height="6" src="images/1x1.gif" border="0"></td></tr>
+                    <tr valign="top">
+{if $user->custom_logo}
+                      <td height="55" align="center"><img alt="Time Tracker" width="300" height="43" src="{$custom_logo}" border="0"></a></td>
+{else}
+                      <td height="55" align="center"><a href="https://www.anuko.com/lp/tt_1.htm" target="_blank"><img alt="Anuko Time Tracker" width="300" height="43" src="images/tt_logo.png" border="0"></a></td>
+{/if}
+                    </tr>
+                  </table>
+                </td>
+              </tr>
+            </table>
+          </td>
+        </tr>
+      </table>
+      <!-- End of top image -->
+      
+{if $authenticated}
+  {if $user->isAdmin()}
+      <!-- Top menu for admin -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td class="systemMenu" height="17" align="center">&nbsp;
+            <a class="systemMenu" href="logout.php">{$i18n.menu.logout}</a> &middot;
+            <a class="systemMenu" href="{$smarty.const.FORUM_LINK}" target="_blank">{$i18n.menu.forum}</a> &middot;
+            <a class="systemMenu" href="{$smarty.const.HELP_LINK}" target="_blank">{$i18n.menu.help}</a>
+          </td>
+        </tr>
+      </table>
+      <!-- End of top menu for admin -->
+
+      <!-- Sub menu for admin -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td align="center" bgcolor="#d9d9d9" nowrap height="17" background="images/subm_bg.gif">&nbsp;
+            <a class="mainMenu" href="admin_teams.php">{$i18n.menu.teams}</a> &middot;
+            <a class="mainMenu" href="admin_options.php">{$i18n.menu.options}</a>
+          </td>
+        </tr>
+      </table>
+      <!-- End of sub menu for admin -->
+  {else}
+      <!-- Top menu for authorized user -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td class="systemMenu" height="17" align="center">&nbsp;
+            <a class="systemMenu" href="logout.php">{$i18n.menu.logout}</a> &middot;
+            <a class="systemMenu" href="profile_edit.php">{$i18n.menu.profile}</a> &middot;
+            <a class="systemMenu" href="{$smarty.const.FORUM_LINK}" target="_blank">{$i18n.menu.forum}</a> &middot;
+            <a class="systemMenu" href="{$smarty.const.HELP_LINK}" target="_blank">{$i18n.menu.help}</a>
+          </td>
+        </tr>
+      </table>
+      <!-- End of top menu for authorized user -->
+
+      <!-- Sub menu for authorized user -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td align="center" bgcolor="#d9d9d9" nowrap height="17" background="images/subm_bg.gif">&nbsp;
+    {if !$user->isClient()}
+           <a class="mainMenu" href="time.php">{$i18n.menu.time}</a>
+    {/if}
+    {if in_array('ex', explode(',', $user->plugins)) && !$user->isClient()}
+            &middot; <a class="mainMenu" href="expenses.php">{$i18n.menu.expenses}</a>
+    {/if}
+            {if !$user->isClient()}&middot; {/if}<a class="mainMenu" href="reports.php">{$i18n.menu.reports}</a>
+    {if ($user->canManageTeam() || $user->isClient()) && in_array('iv', explode(',', $user->plugins))}
+            &middot; <a class="mainMenu" href="invoices.php">{$i18n.title.invoices}</a>
+    {/if}
+    {if (in_array('ch', explode(',', $user->plugins)) && !$user->isClient()) && ($smarty.const.MODE_PROJECTS == $user->tracking_mode
+      || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode
+      || in_array('cl', explode(',', $user->plugins)))}
+            &middot; <a class="mainMenu" href="charts.php">{$i18n.menu.charts}</a>
+    {/if}
+    {if !$user->isClient() && ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+            &middot; <a class="mainMenu" href="projects.php">{$i18n.menu.projects}</a>
+    {/if}
+    {if $user->canManageTeam() && ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+            &middot; <a class="mainMenu" href="tasks.php">{$i18n.menu.tasks}</a>
+    {/if}
+    {if !$user->isClient()}
+            &middot; <a class="mainMenu" href="users.php">{$i18n.menu.users}</a>
+    {/if}
+    {if $user->canManageTeam() && in_array('cl', explode(',', $user->plugins))}
+            &middot; <a class="mainMenu" href="clients.php">{$i18n.menu.clients}</a>
+    {/if}
+    {if $user->isManager()}
+            &middot; <a class="mainMenu" href="export.php">{$i18n.menu.export}</a>
+    {/if}
+          </td>
+        </tr>
+      </table>
+      <!-- End of sub menu for authorized user -->
+  {/if}
+{else}
+      <!-- Top menu for non authorized user -->
+      <table cellspacing="0" cellpadding="3" width="100%" border="0">
+        <tr>
+          <td class="systemMenu" height="17" align="center">&nbsp;
+            <a class="systemMenu" href="login.php">{$i18n.menu.login}</a> &middot;
+  {if isTrue($smarty.const.MULTITEAM_MODE) && $smarty.const.AUTH_MODULE == 'db'}
+            <a class="systemMenu" href="register.php">{$i18n.menu.create_team}</a> &middot;
+  {/if}
+            <a class="systemMenu" href="{$smarty.const.FORUM_LINK}" target="_blank">{$i18n.menu.forum}</a> &middot;
+            <a class="systemMenu" href="{$smarty.const.HELP_LINK}" target="_blank">{$i18n.menu.help}</a>
+          </td>
+        </tr>
+      </table>
+{/if}
+      <br>
+
+      <!-- Page title and user details -->
+{if $title}
+      <table cellspacing="0" cellpadding="5" width="{$tab_width+20}" border="0">
+        <tr><td class="sectionHeader"><div class="pageTitle">{$title}{if $timestring}: {$timestring}{/if}</div></td></tr>
+        <tr><td>{$user->name|escape:'html'}{if $user->isAdmin()} {$i18n.label.role_admin}{elseif $user->isManager()} {$i18n.label.role_manager}{elseif $user->canManageTeam()} {$i18n.label.role_comanager}{/if}{if $user->behalf_id > 0} <b>{$i18n.label.on_behalf} {$user->behalf_name|escape:'html'}</b>{/if}{if $user->team}, {$user->team|escape:'html'}{/if}</td></tr>
+      </table>
+{/if}
+      <!-- End of page title and user details -->
+
+      <!-- Output errors -->
+{if !$errors->isEmpty()}
+      <table cellspacing="4" cellpadding="7" width="{$tab_width}" border="0">
+        <tr>
+          <td class="error">
+  {foreach $errors->getErrors() as $error}
+            {$error.message}<br> {* No need to escape as they are not coming from user and may contain a link. *}
+  {/foreach}
+          </td>
+        </tr>
+      </table>
+{/if}
+      <!-- End of output errors -->
+
+      <!-- Output messages -->
+{if !$messages->isEmpty()}
+      <table cellspacing="4" cellpadding="7" width="{$tab_width}" border="0">
+        <tr>
+          <td class="info_message">
+  {foreach $messages->getErrors() as $message}
+            {$message.message}<br> {* No need to escape. *}
+  {/foreach}
+          </td>
+        </tr>
+      </table>
+{/if}
+      <!-- End of output messages -->
\ No newline at end of file
diff --git a/WEB-INF/templates/import.tpl b/WEB-INF/templates/import.tpl
new file mode 100644 (file)
index 0000000..ae7a002
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.importForm.open}
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td align="center">
+{if $user->isAdmin()}
+      <table border="0" width="60%">
+        <tr><td colspan="2">{$i18n.form.import.hint}<br></td></tr>
+        <tr><td colspan="2">&nbsp;</td></tr>
+        <tr>
+          <td align='right'>{$i18n.form.import.file}:</td>
+          <td>{$forms.importForm.xmlfile.control}</td>
+        </tr>
+        <tr><td height="50" align="center" colspan="2">{$forms.importForm.btn_submit.control}</td></tr>
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
+{$forms.importForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/index.tpl b/WEB-INF/templates/index.tpl
new file mode 100644 (file)
index 0000000..fd7c5d9
--- /dev/null
@@ -0,0 +1,5 @@
+{include file="header.tpl"}
+
+{if $content_page_name}{include file="$content_page_name"}{/if}
+
+{include file="footer.tpl"}
\ No newline at end of file
diff --git a/WEB-INF/templates/invoice_add.tpl b/WEB-INF/templates/invoice_add.tpl
new file mode 100644 (file)
index 0000000..092e3ff
--- /dev/null
@@ -0,0 +1,53 @@
+{$forms.invoiceForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.form.invoice.number} (*):</td>
+          <td>{$forms.invoiceForm.number.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.date} (*):</td>
+          <td>{$forms.invoiceForm.date.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.client} (*):</td>
+          <td>{$forms.invoiceForm.client.control}</td>
+        </tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}        
+        <tr>
+          <td align="right">{$i18n.label.project}:</td>
+          <td>{$forms.invoiceForm.project.control}</td>
+        </tr>
+{/if}        
+        <tr>
+          <td align="right">{$i18n.label.start_date} (*):</td>
+          <td>{$forms.invoiceForm.start.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.end_date} (*):</td>
+          <td>{$forms.invoiceForm.finish.control}</td>
+        </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.invoiceForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.invoiceForm.close}
+
+<script>
+// Set the date field to browser today in user date format.
+var dateField = document.getElementById("date");
+if (dateField && !dateField.value) {
+  var today = new Date();
+  dateField.value = today.strftime("{$user->date_format}");
+}
+</script> 
\ No newline at end of file
diff --git a/WEB-INF/templates/invoice_delete.tpl b/WEB-INF/templates/invoice_delete.tpl
new file mode 100644 (file)
index 0000000..57823f3
--- /dev/null
@@ -0,0 +1,23 @@
+{$forms.invoiceDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="2" border="0">
+        <tr>
+          <td>{$i18n.form.invoice.invoice_to_delete}:</td>
+          <th>{$invoice_to_delete|escape:'html'}</th>
+        </tr>
+        <tr>
+          <td>{$i18n.form.invoice.invoice_entries}:</td>
+          <td>{$forms.invoiceDeleteForm.delete_invoice_entries.control}</td>
+        </tr>
+        <tr><td colspan="2" align="center">&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$forms.invoiceDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.invoiceDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.invoiceDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/invoice_view.tpl b/WEB-INF/templates/invoice_view.tpl
new file mode 100644 (file)
index 0000000..ceeeffc
--- /dev/null
@@ -0,0 +1,74 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td>
+      <table border=0 width=100%>
+        <tr><td align="center"><b style="font-size: 15pt; font-family: Arial, Helvetica, sans-serif;">{$i18n.title.invoice} {$invoice_name|escape:'html'} </b></td></tr>
+        <tr><td align='left'><b>{$i18n.label.date}:</b> {$invoice_date}</td></tr>
+        <tr><td align='left'><b>{$i18n.label.client}:</b> {$client_name|escape:'html'}</td></tr>
+        <tr><td align='left'><b>{$i18n.label.client_address}:</b> {$client_address|escape:'html'}</td></tr>
+      </table>
+    </td>
+  </tr>
+  <tr>
+    <td valign="top">
+{if $invoice_items}
+      <table border='0' cellpadding='3' cellspacing='1' width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.date}</td>
+          <td class="tableHeader">{$i18n.form.invoice.person}</td>
+  {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+          <td class="tableHeader">{$i18n.label.project}</td>
+  {/if}
+  {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+          <td class="tableHeader">{$i18n.label.task}</td>
+  {/if}
+          <td class="tableHeader">{$i18n.label.note}</td>
+          <td class="tableHeaderCentered" width="5%">{$i18n.label.duration}</td>
+          <td class="tableHeaderCentered" width="5%">{$i18n.label.cost}</td>
+        </tr>
+  {foreach $invoice_items as $invoice_item}
+        <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+          <td valign='top'>{$invoice_item.date}</td>
+          <td valign='top'>{$invoice_item.user_name|escape:'html'}</td>
+    {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+          <td valign='top'>{$invoice_item.project_name|escape:'html'}</td>
+    {/if}
+    {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}        
+          <td valign='top'>{$invoice_item.task_name|escape:'html'}</td>
+    {/if}
+          <td valign='top'>{$invoice_item.note|escape:'html'}</td>
+          <td align='right' valign='top'>{$invoice_item.duration}</td>
+          <td align='right' valign='top'>{$invoice_item.cost}</td>
+        </tr>
+  {/foreach}
+        <tr><td>&nbsp;</td></tr>
+  {if $tax}
+        <tr>
+          <td align="right" colspan="{$colspan}"><b>{$i18n.label.subtotal}:</b></td>
+          <td align="right"><nobr>{$subtotal|escape:'html'}</nobr></td>
+        </tr>
+        <tr>
+          <td align="right" colspan="{$colspan}"><b>{$i18n.label.tax}:</b></td>
+          <td align="right"><nobr>{$tax|escape:'html'}</nobr></td>
+        </tr>
+   {/if}
+        <tr>
+          <td align="right" colspan="{$colspan}"><b>{$i18n.label.total}:</b></td>
+          <td align="right"><nobr>{$total|escape:'html'}</nobr></td>
+        </tr>
+        
+         </table>
+{/if}
+    </td>
+  </tr>
+  <tr><td align="center"><br><form>
+    <input type="button" onclick="chLocation('invoice_send.php?id={$invoice_id}');" value="{$i18n.button.send_by_email}">
+  </form></td></tr>
+</table>
+
+
+
diff --git a/WEB-INF/templates/invoices.tpl b/WEB-INF/templates/invoices.tpl
new file mode 100644 (file)
index 0000000..ad8f537
--- /dev/null
@@ -0,0 +1,40 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam() || $user->isClient()}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.invoice}</td>
+          <td class="tableHeader">{$i18n.label.client}</td>
+          <td class="tableHeader">{$i18n.label.date}</td>
+          <td class="tableHeader">{$i18n.label.view}</td>
+  {if !$user->isClient()}
+          <td class="tableHeader">{$i18n.label.delete}</td>
+  {/if}
+        </tr>
+        {foreach $invoices as $invoice}
+        <tr valign="top" bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$invoice.name|escape:'html'}</td>
+          <td>{$invoice.client_name|escape:'html'}</td>
+          <td>{$invoice.date}</td>
+          <td><a href="invoice_view.php?id={$invoice.id}">{$i18n.label.view}</a></td>
+  {if !$user->isClient()}
+          <td><a href="invoice_delete.php?id={$invoice.id}">{$i18n.label.delete}</a></td>
+  {/if}
+        </tr>
+        {/foreach}
+      </table>
+  
+  {if !$user->isClient()}
+      <table width="100%">
+        <tr><td align="center"><br><form><input type="button" onclick="chLocation('invoice_add.php');" value="{$i18n.button.add_invoice}"></form></td></tr>
+      </table>
+  {/if}
+{/if}
+    </td>
+  </tr>
+</table>
diff --git a/WEB-INF/templates/login.db.tpl b/WEB-INF/templates/login.db.tpl
new file mode 100644 (file)
index 0000000..ed00513
--- /dev/null
@@ -0,0 +1,20 @@
+<table border="0">
+  <tr>
+    <td{if !$i18n.language.rtl} align="right"{/if}>{$i18n.label.login}:</td>
+    <td>{$forms.loginForm.login.control}</td>
+  </tr>
+  <tr>
+    <td{if !$i18n.language.rtl} align="right"{/if}>{$i18n.label.password}:</td>
+    <td>{$forms.loginForm.password.control}</td>
+  </tr>
+  <tr>
+    <td>&nbsp;</td>
+    <td><a href ='password_reset.php'>{$i18n.form.login.forgot_password}</a></td>
+  </tr>
+  <tr>
+    <td colspan="2">&nbsp;</td>
+  </tr>
+  <tr>
+    <td colspan="2" align="center" height="50">{$forms.loginForm.btn_login.control}</td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/login.ldap.tpl b/WEB-INF/templates/login.ldap.tpl
new file mode 100644 (file)
index 0000000..833b0a1
--- /dev/null
@@ -0,0 +1,27 @@
+<table border="0">
+  {if $show_hint}
+  <tr>
+    <td colspan="2" align="center">{$i18n.label.ldap_hint}</td>
+  </tr>
+  <tr>
+    <td colspan="2">&nbsp;</td>
+  </tr>
+  {/if}
+  <tr>
+    <td{if !$i18n.language.rtl} align="right"{/if}>{$i18n.label.login}:</td>
+    <td>{$forms.loginForm.login.control} <font color="#777777">@{$Auth_ldap_params.default_domain}</font></td>
+  </tr>
+  <tr>
+    <td{if !$i18n.language.rtl} align="right"{/if}>{$i18n.label.password}:</td>
+    <td>{$forms.loginForm.password.control}</td>
+  </tr>
+  <tr>
+    <td colspan="2">&nbsp;</td>
+  </tr>
+  <tr>
+    <td colspan="2">&nbsp;</td>
+  </tr>
+  <tr>
+    <td colspan="2" align="center" height="50">{$forms.loginForm.btn_login.control}</td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/login.tpl b/WEB-INF/templates/login.tpl
new file mode 100644 (file)
index 0000000..85050cd
--- /dev/null
@@ -0,0 +1,21 @@
+<script>
+<!--
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+//-->
+</script>
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {$forms.loginForm.open}
+      {include file="login.`$smarty.const.AUTH_MODULE`.tpl"}
+      {$forms.loginForm.close}
+    </td>
+  </tr>
+</table>
+
+{if !empty($about_text)}
+  <div id="LoginAboutText"> {$about_text} </div>
+{/if}
\ No newline at end of file
diff --git a/WEB-INF/templates/mail.tpl b/WEB-INF/templates/mail.tpl
new file mode 100644 (file)
index 0000000..5d748af
--- /dev/null
@@ -0,0 +1,43 @@
+{$forms.mailForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+    <table cellspacing="4" cellpadding="7" border="0">
+    <tr>
+      <td valign="top" colspan="2">
+        <table>
+        <tr>
+          <td align='right'>{$i18n.form.mail.from} (*):</td>
+          <td>{$smarty.const.SENDER}</td>
+        </tr>
+        <tr>
+          <td align='right'>{$i18n.form.mail.to} (*):</td>
+          <td>{$forms.mailForm.receiver.control}</td>
+        </tr>
+        <tr>
+          <td align='right'>{$i18n.form.mail.cc}:</td>
+          <td>{$forms.mailForm.cc.control}</td>
+        </tr>
+        <tr>
+          <td align='right'>{$i18n.form.mail.subject} (*):</td>
+          <td>{$forms.mailForm.subject.control}</td>
+        </tr>
+        <tr>
+          <td align='right'>{$i18n.label.comment}:</td>
+          <td>{$forms.mailForm.comment.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="70">{$forms.mailForm.btn_send.control}</td>
+        </tr>
+        </table>
+      </td>
+    </tr>
+    </table>
+  </td>
+</tr>
+</table>
+{$forms.mailForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/access_denied.tpl b/WEB-INF/templates/mobile/access_denied.tpl
new file mode 100644 (file)
index 0000000..fa0c45f
--- /dev/null
@@ -0,0 +1 @@
+<!-- empty -->
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/header.tpl b/WEB-INF/templates/mobile/header.tpl
new file mode 100644 (file)
index 0000000..2a663c8
--- /dev/null
@@ -0,0 +1,83 @@
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset={$smarty.const.CHARSET}">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="icon" href="../favicon.ico" type="image/x-icon">
+  <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
+  <link href="../{$smarty.const.DEFAULT_CSS}" rel="stylesheet" type="text/css">
+{if $i18n.language.rtl}
+  <link href="../{$smarty.const.RTL_CSS}" rel="stylesheet" type="text/css">
+{/if}
+  <title>Time Tracker{if $title} - {$title}{/if}</title>
+  <script src="../js/strftime.js"></script>
+  <script>
+    {* Setup locale for strftime *}
+    {$js_date_locale}
+  </script>
+  <script src="../js/strptime.js"></script>
+</head>
+
+<body leftmargin="0" topmargin="0" marginheight="0" marginwidth="0" {$onload}>
+
+{assign var="tab_width" value="300"}
+
+<table height="100%" cellspacing="0" cellpadding="0" width="320" border="0">
+  <tr>
+    <td valign="top" align="center"> <!-- This is to centrally align all our content. -->
+
+      <!-- Top image -->
+      <table cellspacing="0" cellpadding="0" width="100%" border="0">
+        <tr>
+{if $user->custom_logo}
+          <td align="center">
+{else}
+          <td bgcolor="#a6ccf7" background="../images/top_bg.gif" align="center">
+{/if}
+            <table cellspacing="0" cellpadding="0" width="{$tab_width}" border="0">
+              <tr>
+                <td valign="top">
+                  <table cellspacing="0" cellpadding="0" width="100%" border="0">
+                    <tr><td height="6" colspan="2"><img width="1" height="6" src="../images/1x1.gif" border="0"></td></tr>
+                    <tr valign="top">
+{if $user->custom_logo}
+                      <td height="55" align="center"><img alt="Time Tracker" width="300" height="43" src="{$mobile_custom_logo}" border="0"></td>
+{else}
+                      <td height="55" align="center"><img alt="Anuko Time Tracker" width="300" height="43" src="../images/tt_logo.png" border="0"></td>
+{/if}
+                    </tr>
+                  </table>
+                </td>
+              </tr>
+            </table>
+          </td>
+        </tr>
+      </table>
+      <!-- End of top image -->
+
+      <!-- Output errors -->
+{if !$errors->isEmpty()}
+      <table cellspacing="4" cellpadding="7" width="{$tab_width}" border="0">
+        <tr>
+          <td class="error">
+  {foreach $errors->getErrors() as $error}
+            {$error.message}<br> {* No need to escape as they are not coming from user and may contain a link. *}
+  {/foreach}
+          </td>
+        </tr>
+      </table>
+{/if}
+      <!-- End of output errors -->
+
+      <!-- Output messages -->
+{if !$messages->isEmpty()}
+      <table cellspacing="4" cellpadding="7" width="{$tab_width}" border="0">
+        <tr>
+          <td class="info_message">
+  {foreach $messages->getErrors() as $message}
+            {$message.message}<br> {* No need to escape. *}
+  {/foreach}
+          </td>
+        </tr>
+      </table>
+{/if}
+      <!-- End of output messages -->
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/index.tpl b/WEB-INF/templates/mobile/index.tpl
new file mode 100644 (file)
index 0000000..8d8993c
--- /dev/null
@@ -0,0 +1,3 @@
+{include file="mobile/header.tpl"}
+
+{if $content_page_name}{include file="$content_page_name"}{/if}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/login.db.tpl b/WEB-INF/templates/mobile/login.db.tpl
new file mode 100644 (file)
index 0000000..11a7e71
--- /dev/null
@@ -0,0 +1,7 @@
+<table border="0">
+  <tr><td>{$i18n.label.login}:</td></tr>
+  <tr><td>{$forms.loginForm.login.control}</td></tr>
+  <tr><td>{$i18n.label.password}:</td></tr>
+  <tr><td>{$forms.loginForm.password.control}</td></tr>
+  <tr><td align="center" height="50">{$forms.loginForm.btn_login.control}</td></tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/login.ldap.tpl b/WEB-INF/templates/mobile/login.ldap.tpl
new file mode 100644 (file)
index 0000000..11a7e71
--- /dev/null
@@ -0,0 +1,7 @@
+<table border="0">
+  <tr><td>{$i18n.label.login}:</td></tr>
+  <tr><td>{$forms.loginForm.login.control}</td></tr>
+  <tr><td>{$i18n.label.password}:</td></tr>
+  <tr><td>{$forms.loginForm.password.control}</td></tr>
+  <tr><td align="center" height="50">{$forms.loginForm.btn_login.control}</td></tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/login.tpl b/WEB-INF/templates/mobile/login.tpl
new file mode 100644 (file)
index 0000000..f70c375
--- /dev/null
@@ -0,0 +1,17 @@
+<script>
+<!--
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+//-->
+</script>
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {$forms.loginForm.open}
+      {include file="mobile/login.`$smarty.const.AUTH_MODULE`.tpl"}
+      {$forms.loginForm.close}
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/time.tpl b/WEB-INF/templates/mobile/time.tpl
new file mode 100644 (file)
index 0000000..8ae65b0
--- /dev/null
@@ -0,0 +1,296 @@
+<script>
+// We need a few arrays to populate project and task dropdowns.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// When project selection changes, the task dropdown must be repopulated similarly.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+// task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project.
+// task_names[100] = "Coding";            // Task name.
+
+//Prepare an array of projects ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Prepare an array of task ids for projects.
+task_ids = new Array();
+{foreach $project_list as $project}
+  task_ids[{$project.id}] = "{$project.tasks}";
+{/foreach}
+// Prepare an array of task names.
+task_names = new Array();
+{foreach $task_list as $task}
+  task_names[{$task.id}] = "{$task.name|escape:'javascript'}";
+{/foreach}
+
+// Mandatory top options for project and task dropdowns.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+empty_label_task = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The populateDropdowns function populates the "project" and "task" dropdown controls
+// with relevant values.
+function fillDropdowns() {
+  if(document.body.contains(document.timeRecordForm.client))
+    fillProjectDropdown(document.timeRecordForm.client.value);
+
+  fillTaskDropdown(document.timeRecordForm.project.value);
+}
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).    
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  var project_reset = true;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  }
+
+  // If project selection was reset - clear the tasks dropdown.
+  if (project_reset) {
+    dropdown = document.getElementById("task");
+    dropdown.length = 0;
+    dropdown.options[0] = new Option(empty_label_task, '', true);
+  }
+}
+
+// The fillTaskDropdown function populates the task combo box with
+// tasks associated with a selected project (project id is passed here as id).    
+function fillTaskDropdown(id) {
+  var str_ids = task_ids[id];
+
+  var dropdown = document.getElementById("task");
+  if (dropdown == null) return; // Nothing to do.
+  
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_task, '', true);
+
+  // Populate the dropdown from the task_names array.
+  if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    var idx = 1;
+    for (var i = 0; i < len; i++) {
+      var t_id = ids[i];
+      if (task_names[t_id]) {
+        dropdown.options[idx] = new Option(task_names[t_id], t_id);
+        idx++;
+      }
+    }
+
+    // If a previously selected item is still in dropdown - select it.
+       if (dropdown.options.length > 0) {
+      for (var i = 0; i < dropdown.options.length; i++) {
+        if (dropdown.options[i].value == selected_item) {
+          dropdown.options[i].selected = true;
+        }
+      }
+    }
+  }
+}
+
+// The formDisable function disables some fields depending on what we have in other fields.
+function formDisable(formField) {
+  formFieldValue = eval("document.timeRecordForm." + formField + ".value");
+  formFieldName = eval("document.timeRecordForm." + formField + ".name");
+
+  if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = false;
+    x.style.background = "white";
+  }
+
+  if ((formFieldValue != "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+       x.value = "";
+       x.disabled = true;
+       x.style.background = "#e9e9e9";
+       var x = eval("document.timeRecordForm.finish");
+       x.value = "";
+       x.disabled = true;
+       x.style.background = "#e9e9e9";
+  }
+
+  if ((formFieldValue == "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+    x.disabled = false;
+    x.style.background = "white";
+    var x = eval("document.timeRecordForm.finish");
+    x.disabled = false;
+    x.style.background = "white";
+  }
+}
+
+// The setNow function fills a given field with current time.
+function setNow(formField) {
+  var x = eval("document.timeRecordForm.start");
+  x.disabled = false;
+  x.style.background = "white";
+  var x = eval("document.timeRecordForm.finish");
+  x.disabled = false;
+  x.style.background = "white";
+  var today = new Date();
+  var time_format = '{$user->time_format}';
+  var obj = eval("document.timeRecordForm." + formField);
+  obj.value = today.strftime(time_format);
+  formDisable(formField);
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+<style>
+.not_billable td {
+       color: #ff6666;
+}
+</style>
+
+<table cellspacing="3" cellpadding="0" border="0" width="100%">
+  <tr>
+    <td class="sectionHeaderNoBorder" align="right"><a href="time.php?date={$prev_date}">&lt;&lt;</a></td>
+    <td class="sectionHeaderNoBorder" align="center">{$timestring}</td>
+    <td class="sectionHeaderNoBorder" align="left"><a href="time.php?date={$next_date}">&gt;&gt;</a></td>
+  </tr>
+</table>
+
+<table cellspacing="3" cellpadding="0" border="0" width="100%">
+<tr>
+  <td align="center">
+    {if $time_records}
+      <table border='0' cellpadding='4' cellspacing='1' width="100%">
+      {foreach $time_records as $record}
+      <tr bgcolor="{cycle values="#ccccce,#f5f5f5"}" {if !$record.billable} class="not_billable" {/if}>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign='top'>{$record.project|escape:'html'}</td>
+{/if}
+        <td align='right' valign='top'>{if $record.duration == '0:00'}<font color="#ff0000">{/if}{$record.duration}{if $record.duration == '0:00'}</font>{/if}
+        <td align='center'>{if $record.invoice_id}&nbsp;{else}<a href='time_edit.php?id={$record.id}'>{$i18n.label.edit}</a>{/if}</td>
+      </tr>
+      {/foreach}
+         </table>
+         <table border='0'>
+      <tr>
+        <td align='right'>{$i18n.label.day_total}:</td>
+        <td>{$day_total}</td>
+      </tr>
+      </table>
+    {/if}
+  </td>
+</tr>
+</table>
+
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table width = "100%">
+  <tr>
+       <td valign="top">
+    <table border="0">
+{if in_array('cl', explode(',', $user->plugins))}
+    <tr><td>{$i18n.label.client}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.client.control}</td></tr>
+{/if}
+{if in_array('iv', explode(',', $user->plugins))}
+    <tr><td><label>{$forms.timeRecordForm.billable.control}{$i18n.form.time.billable}</label></td></tr>
+{/if}
+{if ($custom_fields && $custom_fields->fields[0])}
+      <tr><td>{$custom_fields->fields[0]['label']|escape:'html'}:</td></tr>
+      <tr><td>{$forms.timeRecordForm.cf_1.control}</td></tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr><td>{$i18n.label.project}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.project.control}</td></tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr><td>{$i18n.label.task}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.task.control}</td></tr>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr><td>{$i18n.label.start}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.start.control}&nbsp;<input onclick="setNow('start');" type="button" value="{$i18n.button.now}"></td></tr>
+
+    <tr><td>{$i18n.label.finish}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.finish.control}&nbsp;<input onclick="setNow('finish');" type="button" value="{$i18n.button.now}"></td></tr>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr><td>{$i18n.label.duration}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.duration.control}</td></tr>
+{/if}
+
+    <tr><td>{$i18n.label.note}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.note.control}</td></tr>
+    </table>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2" height="50" align="center">{$forms.timeRecordForm.btn_submit.control}</td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
+{$forms.timeRecordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/time_delete.tpl b/WEB-INF/templates/mobile/time_delete.tpl
new file mode 100644 (file)
index 0000000..31c6daa
--- /dev/null
@@ -0,0 +1,32 @@
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table border='0' cellpadding='3' cellspacing='1' width="100%">
+  <tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td class="tableHeader" align="center">{$i18n.label.project}</td>
+{/if}
+    <td class="tableHeader" align="center">{$i18n.label.duration}</td>
+       <td class="tableHeader" align="center">{$i18n.label.note}</td>
+  </tr>
+  <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td>{$time_rec.project_name|escape:'html'}</td>
+{/if}
+    <td align="right">{if $time_rec.duration<>'0:00'}{$time_rec.duration}{else}<font color="#ff0000">{$i18n.form.time.uncompleted}</font>{/if}</td>
+    <td>{if $time_rec.comment}{$time_rec.comment|escape:'html'}{else}&nbsp;{/if}</td>
+  </tr>
+  </table>
+  <table width="100%">
+  <tr>
+    <td align="center">&nbsp;</td>
+  </tr>
+  <tr>
+    <td align="center">{$forms.timeRecordForm.delete_button.control}&nbsp;&nbsp;{$forms.timeRecordForm.cancel_button.control}</td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
+{$forms.timeRecordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/mobile/time_edit.tpl b/WEB-INF/templates/mobile/time_edit.tpl
new file mode 100644 (file)
index 0000000..977f0a2
--- /dev/null
@@ -0,0 +1,254 @@
+<script>
+// We need a few arrays to populate project and task dropdowns.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// When project selection changes, the task dropdown must be repopulated similarly.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+// task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project.
+// task_names[100] = "Coding";            // Task name.
+
+//Prepare an array of projects ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Prepare an array of task ids for projects.
+task_ids = new Array();
+{foreach $project_list as $project}
+  task_ids[{$project.id}] = "{$project.tasks}";
+{/foreach}
+// Prepare an array of task names.
+task_names = new Array();
+{foreach $task_list as $task}
+  task_names[{$task.id}] = "{$task.name|escape:'javascript'}";
+{/foreach}
+
+// Mandatory top options for project and task dropdowns.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+empty_label_task = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The populateDropdowns function populates the "project" and "task" dropdown controls
+// with relevant values.
+function fillDropdowns() {
+  if(document.body.contains(document.timeRecordForm.client))
+    fillProjectDropdown(document.timeRecordForm.client.value);
+
+  fillTaskDropdown(document.timeRecordForm.project.value);
+}
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected clientt (client id is passed here as id).
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  var project_reset = true;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  }
+
+  // If project selection was reset - clear the tasks dropdown.
+  if (project_reset) {
+    dropdown = document.getElementById("task");
+    dropdown.length = 0;
+    dropdown.options[0] = new Option(empty_label_task, '', true);
+  }
+}
+
+// The fillTaskDropdown function populates the task combo box with
+// tasks associated with a selected project (project id is passed here as id).
+function fillTaskDropdown(id) {
+  var str_ids = task_ids[id];
+
+  var dropdown = document.getElementById("task");
+  if (dropdown == null) return; // Nothing to do.
+  
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_task, '', true);
+
+  // Populate the dropdown from the task_names array.
+  if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    var idx = 1;
+    for (var i = 0; i < len; i++) {
+      var t_id = ids[i];
+      if (task_names[t_id]) {
+        dropdown.options[idx] = new Option(task_names[t_id], t_id);
+        idx++;
+      }
+    }
+
+    // If a previously selected item is still in dropdown - select it.
+       if (dropdown.options.length > 0) {
+      for (var i = 0; i < dropdown.options.length; i++) {
+        if (dropdown.options[i].value == selected_item) {
+          dropdown.options[i].selected = true;
+        }
+      }
+    }
+  }
+}
+
+// The formDisable function disables some fields depending on what we have in other fields.
+function formDisable(formField) {
+  var formFieldValue = eval("document.timeRecordForm." + formField + ".value");
+  var formFieldName = eval("document.timeRecordForm." + formField + ".name");
+
+  if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = false;
+    x.style.background = "white";
+  }
+
+  if ((formFieldValue != "") && (formFieldName == "duration")) {
+    var x = eval("document.timeRecordForm.start");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+    var x = eval("document.timeRecordForm.finish");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if ((formFieldValue == "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+       x.disabled = false;
+       x.style.background = "white";
+       var x = eval("document.timeRecordForm.finish");
+       x.disabled = false;
+       x.style.background = "white";
+  }
+}
+
+// The setNow function fills a given field with current time.
+function setNow(formField) {
+  var x = eval("document.timeRecordForm.start");
+  x.disabled = false;
+  x.style.background = "white";
+  var x = eval("document.timeRecordForm.finish");
+  x.disabled = false;
+  x.style.background = "white";
+  var today = new Date();
+  var time_format = '{$user->time_format}';
+  var obj = eval("document.timeRecordForm." + formField);
+  obj.value = today.strftime(time_format);
+  formDisable(formField);
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+</script>
+
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table width = "100%">
+  <tr>
+       <td valign="top">
+    <table border="0">
+{if in_array('cl', explode(',', $user->plugins))}
+    <tr><td>{$i18n.label.client}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.client.control}</td></tr>
+{/if}
+{if in_array('iv', explode(',', $user->plugins))}
+    <tr><td><label>{$forms.timeRecordForm.billable.control}{$i18n.form.time.billable}</label></td></tr>
+{/if}
+{if ($custom_fields && $custom_fields->fields[0])} 
+    <tr><td>{$custom_fields->fields[0]['label']|escape:'html'}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.cf_1.control}</td></tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr><td>{$i18n.label.project}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.project.control}</td></tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr><td>{$i18n.label.task}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.task.control}</td></tr>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr><td>{$i18n.label.start}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.start.control}&nbsp;<input onclick="setNow('start');" type="button" value="{$i18n.button.now}"></td></tr>
+    <tr><td>{$i18n.label.finish}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.finish.control}&nbsp;<input onclick="setNow('finish');" type="button" value="{$i18n.button.now}"></td></tr>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr><td>{$i18n.label.duration}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.duration.control}</td></tr>
+{/if}
+    <tr><td>{$i18n.label.date}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.date.control}</td></tr>
+    <tr><td>{$i18n.label.note}:</td></tr>
+    <tr><td>{$forms.timeRecordForm.note.control}</td></tr>
+    <tr><td align="center">{$forms.timeRecordForm.btn_save.control}&nbsp;{$forms.timeRecordForm.btn_delete.control}</td></tr>
+    </table>
+    </td>
+    </tr>
+  </table>
+  </td>
+  </tr>
+</table>
+{$forms.timeRecordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/notification_add.tpl b/WEB-INF/templates/notification_add.tpl
new file mode 100644 (file)
index 0000000..fe209ac
--- /dev/null
@@ -0,0 +1,30 @@
+{$forms.notificationForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.fav_report} (*):</td>
+          <td>{$forms.notificationForm.fav_report.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.cron_schedule} (*):</td>
+             <td>{$forms.notificationForm.cron_spec.control} <a href="https://www.anuko.com/lp/tt_6.htm" target="_blank">{$i18n.label.what_is_it}</a></td>
+           </tr>
+           <tr>
+             <td align="right">{$i18n.label.email} (*):</td>
+             <td>{$forms.notificationForm.email.control}</td>
+           </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.notificationForm.btn_add.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.notificationForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/notification_delete.tpl b/WEB-INF/templates/notification_delete.tpl
new file mode 100644 (file)
index 0000000..4b8c617
--- /dev/null
@@ -0,0 +1,18 @@
+{$forms.notificationDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$notification_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr><td colspan="2" align="center">&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$forms.notificationDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.notificationDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.notificationDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/notification_edit.tpl b/WEB-INF/templates/notification_edit.tpl
new file mode 100644 (file)
index 0000000..9234d2a
--- /dev/null
@@ -0,0 +1,30 @@
+{$forms.notificationForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.fav_report} (*):</td>
+          <td>{$forms.notificationForm.fav_report.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.cron_schedule} (*):</td>
+             <td>{$forms.notificationForm.cron_spec.control} <a href="https://www.anuko.com/lp/tt_6.htm" target="_blank">{$i18n.label.what_is_it}</a></td>
+           </tr>
+           <tr>
+             <td align="right">{$i18n.label.email} (*):</td>
+             <td>{$forms.notificationForm.email.control}</td>
+           </tr>
+        <tr>
+          <td height="40"></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.notificationForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.notificationForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/notifications.tpl b/WEB-INF/templates/notifications.tpl
new file mode 100644 (file)
index 0000000..78bd8b1
--- /dev/null
@@ -0,0 +1,34 @@
+{$forms.notificationsForm.open}
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+      {if $user->canManageTeam()}
+         <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.cron_schedule}</td>
+          <td class="tableHeader">{$i18n.label.email}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+        {if $notifications}
+          {foreach $notifications as $notification}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$notification['name']|escape:'html'}</td>
+          <td>{$notification['cron_spec']|escape:'html'}</td>
+          <td>{$notification['email']|escape:'html'}</td>
+          <td><a href="notification_edit.php?id={$notification['id']}">{$i18n.label.edit}</a></td>
+          <td><a href="notification_delete.php?id={$notification['id']}">{$i18n.label.delete}</a></td>
+        </tr>
+          {/foreach}
+        {/if}
+      </table>
+    
+      <table width="100%">
+        <tr><td align="center"><br>{$forms.notificationsForm.btn_add.control}</td></tr>
+         </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.notificationsForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/password_change.tpl b/WEB-INF/templates/password_change.tpl
new file mode 100644 (file)
index 0000000..c3cadbd
--- /dev/null
@@ -0,0 +1,41 @@
+{$forms.newPasswordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $result_message}
+      <table cellspacing="4" cellpadding="7" border="0" width="100%">
+        <tr><td align="center"><font color="red"><b>{$result_message}</b></font></td></tr>
+      </table>
+         {else}
+      <table>
+        <tr>
+          <td colspan="4" height="40">{$i18n.form.change_password.tip}</td>
+        </tr>
+        <tr>
+          <td colspan="2">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.password} (*):</td>
+          <td colspan="3">{$forms.newPasswordForm.password1.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.confirm_password} (*):</td>
+          <td colspan="3">{$forms.newPasswordForm.password2.control}</td>
+        </tr>
+        <tr>
+          <td colspan="2">&nbsp;</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td>&nbsp;</td>
+          <td colspan="3" align="center">{$forms.newPasswordForm.btn_save.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.newPasswordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/password_reset.tpl b/WEB-INF/templates/password_reset.tpl
new file mode 100644 (file)
index 0000000..17fe05a
--- /dev/null
@@ -0,0 +1,27 @@
+{$forms.resetPasswordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      {if $result_message}
+      <table cellspacing="4" cellpadding="7" border="0" width="100%">
+        <tr><td align="center"><font color="red"><b>{$result_message}</b></font></td></tr>
+      </table>
+         {else}
+      <table>
+        <tr>
+          <td align="right">{$i18n.label.login}:</td>
+          <td colspan="3">{$forms.resetPasswordForm.login.control}</td>
+        </tr>
+        <tr>
+          <td colspan="4">&nbsp;</td>
+        </tr>
+        <tr>
+          <td>&nbsp;</td>
+          <td colspan="3" align="center">{$forms.resetPasswordForm.btn_submit.control}</td>
+        </tr>
+      </table>
+      {/if}
+    </td>
+  </tr>
+</table>
+{$forms.resetPasswordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/profile_edit.tpl b/WEB-INF/templates/profile_edit.tpl
new file mode 100644 (file)
index 0000000..032cc5f
--- /dev/null
@@ -0,0 +1,196 @@
+<script>
+// handleControls - controls visibility of controls.
+function handlePluginCheckboxes() {
+  var clientsCheckbox = document.getElementById("clients");
+  var invoicesCheckbox = document.getElementById("invoices");
+  var requiredCheckbox = document.getElementById("client_required");
+  var requiredLabel = document.getElementById("required_label");
+  if (clientsCheckbox.checked) {
+    requiredCheckbox.style.visibility = "visible";
+       requiredLabel.style.visibility = "visible";
+       invoicesCheckbox.disabled = false;
+  } else {
+       requiredCheckbox.checked = false;
+       requiredCheckbox.style.visibility = "hidden";
+       requiredLabel.style.visibility = "hidden";
+       invoicesCheckbox.checked = false;
+    invoicesCheckbox.disabled = true;
+  } 
+
+  var expensesCheckbox = document.getElementById("expenses");
+  var taxCheckbox = document.getElementById("tax_expenses");
+  var taxLabel = document.getElementById("tax_label");
+  if (expensesCheckbox.checked) {
+    taxCheckbox.style.visibility = "visible";
+    taxLabel.style.visibility = "visible";
+  } else {
+       taxCheckbox.checked = false;
+    taxCheckbox.style.visibility = "hidden";
+    taxLabel.style.visibility = "hidden";
+  }
+
+  var customFieldsCheckbox = document.getElementById("custom_fields");
+  var configureLabel = document.getElementById("cf_config");
+  if (customFieldsCheckbox.checked) {
+    configureLabel.style.visibility = "visible";
+  } else {
+    configureLabel.style.visibility = "hidden";
+  }
+
+  var notificationsCheckbox = document.getElementById("notifications");
+  configureLabel = document.getElementById("notifications_config");
+  if (notificationsCheckbox.checked) {
+    configureLabel.style.visibility = "visible";
+  } else {
+    configureLabel.style.visibility = "hidden";
+  }
+}
+</script>
+
+{$forms.profileForm.open}
+
+{if $user->canManageTeam()}
+{include file="datetime_format_preview.tpl"}
+{/if}
+
+<table cellspacing="4" cellpadding="7" border="0">
+    <tr>
+      <td>
+        <table cellspacing="1" cellpadding="2" border="0">
+          <tr>
+            <td align="right" nowrap>{$i18n.label.person_name} (*):</td>
+            <td>{$forms.profileForm.name.control}</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.login} (*):</td>
+            <td>{$forms.profileForm.login.control}</td>
+          </tr>
+
+{if !$auth_external}
+          <tr>
+            <td align="right" nowrap>{$i18n.label.password} (*):</td>
+            <td>{$forms.profileForm.password1.control}</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.confirm_password} (*):</td>
+            <td>{$forms.profileForm.password2.control}</td>
+          </tr>
+{/if}
+
+          <tr>
+            <td align="right" nowrap>{$i18n.label.email}:</td>
+            <td>{$forms.profileForm.email.control}</td>
+          </tr>
+          <tr>
+            <td></td>
+            <td>{$i18n.label.required_fields}</td>
+          </tr>
+
+{if $user->canManageTeam()}
+          <tr>
+            <td colspan="2">&nbsp;</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.team_name}:</td>
+            <td>{$forms.profileForm.team_name.control}</td>
+          </tr>
+          <!-- <tr>
+            <td align="right">{$i18n.label.address}:</td>
+            <td>{$forms.profileForm.address.control}</td>
+          </tr> -->
+          <tr>
+            <td align="right">{$i18n.label.currency}:</td>
+            <td>{$forms.profileForm.currency.control}</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.lock_interval}:</td>
+            <td>{$forms.profileForm.lock_interval.control}</td>
+          </tr>
+          <tr>
+           <td align="right" nowrap>{$i18n.label.language}:</td>
+           <td>{$forms.profileForm.lang.control}</td>
+          </tr>
+          <tr>
+            <td align="right">{$i18n.label.decimal_mark}:</td>
+            <td>{$forms.profileForm.decimal_mark.control} <font id="decimal_preview" color="#777777">&nbsp;</font></td>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.date_format}:</td>
+            <td>{$forms.profileForm.format_date.control} <font id="date_format_preview" color="#777777">&nbsp;</font></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.time_format}:</td>
+            <td>{$forms.profileForm.format_time.control} <font id="time_format_preview" color="#777777">&nbsp;</font></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.label.week_start}:</td>
+            <td>{$forms.profileForm.start_week.control}</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.form.profile.tracking_mode}:</td>
+            <td>{$forms.profileForm.tracking_mode.control}</td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$i18n.form.profile.record_type}:</td>
+            <td>{$forms.profileForm.record_type.control}</td>
+          </tr>
+
+          {* initialize preview text *}
+          <script>
+            MakeFormatPreview("date_format_preview", document.getElementById("format_date"));
+            MakeFormatPreview("time_format_preview", document.getElementById("format_time"));
+            
+            function adjustDecimalPreview()
+            {
+              var mark = document.getElementById("decimal_mark").value;
+              var example = document.getElementById("decimal_preview");
+              example.innerHTML = "<i>3"+mark+"14</i>";
+            }
+            adjustDecimalPreview();
+          </script>
+
+          <tr>
+            <td>&nbsp;</td>
+            <td>&nbsp;</td>
+          </tr>
+
+          <tr>
+             <td colspan="2" class="sectionHeader">{$i18n.form.profile.plugins}</td>
+          </tr>
+          <tr><td>&nbsp;</td></tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.charts.control}</td>
+            <td><label for="charts">{$i18n.title.charts}</label></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.clients.control}</td>
+            <td><label for="clients">{$i18n.title.clients}</label> {$forms.profileForm.client_required.control} <span id="required_label"><label for="client_required">{$i18n.label.required}</label></span></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.invoices.control}</td>
+            <td><label for="invoices">{$i18n.title.invoices}</label></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.custom_fields.control}</td>
+            <td><label for="custom_fields">{$i18n.label.custom_fields}</label> <span id="cf_config"><a href="cf_custom_fields.php">{$i18n.label.configure}</a></span></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.expenses.control}</td>
+            <td><label for="expenses">{$i18n.title.expenses}</label> {$forms.profileForm.tax_expenses.control} <span id="tax_label"><label for="tax_expenses">{$i18n.label.tax}</label></span></td>
+          </tr>
+          <tr>
+            <td align="right" nowrap>{$forms.profileForm.notifications.control}</td>
+            <td><label for="notifications">{$i18n.title.notifications}</label> <span id="notifications_config"><a href="notifications.php">{$i18n.label.configure}</a></span></td>
+          </tr>
+{/if}
+
+          <tr>
+            <td colspan="2">&nbsp;</td>
+          </tr>
+          <tr>
+            <td colspan="2" height="50" align="center">{$forms.profileForm.btn_save.control}</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+</table>
+{$forms.profileForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/project_add.tpl b/WEB-INF/templates/project_add.tpl
new file mode 100644 (file)
index 0000000..2bd7f6f
--- /dev/null
@@ -0,0 +1,42 @@
+{$forms.projectForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.projectForm.project_name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.projectForm.description.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.users}:</td>
+          <td>{$forms.projectForm.users.control}</td>
+        </tr>
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.tasks}:</td>
+          <td>{$forms.projectForm.tasks.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.projectForm.btn_add.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/project_delete.tpl b/WEB-INF/templates/project_delete.tpl
new file mode 100644 (file)
index 0000000..bf1fc95
--- /dev/null
@@ -0,0 +1,18 @@
+{$forms.projectDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$project_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr><td colspan="2" align="center">&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$forms.projectDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.projectDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/project_edit.tpl b/WEB-INF/templates/project_edit.tpl
new file mode 100644 (file)
index 0000000..c94e475
--- /dev/null
@@ -0,0 +1,45 @@
+{$forms.projectForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align = "right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.projectForm.project_name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.projectForm.description.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.status}:</td>
+          <td>{$forms.projectForm.status.control}</td>
+        </tr>
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.users}:</td>
+          <td>{$forms.projectForm.users.control}</td>
+        </tr>
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right">{$i18n.label.tasks}:</td>
+          <td>{$forms.projectForm.tasks.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.projectForm.btn_save.control} {$forms.projectForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.projectForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/projects.tpl b/WEB-INF/templates/projects.tpl
new file mode 100644 (file)
index 0000000..8862d6a
--- /dev/null
@@ -0,0 +1,84 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+  {if $inactive_projects}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.projects.active_projects}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+  {if $active_projects}
+    {foreach $active_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+          <td><a href="project_edit.php?id={$project.id}">{$i18n.label.edit}</a></td>
+          <td><a href="project_delete.php?id={$project.id}">{$i18n.label.delete}</a></td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('project_add.php');" value="{$i18n.button.add_project}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_projects}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.projects.inactive_projects}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+    {foreach $inactive_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+          <td><a href="project_edit.php?id={$project.id}">{$i18n.label.edit}</a></td>
+          <td><a href="project_delete.php?id={$project.id}">{$i18n.label.delete}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('project_add.php');" value="{$i18n.button.add_project}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.description}</td>
+        </tr>
+  {if $active_projects}
+    {foreach $active_projects as $project}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$project.name|escape:'html'}</td>
+          <td>{$project.description|escape:'html'}</td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/register.tpl b/WEB-INF/templates/register.tpl
new file mode 100644 (file)
index 0000000..135230b
--- /dev/null
@@ -0,0 +1,47 @@
+{$forms.profileForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right" nowrap>{$i18n.label.team_name}:</td>
+          <td>{$forms.profileForm.team_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.currency}:</td>
+          <td>{$forms.profileForm.currency.control}</td>
+        </tr>            
+        <tr><td>&nbsp;</td></tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_name} (*):</td>
+          <td>{$forms.profileForm.manager_name.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.manager_login} (*):</td>
+          <td>{$forms.profileForm.manager_login.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.password} (*):</td>
+          <td>{$forms.profileForm.password1.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.confirm_password} (*):</td>
+          <td>{$forms.profileForm.password2.control}</td>
+        </tr>
+        <tr>
+          <td align="right" nowrap>{$i18n.label.email}:</td>
+          <td>{$forms.profileForm.manager_email.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr><td colspan="2">&nbsp;</td></tr>
+        <tr>
+          <td colspan="2" height="50" align="center">{$forms.profileForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.profileForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/report.tpl b/WEB-INF/templates/report.tpl
new file mode 100644 (file)
index 0000000..eb7ed47
--- /dev/null
@@ -0,0 +1,163 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+{$forms.reportForm.open}
+<table width="720">
+  <td valign="top">
+    <table border="0" cellpadding="3" cellspacing="1" width="100%">
+      <tr>
+        <td valign="top" class="sectionHeaderNoBorder" align="center">{$i18n.form.report.export} <a href="tofile.php?type=xml">XML</a> {$i18n.label.or} <a href="tofile.php?type=csv">CSV</a></td>
+      </tr>
+    </table>
+    <table border="0" cellpadding="3" cellspacing="1" width="100%">
+<!-- totals only report -->
+{if $bean->getAttribute('chtotalsonly')}
+      <tr>
+        <td class="tableHeader">{$group_by_header|escape:'html'}</td>
+        {if $bean->getAttribute('chduration')}<td class="tableHeaderCentered" width="5%">{$i18n.label.duration}</td>{/if}
+        {if $bean->getAttribute('chcost')}<td class="tableHeaderCentered" width="5%">{$i18n.label.cost}</td>{/if}
+      </tr>
+  {foreach $subtotals as $subtotal}
+      <tr class="rowReportSubtotal">
+        <td class="cellLeftAlignedSubtotal">{if $subtotal['name']}{$subtotal['name']|escape:'html'}{else}&nbsp;{/if}</td>
+        {if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotal['time']}</td>{/if}
+        {if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->canManageTeam() || $user->isClient()}{$subtotal['cost']}{else}{$subtotal['expenses']}{/if}</td>{/if}
+      </tr>
+  {/foreach}
+      <!-- print totals -->
+      <tr><td>&nbsp;</td></tr>
+      <tr class="rowReportSubtotal">
+        <td class="cellLeftAlignedSubtotal">{$i18n.label.total}</td>
+        {if $bean->getAttribute('chduration')}<td nowrap class="cellRightAlignedSubtotal">{$totals['time']}</td>{/if}
+        {if $bean->getAttribute('chcost')}<td nowrap class="cellRightAlignedSubtotal">{$user->currency|escape:'html'} {if $user->canManageTeam() || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}</td>{/if}
+      </tr>
+{else}
+<!-- normal report -->    
+      <tr>
+        <td class="tableHeader">{$i18n.label.date}</td>
+  {if $user->canManageTeam() || $user->isClient()}<td class="tableHeader">{$i18n.label.user}</td>{/if}
+  {if $bean->getAttribute('chclient')}<td class="tableHeader">{$i18n.label.client}</td>{/if}
+  {if $bean->getAttribute('chproject')}<td class="tableHeader">{$i18n.label.project}</td>{/if}
+  {if $bean->getAttribute('chtask')}<td class="tableHeader">{$i18n.label.task}</td>{/if}
+  {if $bean->getAttribute('chcf_1')}<td class="tableHeader">{$custom_fields->fields[0]['label']|escape:'html'}</td>{/if}
+  {if $bean->getAttribute('chstart')}<td class="tableHeaderCentered" width="5%">{$i18n.label.start}</td>{/if}
+  {if $bean->getAttribute('chfinish')}<td class="tableHeaderCentered" width="5%">{$i18n.label.finish}</td>{/if}
+  {if $bean->getAttribute('chduration')}<td class="tableHeaderCentered" width="5%">{$i18n.label.duration}</td>{/if}
+  {if $bean->getAttribute('chnote')}<td class="tableHeader">{$i18n.label.note}</td>{/if}
+  {if $bean->getAttribute('chcost')}<td class="tableHeaderCentered" width="5%">{$i18n.label.cost}</td>{/if}
+  {if $bean->getAttribute('chinvoice')}<td class="tableHeader">{$i18n.label.invoice}</td>{/if}    
+      </tr>
+  {foreach $report_items as $item}
+    <!-- print subtotal for a block of grouped values -->
+    {$cur_date = $item.date}      
+    {if $print_subtotals}
+      {$cur_grouped_by = $item.grouped_by}
+      {if $cur_grouped_by != $prev_grouped_by && !$first_pass}
+      <tr class="rowReportSubtotal">
+        <td class="cellLeftAlignedSubtotal">{$i18n.label.subtotal}
+        {if $user->canManageTeam() || $user->isClient()}<td class="cellLeftAlignedSubtotal">{if $group_by == 'user'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+        {if $bean->getAttribute('chclient')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'client'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+        {if $bean->getAttribute('chproject')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'project'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+        {if $bean->getAttribute('chtask')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'task'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+        {if $bean->getAttribute('chcf_1')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'cf_1'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+        {if $bean->getAttribute('chstart')}<td></td>{/if}
+        {if $bean->getAttribute('chfinish')}<td></td>{/if}
+        {if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotals[$prev_grouped_by]['time']}</td>{/if}
+        {if $bean->getAttribute('chnote')}<td></td>{/if}
+        {if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->canManageTeam() || $user->isClient()}{$subtotals[$prev_grouped_by]['cost']}{else}{$subtotals[$prev_grouped_by]['expenses']}{/if}</td>{/if}
+        {if $bean->getAttribute('chinvoice')}<td></td>{/if}
+      </tr>
+      <tr><td>&nbsp;</td></tr>
+      {/if}
+    {$first_pass = false} 
+    {/if}
+      <!--  print regular row --> 
+      {if $cur_date != $prev_date}
+        {if $report_row_class == 'rowReportItem'} {$report_row_class = 'rowReportItemAlt'} {else} {$report_row_class = 'rowReportItem'} {/if}
+      {/if}
+      <tr class="{$report_row_class}">
+        <td class="cellLeftAligned">{$item.date}</td>
+    {if $user->canManageTeam() || $user->isClient()}<td class="cellLeftAligned">{$item.user|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chclient')}<td class="cellLeftAligned">{$item.client|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chproject')}<td class="cellLeftAligned">{$item.project|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chtask')}<td class="cellLeftAligned">{$item.task|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chcf_1')}<td class="cellLeftAligned">{$item.cf_1|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chstart')}<td nowrap class="cellRightAligned">{$item.start}</td>{/if}
+    {if $bean->getAttribute('chfinish')}<td nowrap class="cellRightAligned">{$item.finish}</td>{/if}
+    {if $bean->getAttribute('chduration')}<td class="cellRightAligned">{$item.duration}</td>{/if}
+    {if $bean->getAttribute('chnote')}<td class="cellLeftAligned">{$item.note|escape:'html'}</td>{/if}
+    {if $bean->getAttribute('chcost')}<td class="cellRightAligned">{if $user->canManageTeam() || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}</td>{/if}
+    {if $bean->getAttribute('chinvoice')}
+        <td class="cellRightAligned">{$item.invoice|escape:'html'}</td>
+      {if $use_checkboxes}
+        {if 1 == $item.type}<td bgcolor="white"><input type="checkbox" name="log_id_{$item.id}"></td>{/if}
+        {if 2 == $item.type}<td bgcolor="white"><input type="checkbox" name="item_id_{$item.id}"></td>{/if}
+      {/if}
+    {/if}
+      </tr>
+    {$prev_date = $item.date}
+    {if $print_subtotals} {$prev_grouped_by = $item.grouped_by} {/if}
+  {/foreach}
+  <!-- print a terminating subtotal -->
+  {if $print_subtotals}      
+      <tr class="rowReportSubtotal">
+        <td class="cellLeftAlignedSubtotal">{$i18n.label.subtotal}
+    {if $user->canManageTeam() || $user->isClient()}<td class="cellLeftAlignedSubtotal">{if $group_by == 'user'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+    {if $bean->getAttribute('chclient')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'client'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+    {if $bean->getAttribute('chproject')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'project'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+    {if $bean->getAttribute('chtask')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'task'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+    {if $bean->getAttribute('chcf_1')}<td class="cellLeftAlignedSubtotal">{if $group_by == 'cf_1'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}</td>{/if}{/if}
+    {if $bean->getAttribute('chstart')}<td></td>{/if}
+    {if $bean->getAttribute('chfinish')}<td></td>{/if}
+    {if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$subtotals[$cur_grouped_by]['time']}</td>{/if}
+    {if $bean->getAttribute('chnote')}<td></td>{/if}
+    {if $bean->getAttribute('chcost')}<td class="cellRightAlignedSubtotal">{if $user->canManageTeam() || $user->isClient()}{$subtotals[$cur_grouped_by]['cost']}{else}{$subtotals[$cur_grouped_by]['expenses']}{/if}</td>{/if}
+    {if $bean->getAttribute('chinvoice')}<td></td>{/if}
+      </tr>
+  {/if}
+  <!-- print totals -->
+      <tr><td>&nbsp;</td></tr>
+      <tr class="rowReportSubtotal">
+        <td class="cellLeftAlignedSubtotal">{$i18n.label.total}</td>
+    {if $user->canManageTeam() || $user->isClient()}<td></td>{/if}
+    {if $bean->getAttribute('chclient')}<td></td>{/if}
+    {if $bean->getAttribute('chproject')}<td></td>{/if}
+    {if $bean->getAttribute('chtask')}<td></td>{/if}
+    {if $bean->getAttribute('chcf_1')}<td></td>{/if}
+    {if $bean->getAttribute('chstart')}<td></td>{/if}
+    {if $bean->getAttribute('chfinish')}<td></td>{/if}
+    {if $bean->getAttribute('chduration')}<td class="cellRightAlignedSubtotal">{$totals['time']}</td>{/if}
+    {if $bean->getAttribute('chnote')}<td></td>{/if}
+    {if $bean->getAttribute('chcost')}<td nowrap class="cellRightAlignedSubtotal">{$user->currency|escape:'html'} {if $user->canManageTeam() || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}</td>{/if}
+    {if $bean->getAttribute('chinvoice')}<td></td>{/if}
+      </tr>
+{/if}
+    </table>
+  </td>
+</tr>
+</table>
+{if $use_checkboxes && $report_items}
+<table width="720" cellspacing="4" cellpadding="4" border="0">
+  <tr>
+    <td align="right">
+      <table>
+        <tr><td>{$forms.reportForm.recent_invoice.control} {$forms.reportForm.btn_submit.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{/if}
+{$forms.reportForm.close}
+
+<table width="720" cellspacing="4" cellpadding="4" border="0">
+<tr>
+  <td align="center">
+  <table>
+  <tr>
+    <td><input type="button" onclick="chLocation('report_send.php');" value="{$i18n.button.send_by_email}"></td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl
new file mode 100644 (file)
index 0000000..7e4c90b
--- /dev/null
@@ -0,0 +1,301 @@
+<script>
+// We need a couple of array-like objects, one for associated task ids, another for task names.
+// For performance, and because associated arrays are frowned upon in JavaScript, we'll use a simple object
+// with properties for project tasks. Format:
+
+// obj_tasks.p325 = "100,101,302,303,304"; // Tasks ids for project 325 are "100,101,302,303,304".
+// obj_tasks.p408 = "100,302";  // Tasks ids for project 408 are "100,302".
+
+// Create an object for task ids.
+obj_tasks = {};
+var project_prefix = "p"; // Prefix for project property.
+var project_property;
+
+// Populate obj_tasks with task ids for each relevant project.
+{foreach $project_list as $project}
+  project_property = project_prefix + {$project.id};
+  obj_tasks[project_property] = "{$project.tasks}";
+{/foreach}
+
+// Prepare an array of task names.
+// Format: task_names[0] = Array(100, 'Coding'), task_names[1] = Array(302, 'Debugging'), etc...
+// First element = task_id, second element = task name.
+task_names = new Array();
+var idx = 0;
+{foreach $task_list as $task}
+  task_names[idx] = new Array({$task.id}, "{$task.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+       
+// empty_label is the mandatory top option in the tasks dropdown.
+empty_label = '{$i18n.dropdown.all|escape:'javascript'}';
+
+// inArray - determines whether needle is in haystack array.
+function inArray(needle, haystack) {
+  var length = haystack.length;
+  for(var i = 0; i < length; i++) {
+       if(haystack[i] == needle) return true;
+  }
+  return false;
+}
+       
+// The fillTaskDropdown function populates the task combo box with
+// tasks associated with a selected project_id.    
+function fillTaskDropdown(project_id) {
+  var str_task_ids;
+  // Get a string of comma-separated task ids.
+  if (project_id) {  
+    var property = "p" + project_id;
+    str_task_ids = obj_tasks[property];
+  }
+  if (str_task_ids) {
+    var task_ids = new Array(); // Array of task ids.
+    task_ids = str_task_ids.split(",");
+  }
+  
+  var dropdown = document.getElementById("task");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+  
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label, '', true);
+
+  // Populate the dropdown with associated tasks.
+  len = task_names.length;
+  var dropdown_idx = 0;
+  for (var i = 0; i < len; i++) {
+    if (!project_id) {
+      // No project is selected. Fill in all tasks.
+      dropdown.options[dropdown_idx+1] = new Option(task_names[i][1], task_names[i][0]);
+      dropdown_idx++;
+    } else if (str_task_ids) {
+      // Project is selected and has associated tasks. Fill them in.
+      if (inArray(task_names[i][0], task_ids)) {
+        dropdown.options[dropdown_idx+1] = new Option(task_names[i][1], task_names[i][0]);
+        dropdown_idx++;
+      }
+    } 
+  }
+
+  // If a previously selected item is still in dropdown - select it.
+  if (dropdown.options.length > 0) {
+    for (var i = 0; i < dropdown.options.length; i++) {
+      if (dropdown.options[i].value == selected_item)  {
+        dropdown.options[i].selected = true;
+      }
+    }
+  }
+}
+
+// Build JavaScript array for assigned projects out of passed in PHP array.
+var assigned_projects = new Array();
+{if $assigned_projects}
+  {foreach $assigned_projects as $user_id => $projects}
+    assigned_projects[{$user_id}] = new Array();
+       {if $projects}
+         {foreach $projects as $idx => $project_id}
+           assigned_projects[{$user_id}][{$idx}] = {$project_id};
+         {/foreach}
+    {/if}
+  {/foreach}
+{/if}
+       
+// selectAssignedUsers is called when a project is changed in project dropdown.
+// It selects users on the form who are assigned to this project.
+function selectAssignedUsers(project_id) {
+  var user_id;
+  var len;
+
+  for (var i = 0; i < document.reportForm.elements.length; i++) {
+    if ((document.reportForm.elements[i].type == 'checkbox') && (document.reportForm.elements[i].name == 'users[]')) {
+      user_id = document.reportForm.elements[i].value;
+      if (project_id)
+        document.reportForm.elements[i].checked = false;
+      else
+        document.reportForm.elements[i].checked = true;
+
+      if(assigned_projects[user_id] != undefined)
+        len = assigned_projects[user_id].length;
+      else
+        len = 0;
+
+      if (project_id != '')
+        for (var j = 0; j < len; j++) {
+          if (project_id == assigned_projects[user_id][j]) {
+            document.reportForm.elements[i].checked = true;
+            break;
+          }
+        }
+    }
+  }
+}
+
+// handleCheckboxes - unmarks and disables the "Totals only" checkbox when
+// "no grouping" is selected in the associated dropdown.
+// In future we need to improve this function and hide not relevant elements completely.
+function handleCheckboxes() {
+  var totalsOnlyCheckbox = document.getElementById("chtotalsonly");
+  if ("no_grouping" == document.getElementById("group_by").value) {
+       // Unmark and disable the "Totals only" checkbox.
+    totalsOnlyCheckbox.checked = false;
+    totalsOnlyCheckbox.disabled = true;
+  } else
+       totalsOnlyCheckbox.disabled = false;
+}
+</script>
+
+{$forms.reportForm.open}
+<div style="padding: 0 0 10 0;">
+  <table border="0" bgcolor="#efefef" width="720">
+    <tr>
+      <td>
+        <table cellspacing="1" cellpadding="3" border="0">
+          <tr>
+            <td>{$i18n.label.fav_report}:</td><td>{$forms.reportForm.favorite_report.control}</td>
+            <td>{$forms.reportForm.btn_generate.control}&nbsp;{$forms.reportForm.btn_delete.control}</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</div>
+
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td valign="top" colspan="2" align="center">
+      <table border="0" cellpadding="3">
+{if ((in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id)) || ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN))}          
+        <tr>
+  {if in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id)}<td><b>{$i18n.label.client}</b></td>{else}<td>&nbsp;</td>{/if}
+          <td>&nbsp;</td>
+  {if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)}<td><b>{$i18n.label.option}</b></td>{else}<td>&nbsp;</td>{/if}
+        </tr>
+        <tr>
+          <td>{$forms.reportForm.client.control}</td>
+          <td>&nbsp;</td>
+          <td>{$forms.reportForm.option.control}</td>
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}      
+        <tr>
+          <td><b>{$i18n.label.project}</b></td>
+          <td>&nbsp;</td>
+  {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+          <td><b>{$i18n.label.task}</b></td>
+  {/if}           
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td>{$forms.reportForm.project.control}</td>
+          <td>&nbsp;</td>
+  {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+          <td>{$forms.reportForm.task.control}</td>
+  {/if}
+        </tr>
+{/if}
+{if in_array('iv', explode(',', $user->plugins))} 
+        <tr>
+          <td><b>{$i18n.form.time.billable}</b></td>
+          <td>&nbsp;</td>
+          <td><b>{$i18n.label.invoice}</b></td>
+        </tr>
+        <tr valign="top">
+          <td>{$forms.reportForm.include_records.control}</td>
+          <td>&nbsp;</td>
+          <td>{$forms.reportForm.invoice.control}</td>
+        </tr>
+{/if}
+{if $user->canManageTeam() || $user->isClient()}
+        <tr>
+          <td colspan="3"><b>{$i18n.label.users}</b></td>
+        </tr>
+        <tr>
+          <td colspan="3">{$forms.reportForm.users.control}</td>
+        </tr>
+{/if}
+        <tr>
+          <td><b>{$i18n.form.reports.select_period}</b></td>
+          <td>&nbsp;</td>
+          <td><b>{$i18n.form.reports.set_period}</b></td>
+        </tr>
+        <tr valign="top">
+          <td>{$forms.reportForm.period.control}</td>
+          <td align="right">{$i18n.label.start_date}:</td>
+          <td>{$forms.reportForm.start_date.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td align="right">{$i18n.label.end_date}:</td>
+          <td>{$forms.reportForm.end_date.control}</td>
+        </tr>
+        <tr><td colspan="3"><b>{$i18n.form.reports.show_fields}</b></td></tr>
+        <tr>
+          <td colspan="3">
+            <table border="0" width="100%">
+{if in_array('cl', explode(',', $user->plugins)) || in_array('iv', explode(',', $user->plugins))}          
+              <tr>
+  {if in_array('cl', explode(',', $user->plugins))}
+                <td width="25%"><label>{$forms.reportForm.chclient.control}&nbsp;{$i18n.label.client}</label></td>
+  {/if}
+  {if ($user->canManageTeam() || $user->isClient()) && in_array('iv', explode(',', $user->plugins))}
+                <td width="25%"><label>{$forms.reportForm.chinvoice.control}&nbsp;{$i18n.label.invoice}</label></td>
+  {/if}
+              </tr>
+{/if}            
+              <tr>
+                <td width="25%">{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}<label>{$forms.reportForm.chproject.control}&nbsp;{$i18n.label.project}</label>{/if}</td>
+                <td width="25%">{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}<label>{$forms.reportForm.chstart.control}&nbsp;{$i18n.label.start}</label>{/if}</td>
+                <td width="25%"><label>{$forms.reportForm.chduration.control}&nbsp;{$i18n.label.duration}</label></td>
+{if ((($user->canManageTeam() || $user->isClient()) || in_array('ex', explode(',', $user->plugins))) && defined('COST_ON_REPORTS') && isTrue($smarty.const.COST_ON_REPORTS))}
+                  <td width="25%"><label>{$forms.reportForm.chcost.control}&nbsp;{$i18n.label.cost}</label></td>
+{else}
+                  <td></td>
+{/if}
+              </tr>
+              <tr>
+               <td>{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}<label>{$forms.reportForm.chtask.control}&nbsp;{$i18n.label.task}</label>{/if}</td>
+               <td>{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}<label>{$forms.reportForm.chfinish.control}&nbsp;{$i18n.label.finish}</label>{/if}</td>
+                <td><label>{$forms.reportForm.chnote.control}&nbsp;{$i18n.label.note}</label></td>
+{if ($custom_fields && $custom_fields->fields[0])}
+                <td><label>{$forms.reportForm.chcf_1.control}&nbsp;{$custom_fields->fields[0]['label']|escape:'html'}</label></td>
+{else}
+                <td></td>
+{/if}
+              </tr>
+            </table>
+          </td>
+        </tr>
+        <tr>
+          <td><b>{$i18n.form.reports.group_by}</b></td>
+        </tr>
+        <tr valign="top">
+          <td>{$forms.reportForm.group_by.control} <label>{$forms.reportForm.chtotalsonly.control} {$i18n.form.reports.totals_only}</label></td>
+        </tr>
+      </table>
+      
+<div style="padding: 10 0 10 0;">
+  <table border="0" bgcolor="#efefef" width="720">
+    <tr>
+      <td align="center">
+        <table cellspacing="1" cellpadding="3" border="0">
+          <tr>
+            <td>{$i18n.form.reports.save_as_favorite}:</td><td>{$forms.reportForm.new_fav_report.control}</td>
+            <td>{$forms.reportForm.btn_save.control}</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</div>
+
+      <table border="0" cellpadding="3" width="100%">
+        <tr><td colspan="3" height="50" align="center">{$forms.reportForm.btn_generate.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.reportForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/task_add.tpl b/WEB-INF/templates/task_add.tpl
new file mode 100644 (file)
index 0000000..9b568c8
--- /dev/null
@@ -0,0 +1,33 @@
+{$forms.taskForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.taskForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.taskForm.description.control}</td>
+        </tr>
+        <tr valign="top">
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.taskForm.projects.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.taskForm.btn_submit.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/task_delete.tpl b/WEB-INF/templates/task_delete.tpl
new file mode 100644 (file)
index 0000000..1b2d527
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.taskDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$task_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.taskDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.taskDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/task_edit.tpl b/WEB-INF/templates/task_edit.tpl
new file mode 100644 (file)
index 0000000..b82a093
--- /dev/null
@@ -0,0 +1,37 @@
+{$forms.taskForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="1" cellpadding="2" border="0">
+        <tr>
+          <td align="right">{$i18n.label.thing_name} (*):</td>
+          <td>{$forms.taskForm.name.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.description}:</td>
+          <td>{$forms.taskForm.description.control}</td>
+        </tr>
+        <tr>
+          <td align = "right">{$i18n.label.status}:</td>
+          <td>{$forms.taskForm.status.control}</td>
+        </tr>
+        <tr valign="top">
+          <td align="right">{$i18n.label.projects}:</td>
+          <td>{$forms.taskForm.projects.control}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>{$i18n.label.required_fields}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td>&nbsp;</td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center" height="50">{$forms.taskForm.btn_save.control} {$forms.taskForm.btn_copy.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.taskForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/tasks.tpl b/WEB-INF/templates/tasks.tpl
new file mode 100644 (file)
index 0000000..54d5be5
--- /dev/null
@@ -0,0 +1,84 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+  {if $inactive_tasks}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.tasks.active_tasks}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+  {if $active_tasks}
+    {foreach $active_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+          <td><a href="task_edit.php?id={$task.id}">{$i18n.label.edit}</a></td>
+          <td><a href="task_delete.php?id={$task.id}">{$i18n.label.delete}</a></td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('task_add.php');" value="{$i18n.button.add_task}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_tasks}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.tasks.inactive_tasks}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.thing_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.description}</td>
+          <td class="tableHeader">{$i18n.label.edit}</td>
+          <td class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+    {foreach $inactive_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+          <td><a href="task_edit.php?id={$task.id}">{$i18n.label.edit}</a></td>
+          <td><a href="task_delete.php?id={$task.id}">{$i18n.label.delete}</a></td>
+        </tr>
+    {/foreach}
+      </table>
+
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('task_add.php');" value="{$i18n.button.add_task}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td class="tableHeader">{$i18n.label.thing_name}</td>
+          <td class="tableHeader">{$i18n.label.description}</td>
+        </tr>
+  {if $active_tasks}
+    {foreach $active_tasks as $task}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$task.name|escape:'html'}</td>
+          <td>{$task.description|escape:'html'}</td>
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+  {/if}
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates/time.tpl b/WEB-INF/templates/time.tpl
new file mode 100644 (file)
index 0000000..9367065
--- /dev/null
@@ -0,0 +1,361 @@
+<script>
+// We need a few arrays to populate project and task dropdowns.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// When project selection changes, the task dropdown must be repopulated similarly.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+// task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project.
+// task_names[100] = "Coding";            // Task name.
+
+// Prepare an array of project ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Prepare an array of task ids for projects.
+task_ids = new Array();
+{foreach $project_list as $project}
+  task_ids[{$project.id}] = "{$project.tasks}";
+{/foreach}
+// Prepare an array of task names.
+task_names = new Array();
+{foreach $task_list as $task}
+  task_names[{$task.id}] = "{$task.name|escape:'javascript'}";
+{/foreach}
+
+// Mandatory top options for project and task dropdowns.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+empty_label_task = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The populateDropdowns function populates the "project" and "task" dropdown controls
+// with relevant values.
+function fillDropdowns() {
+  if(document.body.contains(document.timeRecordForm.client))
+    fillProjectDropdown(document.timeRecordForm.client.value);
+
+  fillTaskDropdown(document.timeRecordForm.project.value);
+}
+
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected client (client id is passed here as id).    
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  var project_reset = true;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  }
+
+  // If project selection was reset - clear the tasks dropdown.
+  if (project_reset) {
+    dropdown = document.getElementById("task");
+    dropdown.length = 0;
+    dropdown.options[0] = new Option(empty_label_task, '', true);
+  }
+}
+
+// The fillTaskDropdown function populates the task combo box with
+// tasks associated with a selected project (project id is passed here as id).    
+function fillTaskDropdown(id) {
+  var str_ids = task_ids[id];
+
+  var dropdown = document.getElementById("task");
+  if (dropdown == null) return; // Nothing to do.
+  
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+  
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_task, '', true);
+
+  // Populate the dropdown from the task_names array.
+  if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    var idx = 1;
+    for (var i = 0; i < len; i++) {
+      var t_id = ids[i];
+      if (task_names[t_id]) {
+        dropdown.options[idx] = new Option(task_names[t_id], t_id);
+        idx++;
+      }
+    }
+
+    // If a previously selected item is still in dropdown - select it.
+       if (dropdown.options.length > 0) {
+      for (var i = 0; i < dropdown.options.length; i++) {
+        if (dropdown.options[i].value == selected_item)  {
+          dropdown.options[i].selected = true;
+        }
+      }
+    }
+  }
+}
+
+// The formDisable function disables some fields depending on what we have in other fields.
+function formDisable(formField) {
+  formFieldValue = eval("document.timeRecordForm." + formField + ".value");
+  formFieldName = eval("document.timeRecordForm." + formField + ".name");
+
+  if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = false;
+    x.style.background = "white";
+  }
+
+  if ((formFieldValue != "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+       x.value = "";
+       x.disabled = true;
+       x.style.background = "#e9e9e9";
+       var x = eval("document.timeRecordForm.finish");
+       x.value = "";
+       x.disabled = true;
+       x.style.background = "#e9e9e9";
+  }
+
+  if ((formFieldValue == "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+    x.disabled = false;
+    x.style.background = "white";
+    var x = eval("document.timeRecordForm.finish");
+    x.disabled = false;
+    x.style.background = "white";
+  }
+}
+
+// The setNow function fills a given field with current time.
+function setNow(formField) {
+  var x = eval("document.timeRecordForm.start");
+  x.disabled = false;
+  x.style.background = "white";
+  var x = eval("document.timeRecordForm.finish");
+  x.disabled = false;
+  x.style.background = "white";
+  var today = new Date();
+  var time_format = '{$user->time_format}';
+  var obj = eval("document.timeRecordForm." + formField);
+  obj.value = today.strftime(time_format);
+  formDisable(formField);
+}
+
+function get_date() {
+  var date = new Date();
+  return date.strftime("%Y-%m-%d");
+}
+
+function get_time() {
+  var date = new Date();
+  return date.strftime("%H:%M");
+}
+</script>
+
+<style>
+.not_billable td {
+  color: #ff6666;
+}
+</style>
+
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="0" border="0">
+  <tr>
+    <td valign="top">
+      <table>
+{if $on_behalf_control}
+        <tr>
+          <td align="right">{$i18n.label.user}:</td>
+          <td>{$forms.timeRecordForm.onBehalfUser.control}</td>
+        </tr>
+{/if}
+{if in_array('cl', explode(',', $user->plugins))}
+        <tr>
+          <td align="right">{$i18n.label.client}{if in_array('cm', explode(',', $user->plugins))} (*){/if}:</td>
+          <td>{$forms.timeRecordForm.client.control}</td>
+        </tr>
+{/if}
+{if in_array('iv', explode(',', $user->plugins))}
+        <tr>
+          <td align="right">&nbsp;</td>
+          <td><label>{$forms.timeRecordForm.billable.control}{$i18n.form.time.billable}</label></td>
+        </tr>
+{/if}
+{if ($custom_fields && $custom_fields->fields[0])}
+        <tr>
+          <td align="right">{$custom_fields->fields[0]['label']|escape:'html'}{if $custom_fields->fields[0]['required']} (*){/if}:</td><td>{$forms.timeRecordForm.cf_1.control}</td>
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.project} (*):</td>
+          <td>{$forms.timeRecordForm.project.control}</td>
+        </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <tr>
+          <td align="right">{$i18n.label.task} (*):</td>
+          <td>{$forms.timeRecordForm.task.control}</td>
+        </tr>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+        <tr>
+          <td align="right">{$i18n.label.start}:</td>
+          <td>{$forms.timeRecordForm.start.control}&nbsp;<input onclick="setNow('start');" type="button" tabindex="-1" value="{$i18n.button.now}"></td>
+        </tr>
+        <tr>
+          <td align="right">{$i18n.label.finish}:</td>
+          <td>{$forms.timeRecordForm.finish.control}&nbsp;<input onclick="setNow('finish');" type="button" tabindex="-1" value="{$i18n.button.now}"></td>
+        </tr>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+        <tr>
+          <td align="right">{$i18n.label.duration}:</td>
+          <td>{$forms.timeRecordForm.duration.control}&nbsp;{$i18n.form.time.duration_format}</td>
+        </tr>
+{/if}
+      </table>
+    </td>
+    <td valign="top">
+      <table>
+        <tr><td>{$forms.timeRecordForm.date.control}</td></tr>
+      </table>
+    </td>
+  </tr>
+</table>
+
+<table>
+  <tr>
+    <td align="right">{$i18n.label.note}:</td>
+    <td align="left">{$forms.timeRecordForm.note.control}</td>
+  </tr>
+  <tr>
+    <td align="center" colspan="2">{$forms.timeRecordForm.btn_submit.control}</td>
+  </tr>
+</table>
+
+<table width="720">
+<tr>
+  <td valign="top">
+    {if $time_records}
+      <table border='0' cellpadding='3' cellspacing='1' width="100%">
+      <tr>
+{if in_array('cl', explode(',', $user->plugins))}
+        <td width="20%" class="tableHeader">{$i18n.label.client}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.project}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td class="tableHeader">{$i18n.label.task}</td>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+        <td width="5%" class="tableHeader" align='right'>{$i18n.label.start}</td>
+        <td width="5%" class="tableHeader" align='right'>{$i18n.label.finish}</td>
+{/if}
+        <td width="5%" class="tableHeader">{$i18n.label.duration}</td>
+        <td class="tableHeader">{$i18n.label.note}</td>
+        <td width="5%" class="tableHeader">{$i18n.label.edit}</td>
+      </tr>
+      {foreach $time_records as $record}
+      <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}" {if !$record.billable} class="not_billable" {/if}>
+{if in_array('cl', explode(',', $user->plugins))}
+        <td valign='top'>{$record.client|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign='top'>{$record.project|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+        <td valign='top'>{$record.task|escape:'html'}</td>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+        <td nowrap align='right' valign='top'>{if $record.start}{$record.start}{else}&nbsp;{/if}</td>
+        <td nowrap align='right' valign='top'>{if $record.finish}{$record.finish}{else}&nbsp;{/if}</td>
+{/if}
+        <td align='right' valign='top'>{if $record.duration <> '0:00'}{$record.duration}{else}<font color="#ff0000">{$i18n.form.time.uncompleted}</font>{/if}</td>
+        <td valign='top'>{if $record.comment}{$record.comment|escape:'html'}{else}&nbsp;{/if}</td>
+        <td valign='top' align='center'>
+        {if $record.invoice_id}
+          &nbsp;
+        {else}
+          <a href='time_edit.php?id={$record.id}'>{$i18n.label.edit}</a>
+          {if $record.duration == '0:00'}
+          <input type='hidden' name='record_id' value='{$record.id}'>
+          <input type='hidden' name='browser_date' value=''>
+          <input type='hidden' name='browser_time' value=''>
+          <input type='submit' id='btn_stop' name='btn_stop' onclick='browser_date.value=get_date();browser_time.value=get_time()' value='{$i18n.button.stop}'>
+          {/if}          
+        {/if}
+        </td>
+      </tr>
+      {/foreach}
+         </table>
+    {/if}
+  </td>
+</tr>
+</table>
+{if $time_records}
+<table cellpadding="3" cellspacing="1" width="720">
+  <tr>
+    <td align="left">{$i18n.label.week_total}: {$week_total}</td>
+    <td align="right">{$i18n.label.day_total}: {$day_total}</td>
+  </tr>
+</table>
+{/if}
+{$forms.timeRecordForm.close}
+
+
diff --git a/WEB-INF/templates/time_delete.tpl b/WEB-INF/templates/time_delete.tpl
new file mode 100644 (file)
index 0000000..946a21e
--- /dev/null
@@ -0,0 +1,50 @@
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="7" border="0" width="720">
+<tr>
+  <td>
+  <table border='0' cellpadding='3' cellspacing='1' width="100%">
+  <tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td class="tableHeader" align="center">{$i18n.label.project}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td class="tableHeader" align="center">{$i18n.label.task}</td>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <td class="tableHeader" align="center">{$i18n.label.start}</td>
+    <td class="tableHeader" align="center">{$i18n.label.finish}</td>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <td class="tableHeader" align="center">{$i18n.label.duration}</td>
+{/if}
+       <td class="tableHeader" align="center">{$i18n.label.note}</td>
+  </tr>
+  <tr bgcolor="{cycle values="#f5f5f5,#ccccce"}">
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td>{$time_rec.project_name|escape:'html'}</td>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <td>{$time_rec.task_name|escape:'html'}</td>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <td align="right">{if $time_rec.start}{$time_rec.start}{else}&nbsp;{/if}</td>
+    <td align="right">{if $time_rec.finish<>$time_rec.start}{$time_rec.finish}{else}&nbsp;{/if}</td>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <td align="right">{if $time_rec.duration<>'0:00'}{$time_rec.duration}{else}<font color="#ff0000">{$i18n.form.time.uncompleted}</font>{/if}</td>
+{/if}
+    <td>{if $time_rec.comment}{$time_rec.comment|escape:'html'}{else}&nbsp;{/if}</td>
+  </tr>
+  </table>
+  <table width="100%">
+  <tr>
+    <td align="center">&nbsp;</td>
+  </tr>
+  <tr>
+    <td align="center">{$forms.timeRecordForm.delete_button.control}&nbsp;&nbsp;{$forms.timeRecordForm.cancel_button.control}</td>
+  </tr>
+  </table>
+  </td>
+</tr>
+</table>
+{$forms.timeRecordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/time_edit.tpl b/WEB-INF/templates/time_edit.tpl
new file mode 100644 (file)
index 0000000..ba8974f
--- /dev/null
@@ -0,0 +1,275 @@
+<script>
+// We need a few arrays to populate project and task dropdowns.
+// When client selection changes, the project dropdown must be re-populated with only relevant projects.
+// When project selection changes, the task dropdown must be repopulated similarly.
+// Format:
+// project_ids[143] = "325,370,390,400";  // Comma-separated list of project ids for client.
+// project_names[325] = "Time Tracker";   // Project name.
+// task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project.
+// task_names[100] = "Coding";            // Task name.
+
+//Prepare an array of projects ids for clients.
+project_ids = new Array();
+{foreach $client_list as $client}
+  project_ids[{$client.id}] = "{$client.projects}";
+{/foreach}
+// Prepare an array of project names.
+project_names = new Array();
+{foreach $project_list as $project}
+  project_names[{$project.id}] = "{$project.name|escape:'javascript'}";
+{/foreach}
+// We'll use this array to populate project dropdown when client is not selected.
+var idx = 0;
+projects = new Array();
+{foreach $project_list as $project}
+  projects[idx] = new Array("{$project.id}", "{$project.name|escape:'javascript'}");
+  idx++;
+{/foreach}
+
+// Prepare an array of task ids for projects.
+task_ids = new Array();
+{foreach $project_list as $project}
+  task_ids[{$project.id}] = "{$project.tasks}";
+{/foreach}
+// Prepare an array of task names.
+task_names = new Array();
+{foreach $task_list as $task}
+  task_names[{$task.id}] = "{$task.name|escape:'javascript'}";
+{/foreach}
+
+// Mandatory top options for project and task dropdowns.
+empty_label_project = '{$i18n.dropdown.select|escape:'javascript'}';
+empty_label_task = '{$i18n.dropdown.select|escape:'javascript'}';
+
+// The populateDropdowns function populates the "project" and "task" dropdown controls
+// with relevant values.
+function fillDropdowns() {
+  if(document.body.contains(document.timeRecordForm.client))
+    fillProjectDropdown(document.timeRecordForm.client.value);
+
+  fillTaskDropdown(document.timeRecordForm.project.value);
+}
+       
+// The fillProjectDropdown function populates the project combo box with
+// projects associated with a selected clientt (client id is passed here as id).
+function fillProjectDropdown(id) {
+  var str_ids = project_ids[id];
+
+  var dropdown = document.getElementById("project");
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  var project_reset = true;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_project, '', true);
+
+  // Populate project dropdown.
+  if (!id) {
+    // If we are here, client is not selected.
+       var len = projects.length;
+    for (var i = 0; i < len; i++) {
+      dropdown.options[i+1] = new Option(projects[i][1], projects[i][0]);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  } else if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    for (var i = 0; i < len; i++) {
+      var p_id = ids[i];
+      dropdown.options[i+1] = new Option(project_names[p_id], p_id);
+      if (dropdown.options[i+1].value == selected_item)  {
+        dropdown.options[i+1].selected = true;
+        project_reset = false;
+      }
+    }
+  }
+
+  // If project selection was reset - clear the tasks dropdown.
+  if (project_reset) {
+    dropdown = document.getElementById("task");
+    dropdown.length = 0;
+    dropdown.options[0] = new Option(empty_label_task, '', true);
+  }
+}
+
+// The fillTaskDropdown function populates the task combo box with
+// tasks associated with a selected project (project id is passed here as id).
+function fillTaskDropdown(id) {
+  var str_ids = task_ids[id];
+
+  var dropdown = document.getElementById("task");
+  if (dropdown == null) return; // Nothing to do.
+  
+  // Determine previously selected item.
+  var selected_item = dropdown.options[dropdown.selectedIndex].value;
+
+  // Remove existing content.
+  dropdown.length = 0;
+  // Add mandatory top option.
+  dropdown.options[0] = new Option(empty_label_task, '', true);
+
+  // Populate the dropdown from the task_names array.
+  if (str_ids) {
+    var ids = new Array();
+    ids = str_ids.split(",");
+    var len = ids.length;
+
+    var idx = 1;
+    for (var i = 0; i < len; i++) {
+      var t_id = ids[i];
+      if (task_names[t_id]) {
+        dropdown.options[idx] = new Option(task_names[t_id], t_id);
+        idx++;
+      }
+    }
+
+    // If a previously selected item is still in dropdown - select it.
+    if (dropdown.options.length > 0) {
+      for (var i = 0; i < dropdown.options.length; i++) {
+        if (dropdown.options[i].value == selected_item)  {
+          dropdown.options[i].selected = true;
+        }
+      }
+    }
+  }
+}
+
+// The formDisable function disables some fields depending on what we have in other fields.
+function formDisable(formField) {
+  var formFieldValue = eval("document.timeRecordForm." + formField + ".value");
+  var formFieldName = eval("document.timeRecordForm." + formField + ".name");
+
+  if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) {
+    var x = eval("document.timeRecordForm.duration");
+    x.value = "";
+    x.disabled = false;
+    x.style.background = "white";
+  }
+
+  if ((formFieldValue != "") && (formFieldName == "duration")) {
+    var x = eval("document.timeRecordForm.start");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+    var x = eval("document.timeRecordForm.finish");
+    x.value = "";
+    x.disabled = true;
+    x.style.background = "#e9e9e9";
+  }
+
+  if ((formFieldValue == "") && (formFieldName == "duration")) {
+       var x = eval("document.timeRecordForm.start");
+       x.disabled = false;
+       x.style.background = "white";
+       var x = eval("document.timeRecordForm.finish");
+       x.disabled = false;
+       x.style.background = "white";
+  }
+}
+
+// The setNow function fills a given field with current time.
+function setNow(formField) {
+  var x = eval("document.timeRecordForm.start");
+  x.disabled = false;
+  x.style.background = "white";
+  var x = eval("document.timeRecordForm.finish");
+  x.disabled = false;
+  x.style.background = "white";
+  var today = new Date();
+  var time_format = '{$user->time_format}';
+  var obj = eval("document.timeRecordForm." + formField);
+  obj.value = today.strftime(time_format);
+  formDisable(formField);
+}
+</script>
+
+{$forms.timeRecordForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+<tr>
+  <td>
+  <table width = "100%">
+  <tr>
+       <td valign="top">
+    <table border="0">
+{if in_array('cl', explode(',', $user->plugins))}
+    <tr>
+      <td align="right">{$i18n.label.client}{if in_array('cm', explode(',', $user->plugins))} (*){/if}:</td>
+      <td>{$forms.timeRecordForm.client.control}</td>
+    </tr>
+{/if}
+{if in_array('iv', explode(',', $user->plugins))}
+    <tr>
+      <td align="right">&nbsp;</td>
+      <td><label>{$forms.timeRecordForm.billable.control}{$i18n.form.time.billable}</label></td>
+    </tr>
+{/if}
+{if ($custom_fields && $custom_fields->fields[0])} 
+    <tr>
+      <td align="right">{$custom_fields->fields[0]['label']|escape:'html'}{if $custom_fields->fields[0]['required']} (*){/if}:</td><td>{$forms.timeRecordForm.cf_1.control}</td>
+    </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr>
+      <td align="right">{$i18n.label.project} (*):</td>
+      <td>{$forms.timeRecordForm.project.control}</td>
+    </tr>
+{/if}
+{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr>
+      <td align="right">{$i18n.label.task} (*):</td>
+      <td>{$forms.timeRecordForm.task.control}</td>
+    </tr>
+{/if}
+{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr>
+      <td align="right">{$i18n.label.start}:</td>
+      <td>{$forms.timeRecordForm.start.control}&nbsp;<input onclick="setNow('start');" type="button" tabindex="-1" value="{$i18n.button.now}"></td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.finish}:</td>
+      <td>{$forms.timeRecordForm.finish.control}&nbsp;<input onclick="setNow('finish');" type="button" tabindex="-1" value="{$i18n.button.now}"></td>
+    </tr>
+{/if}
+{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}
+    <tr>
+      <td align="right">{$i18n.label.duration}:</td>
+      <td>{$forms.timeRecordForm.duration.control}&nbsp;{$i18n.form.time.duration_format}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.label.date}:</td>
+      <td>{$forms.timeRecordForm.date.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.note}:</td>
+      <td>{$forms.timeRecordForm.note.control}</td>
+    </tr>
+    <tr>
+      <td colspan="2">&nbsp;</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td align="left">{$forms.timeRecordForm.btn_save.control} {$forms.timeRecordForm.btn_copy.control} {$forms.timeRecordForm.btn_delete.control}</td>
+    </tr>
+    </table>
+    </td>
+    </tr>
+  </table>
+  </td>
+  </tr>
+</table>
+{$forms.timeRecordForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/user_add.tpl b/WEB-INF/templates/user_add.tpl
new file mode 100644 (file)
index 0000000..144a8e9
--- /dev/null
@@ -0,0 +1,83 @@
+<script>
+// The setDefaultRate function sets / unsets default rate for a project
+// when a corresponding checkbox is ticked.
+function setDefaultRate(element) {
+  var default_rate = document.userForm.rate.value;
+  if (default_rate == '') {
+    // No default rate, nothing to do!
+    return;
+  }
+  // Iterate through elements of the form to find and set the project rate. 
+  for (var i = 0; i < userForm.elements.length; i++) {
+    if ((userForm.elements[i].type == 'text') && (userForm.elements[i].name == ('rate_'+element.value))) {
+      if (element.checked) {
+        userForm.elements[i].value = default_rate;
+      } else {
+        userForm.elements[i].value = '';
+      }
+      break; // Element is found and set, nothing more to do, break out of the loop.
+    }
+  }
+}
+
+// handleClientControl - controls visibility of the client dropdown depending on the selected user role.
+// We need to show it only when the "Client" user role is selected.
+function handleClientControl() {
+  var clientControl = document.getElementById("client");
+  if ("16" == document.getElementById("role").value)
+    clientControl.style.visibility = "visible";
+  else
+    clientControl.style.visibility = "hidden";
+}
+</script>
+
+{$forms.userForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <table cellspacing="1" cellpadding="2" border="0">
+    <tr>
+      <td align="right">{$i18n.label.person_name} (*):</td>
+      <td>{$forms.userForm.name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.login} (*):</td>
+      <td>{$forms.userForm.login.control}</td>
+    </tr>
+{if !$auth_external}
+    <tr>
+      <td align="right">{$i18n.label.password} (*):</td>
+      <td>{$forms.userForm.pas1.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.confirm_password} (*):</td>
+      <td>{$forms.userForm.pas2.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right" nowrap>{$i18n.label.email}:</td>
+      <td>{$forms.userForm.email.control}</td>
+    </tr>
+{if $user->isManager()}
+    <tr>
+      <td align="right">{$i18n.form.users.role}:</td>
+      <td>{$forms.userForm.role.control} {$forms.userForm.client.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.form.users.default_rate}&nbsp;(0{$user->decimal_mark}00):</td>
+      <td>{$forms.userForm.rate.control}</td>
+    </tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr valign="top">
+      <td align="right">{$i18n.label.projects}:</td>
+      <td>{$forms.userForm.projects.control}</td>
+    </tr>
+    <tr>
+      <td colspan="2" align="center">{$i18n.label.required_fields}</td>
+    </tr>
+{/if}
+    <tr>
+      <td colspan="2" align="center" height="50">{$forms.userForm.btn_submit.control}</td>
+    </tr>
+  </table>
+</table>
+{$forms.userForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/user_delete.tpl b/WEB-INF/templates/user_delete.tpl
new file mode 100644 (file)
index 0000000..291dc4b
--- /dev/null
@@ -0,0 +1,20 @@
+{$forms.userDeleteForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <tr>
+    <td>
+      <table cellspacing="0" cellpadding="0" border="0">
+        <tr>
+          <td colspan="2" align="center"><b>{$user_to_delete|escape:'html'}</b></td>
+        </tr>
+        <tr>
+          <td colspan="2" align="center">&nbsp;</td>
+        </tr>
+        <tr>
+          <td align="right">{$forms.userDeleteForm.btn_delete.control}&nbsp;</td>
+          <td align="left">&nbsp;{$forms.userDeleteForm.btn_cancel.control}</td>
+        </tr>
+      </table>
+    </td>
+  </tr>
+</table>
+{$forms.userDeleteForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/user_edit.tpl b/WEB-INF/templates/user_edit.tpl
new file mode 100644 (file)
index 0000000..5fa9869
--- /dev/null
@@ -0,0 +1,112 @@
+<script>
+// Prepare an array of rates.
+// Format: project_rates[0] = Array(100, '25.00'), project_rates[1] = Array(120, '30.00'), etc...
+// First element = project_id, second element = rate for project. Quotes needed for string representation of rates.
+project_rates = new Array();
+var idx = 0;
+{foreach $rates as $rate}
+project_rates[idx] = new Array({$rate.id}, '{$rate.rate}');
+idx++;
+{/foreach}
+
+// getRate - returns a rate for the project. If rate was set for user previously we'll get this old rate
+// if project time entries for user exists. Otherwise return user default rate.
+function getRate(project_id) {
+  var length = project_rates.length;
+  for(var i = 0; i < length; i++) {
+       if(project_rates[i][0] == project_id) {
+         return project_rates[i][1];
+       }
+  }
+  var default_rate = document.userForm.rate.value;
+  return default_rate;
+}
+
+// The setRate function sets / unsets user rate for a project when a corresponding checkbox is ticked.
+function setRate(element) {
+  var default_rate = document.userForm.rate.value;
+  if (default_rate == '') {
+    // No default rate, nothing to do!
+    return;
+  }
+  // Iterate through elements of the form to find and set the project rate. 
+  for (var i = 0; i < userForm.elements.length; i++) {
+    if ((userForm.elements[i].type == 'text') && (userForm.elements[i].name == ('rate_'+element.value))) {
+      if (element.checked) {
+       userForm.elements[i].value = getRate(element.value);
+      } else {
+        userForm.elements[i].value = '';
+      }
+      break; // Element is found and set, nothing more to do, break out of the loop.
+    }
+  }
+}
+
+// handleClientControl - controls visibility of the client dropdown depending on the selected user role.
+// We need to show it only when the "Client" user role is selected.
+function handleClientControl() {
+  var clientControl = document.getElementById("client");
+  if ("16" == document.getElementById("role").value)
+       clientControl.style.visibility = "visible";
+  else
+       clientControl.style.visibility = "hidden";
+}
+</script>
+
+{$forms.userForm.open}
+<table cellspacing="4" cellpadding="7" border="0">
+  <table cellspacing="1" cellpadding="2" border="0">
+    <tr>
+      <td align="right">{$i18n.label.person_name} (*):</td>
+      <td>{$forms.userForm.name.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.login} (*):</td>
+      <td>{$forms.userForm.login.control}</td>
+    </tr>
+{if !$auth_external}
+    <tr>
+      <td align="right">{$i18n.label.password} (*):</td>
+      <td>{$forms.userForm.pas1.control}</td>
+    </tr>
+    <tr>
+      <td align="right">{$i18n.label.confirm_password} (*):</td>
+      <td>{$forms.userForm.pas2.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right" nowrap>{$i18n.label.email}:</td>
+      <td>{$forms.userForm.email.control}</td>
+    </tr>
+{if $user->isManager() && ($user->id != $user_id)}
+    <tr>
+      <td align="right">{$i18n.form.users.role}:</td>
+      <td>{$forms.userForm.role.control} {$forms.userForm.client.control}</td>
+    </tr>
+{/if}
+{* Prohibit deactivating team manager. Deactivating others is ok. *}
+{if $user->canManageTeam() && !($user->isManager() && $user->id == $user_id)}
+    <tr>
+      <td align="right">{$i18n.label.status}:</td>
+      <td>{$forms.userForm.status.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td align="right">{$i18n.form.users.default_rate}&nbsp;(0{$user->decimal_mark}00):</td>
+      <td>{$forms.userForm.rate.control}</td>
+    </tr>
+{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}
+    <tr valign="top">
+      <td align="right">{$i18n.label.projects}:</td>
+      <td>{$forms.userForm.projects.control}</td>
+    </tr>
+{/if}
+    <tr>
+      <td colspan="2" align="center">{$i18n.label.required_fields}</td>
+    </tr>
+    <tr>
+      <td colspan="2" align="center" height="50">{$forms.userForm.btn_submit.control}</td>
+    </tr>
+  </table>
+</table>
+{$forms.userForm.close}
\ No newline at end of file
diff --git a/WEB-INF/templates/users.tpl b/WEB-INF/templates/users.tpl
new file mode 100644 (file)
index 0000000..f343f9c
--- /dev/null
@@ -0,0 +1,127 @@
+<script>
+  function chLocation(newLocation) { document.location = newLocation; }
+</script>
+
+<table cellspacing="0" cellpadding="7" border="0" width="720">
+  <tr>
+    <td valign="top">
+{if $user->canManageTeam()}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+  {if $inactive_users}
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.users.active_users}</td></tr>
+  {/if}
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td width="10%" class="tableHeader">{$i18n.form.users.role}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.edit}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+  {if $active_users}
+    {foreach $active_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+      {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+      {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+      {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+      {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+      {/if}
+      {if $user->isManager()}
+          <!-- Manager can edit everybody. -->
+          <td><a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a></td>
+          <td>{if $smarty.const.ROLE_MANAGER != $u.role || $can_delete_manager}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {else}
+          <!--  Comanager can edit self and clients or users but not manager and other comanagers. -->
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a>{/if}</td>
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {/if}
+        </tr>
+    {/foreach}
+  {/if}
+      </table>
+      
+      <table width="100%">
+        <tr>
+          <td align="center"><br>
+            <form><input type="button" onclick="chLocation('user_add.php');" value="{$i18n.button.add_user}"></form>
+          </td>
+        </tr>
+      </table>
+
+  {if $inactive_users}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr><td class="sectionHeaderNoBorder">{$i18n.form.users.inactive_users}</td></tr>
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td width="10%" class="tableHeader">{$i18n.form.users.role}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.edit}</td>
+          <td width="10%" class="tableHeader">{$i18n.label.delete}</td>
+        </tr>
+    {foreach $inactive_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+      {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+      {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+      {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+      {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+      {/if}
+      {if $user->isManager()}
+          <!-- Manager can edit everybody. -->
+          <td><a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a></td>
+          <td>{if $smarty.const.ROLE_MANAGER != $u.role || $can_delete_manager}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {else}
+          <!--  Comanager can edit self and clients or users but not manager and other comanagers. -->
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_edit.php?id={$u.id}">{$i18n.label.edit}</a>{/if}</td>
+          <td>{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}<a href="user_delete.php?id={$u.id}">{$i18n.label.delete}</a>{/if}</td>
+      {/if}
+        </tr>
+    {/foreach}
+
+      </table>
+      
+      <table width="100%">
+        <tr>
+          <td align="center" height="50">
+            <form><input type="button" onclick="chLocation('user_add.php');" value="{$i18n.button.add_user}"></form>
+          </td>
+        </tr>
+      </table>
+  {/if}
+{else}
+      <table cellspacing="1" cellpadding="3" border="0" width="100%">
+        <tr>
+          <td width="35%" class="tableHeader">{$i18n.label.person_name}</td>
+          <td width="35%" class="tableHeader">{$i18n.label.login}</td>
+          <td class="tableHeader">{$i18n.form.users.role}</td>
+        </tr>
+  {foreach $active_users as $u}
+        <tr bgcolor="{cycle values="#f5f5f5,#dedee5"}">
+          <td>{$u.name|escape:'html'}</td>
+          <td>{$u.login|escape:'html'}</td>
+    {if $smarty.const.ROLE_MANAGER == $u.role}
+            <td>{$i18n.form.users.manager}</td>
+    {elseif $smarty.const.ROLE_COMANAGER == $u.role}
+            <td>{$i18n.form.users.comanager}</td>
+    {elseif $smarty.const.ROLE_CLIENT == $u.role}
+            <td>{$i18n.label.client}</td>
+    {elseif $smarty.const.ROLE_USER == $u.role}
+            <td>{$i18n.label.user}</td>
+    {/if}
+        </tr>
+  {/foreach}
+      </table>
+{/if}
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/WEB-INF/templates_c/keepme b/WEB-INF/templates_c/keepme
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/images/1x1.gif b/images/1x1.gif
new file mode 100644 (file)
index 0000000..5bfd67a
Binary files /dev/null and b/images/1x1.gif differ
diff --git a/images/calendar.gif b/images/calendar.gif
new file mode 100644 (file)
index 0000000..8526cf5
Binary files /dev/null and b/images/calendar.gif differ
diff --git a/images/subm_bg.gif b/images/subm_bg.gif
new file mode 100644 (file)
index 0000000..2282a6b
Binary files /dev/null and b/images/subm_bg.gif differ
diff --git a/images/top_bg.gif b/images/top_bg.gif
new file mode 100644 (file)
index 0000000..cb170c1
Binary files /dev/null and b/images/top_bg.gif differ
diff --git a/images/tt_logo.png b/images/tt_logo.png
new file mode 100644 (file)
index 0000000..9eca180
Binary files /dev/null and b/images/tt_logo.png differ
diff --git a/js/strftime.js b/js/strftime.js
new file mode 100644 (file)
index 0000000..4d13668
--- /dev/null
@@ -0,0 +1,739 @@
+/*
+ strftime for Javascript
+ Copyright (c) 2008, Philip S Tellis <philip@bluesmoon.info>
+ All rights reserved.
+ This code is distributed under the terms of the BSD licence
+ Redistribution and use of this software in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+   * Redistributions of source code must retain the above copyright notice, this list of conditions
+     and the following disclaimer.
+   * Redistributions in binary form must reproduce the above copyright notice, this list of
+     conditions and the following disclaimer in the documentation and/or other materials provided
+     with the distribution.
+   * The names of the contributors to this file may not be used to endorse or promote products
+     derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file strftime.js
+ * \author Philip S Tellis \<philip@bluesmoon.info\>
+ * \version 1.3
+ * \date 2008/06
+ * \brief Javascript implementation of strftime
+ * 
+ * Implements strftime for the Date object in javascript based on the PHP implementation described at
+ * http://www.php.net/strftime  This is in turn based on the Open Group specification defined
+ * at http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html This implementation does not
+ * include modified conversion specifiers (i.e., Ex and Ox)
+ *
+ * The following format specifiers are supported:
+ *
+ * \copydoc formats
+ *
+ * \%a, \%A, \%b and \%B should be localised for non-English locales.
+ *
+ * \par Usage:
+ * This library may be used as follows:
+ * \code
+ *     var d = new Date();
+ *
+ *     var ymd = d.strftime('%Y/%m/%d');
+ *     var iso = d.strftime('%Y-%m-%dT%H:%M:%S%z');
+ *
+ * \endcode
+ *
+ * \sa \link Date.prototype.strftime Date.strftime \endlink for a description of each of the supported format specifiers
+ * \sa Date.ext.locales for localisation information
+ * \sa http://www.php.net/strftime for the PHP implementation which is the basis for this
+ * \sa http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html for feedback
+ */
+
+//! Date extension object - all supporting objects go in here.
+Date.ext = {};
+
+//! Utility methods
+Date.ext.util = {};
+
+/**
+\brief Left pad a number with something
+\details Takes a number and pads it to the left with the passed in pad character
+\param x       The number to pad
+\param pad     The string to pad with
+\param r       [optional] Upper limit for pad.  A value of 10 pads to 2 digits, a value of 100 pads to 3 digits.
+               Default is 10.
+
+\return The number left padded with the pad character.  This function returns a string and not a number.
+*/
+Date.ext.util.xPad=function(x, pad, r)
+{
+       if(typeof(r) == 'undefined')
+       {
+               r=10;
+       }
+       for( ; parseInt(x, 10)<r && r>1; r/=10)
+               x = pad.toString() + x;
+       return x.toString();
+};
+
+/**
+\brief Currently selected locale.
+\details
+The locale for a specific date object may be changed using \code Date.locale = "new-locale"; \endcode
+The default will be based on the lang attribute of the HTML tag of your document
+*/
+Date.prototype.locale = 'en-GB';
+//! \cond FALSE
+if(document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang)
+{
+       Date.prototype.locale = document.getElementsByTagName('html')[0].lang;
+}
+//! \endcond
+
+/**
+\brief Localised strings for days of the week and months of the year.
+\details
+To create your own local strings, add a locale object to the locales object.
+The key of your object should be the same as your locale name.  For example:
+   en-US,
+   fr,
+   fr-CH,
+   de-DE
+Names are case sensitive and are described at http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+Your locale object must contain the following keys:
+\param a       Short names of days of week starting with Sunday
+\param A       Long names days of week starting with Sunday
+\param b       Short names of months of the year starting with January
+\param B       Long names of months of the year starting with February
+\param c       The preferred date and time representation in your locale
+\param p       AM or PM in your locale
+\param P       am or pm in your locale
+\param x       The  preferred date representation for the current locale without the time.
+\param X       The preferred time representation for the current locale without the date.
+
+\sa Date.ext.locales.en for a sample implementation
+\sa \ref localisation for detailed documentation on localising strftime for your own locale
+*/
+Date.ext.locales = { };
+
+/**
+ * \brief Localised strings for English (British).
+ * \details
+ * This will be used for any of the English dialects unless overridden by a country specific one.
+ * This is the default locale if none specified
+ */
+Date.ext.locales.en = {
+       a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+       A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+       b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+       B: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+       c: '%a %d %b %Y %T %Z',
+       p: ['AM', 'PM'],
+       P: ['am', 'pm'],
+       x: '%d/%m/%y',
+       X: '%T'
+};
+
+//! \cond FALSE
+// Localised strings for US English
+Date.ext.locales['en-US'] = Date.ext.locales.en;
+Date.ext.locales['en-US'].c = '%a %d %b %Y %r %Z';
+Date.ext.locales['en-US'].x = '%D';
+Date.ext.locales['en-US'].X = '%r';
+
+// Localised strings for British English
+Date.ext.locales['en-GB'] = Date.ext.locales.en;
+
+// Localised strings for Australian English
+Date.ext.locales['en-AU'] = Date.ext.locales['en-GB'];
+//! \endcond
+
+//! \brief List of supported format specifiers.
+/**
+ * \details
+ * \arg \%a - abbreviated weekday name according to the current locale
+ * \arg \%A - full weekday name according to the current locale
+ * \arg \%b - abbreviated month name according to the current locale
+ * \arg \%B - full month name according to the current locale
+ * \arg \%c - preferred date and time representation for the current locale
+ * \arg \%C - century number (the year divided by 100 and truncated to an integer, range 00 to 99)
+ * \arg \%d - day of the month as a decimal number (range 01 to 31)
+ * \arg \%D - same as %m/%d/%y
+ * \arg \%e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')
+ * \arg \%g - like %G, but without the century
+ * \arg \%G - The 4-digit year corresponding to the ISO week number
+ * \arg \%h - same as %b
+ * \arg \%H - hour as a decimal number using a 24-hour clock (range 00 to 23)
+ * \arg \%I - hour as a decimal number using a 12-hour clock (range 01 to 12)
+ * \arg \%j - day of the year as a decimal number (range 001 to 366)
+ * \arg \%m - month as a decimal number (range 01 to 12)
+ * \arg \%M - minute as a decimal number
+ * \arg \%n - newline character
+ * \arg \%p - either `AM' or `PM' according to the given time value, or the corresponding strings for the current locale
+ * \arg \%P - like %p, but lower case
+ * \arg \%r - time in a.m. and p.m. notation equal to %I:%M:%S %p
+ * \arg \%R - time in 24 hour notation equal to %H:%M
+ * \arg \%S - second as a decimal number
+ * \arg \%t - tab character
+ * \arg \%T - current time, equal to %H:%M:%S
+ * \arg \%u - weekday as a decimal number [1,7], with 1 representing Monday
+ * \arg \%U - week number of the current year as a decimal number, starting with
+ *            the first Sunday as the first day of the first week
+ * \arg \%V - The ISO 8601:1988 week number of the current year as a decimal number,
+ *            range 01 to 53, where week 1 is the first week that has at least 4 days
+ *            in the current year, and with Monday as the first day of the week.
+ * \arg \%w - day of the week as a decimal, Sunday being 0
+ * \arg \%W - week number of the current year as a decimal number, starting with the
+ *            first Monday as the first day of the first week
+ * \arg \%x - preferred date representation for the current locale without the time
+ * \arg \%X - preferred time representation for the current locale without the date
+ * \arg \%y - year as a decimal number without a century (range 00 to 99)
+ * \arg \%Y - year as a decimal number including the century
+ * \arg \%z - numerical time zone representation
+ * \arg \%Z - time zone name or abbreviation
+ * \arg \%% - a literal `\%' character
+ */
+Date.ext.formats = {
+       a: function(d) { return Date.ext.locales[d.locale].a[d.getDay()]; },
+       A: function(d) { return Date.ext.locales[d.locale].A[d.getDay()]; },
+       b: function(d) { return Date.ext.locales[d.locale].b[d.getMonth()]; },
+       B: function(d) { return Date.ext.locales[d.locale].B[d.getMonth()]; },
+       c: 'toLocaleString',
+       C: function(d) { return Date.ext.util.xPad(parseInt(d.getFullYear()/100, 10), 0); },
+       d: ['getDate', '0'],
+       e: ['getDate', ' '],
+       g: function(d) { return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100, 10), 0); },
+       G: function(d) {
+                       var y = d.getFullYear();
+                       var V = parseInt(Date.ext.formats.V(d), 10);
+                       var W = parseInt(Date.ext.formats.W(d), 10);
+
+                       if(W > V) {
+                               y++;
+                       } else if(W===0 && V>=52) {
+                               y--;
+                       }
+
+                       return y;
+               },
+       H: ['getHours', '0'],
+       I: function(d) { var I=d.getHours()%12; return Date.ext.util.xPad(I===0?12:I, 0); },
+       j: function(d) {
+                       var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
+                       ms += d.getTimezoneOffset()*60000;
+                       var doy = parseInt(ms/60000/60/24, 10)+1;
+                       return Date.ext.util.xPad(doy, 0, 100);
+               },
+       m: function(d) { return Date.ext.util.xPad(d.getMonth()+1, 0); },
+       M: ['getMinutes', '0'],
+       p: function(d) { return Date.ext.locales[d.locale].p[d.getHours() >= 12 ? 1 : 0 ]; },
+       P: function(d) { return Date.ext.locales[d.locale].P[d.getHours() >= 12 ? 1 : 0 ]; },
+       S: ['getSeconds', '0'],
+       u: function(d) { var dow = d.getDay(); return dow===0?7:dow; },
+       U: function(d) {
+                       var doy = parseInt(Date.ext.formats.j(d), 10);
+                       var rdow = 6-d.getDay();
+                       var woy = parseInt((doy+rdow)/7, 10);
+                       return Date.ext.util.xPad(woy, 0);
+               },
+       V: function(d) {
+                       var woy = parseInt(Date.ext.formats.W(d), 10);
+                       var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
+                       // First week is 01 and not 00 as in the case of %U and %W,
+                       // so we add 1 to the final result except if day 1 of the year
+                       // is a Monday (then %W returns 01).
+                       // We also need to subtract 1 if the day 1 of the year is 
+                       // Friday-Sunday, so the resulting equation becomes:
+                       var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
+                       if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
+                       {
+                               idow = 1;
+                       }
+                       else if(idow === 0)
+                       {
+                               idow = Date.ext.formats.V(new Date('' + (d.getFullYear()-1) + '/12/31'));
+                       }
+
+                       return Date.ext.util.xPad(idow, 0);
+               },
+       w: 'getDay',
+       W: function(d) {
+                       var doy = parseInt(Date.ext.formats.j(d), 10);
+                       var rdow = 7-Date.ext.formats.u(d);
+                       var woy = parseInt((doy+rdow)/7, 10);
+                       return Date.ext.util.xPad(woy, 0, 10);
+               },
+       y: function(d) { return Date.ext.util.xPad(d.getFullYear()%100, 0); },
+       Y: 'getFullYear',
+       z: function(d) {
+                       var o = d.getTimezoneOffset();
+                       var H = Date.ext.util.xPad(parseInt(Math.abs(o/60), 10), 0);
+                       var M = Date.ext.util.xPad(o%60, 0);
+                       return (o>0?'-':'+') + H + M;
+               },
+       Z:
+         // does not work reliably (for example on Windows with CP1251), using numeric representations
+         //function(d) { return d.toString().replace(/^.*\(([^)]+)\)$/, '$1'); },
+       function(d) {
+      var o = d.getTimezoneOffset();
+      var H = Date.ext.util.xPad(parseInt(Math.abs(o/60), 10), 0);
+      var M = Date.ext.util.xPad(o%60, 0);
+      return (o>0?'-':'+') + H + M;
+    },
+       '%': function(d) { return '%'; }
+};
+
+/**
+\brief List of aggregate format specifiers.
+\details
+Aggregate format specifiers map to a combination of basic format specifiers.
+These are implemented in terms of Date.ext.formats.
+
+A format specifier that maps to 'locale' is read from Date.ext.locales[current-locale].
+
+\sa Date.ext.formats
+*/
+Date.ext.aggregates = {
+       c: 'locale',
+       D: '%m/%d/%y',
+       h: '%b',
+       n: '\n',
+       r: '%I:%M:%S %p',
+       R: '%H:%M',
+       t: '\t',
+       T: '%H:%M:%S',
+       x: 'locale',
+       X: 'locale'
+};
+
+//! \cond FALSE
+// Cache timezone values because they will never change for a given JS instance
+Date.ext.aggregates.z = Date.ext.formats.z(new Date());
+Date.ext.aggregates.Z = Date.ext.formats.Z(new Date());
+//! \endcond
+
+//! List of unsupported format specifiers.
+/**
+ * \details
+ * All format specifiers supported by the PHP implementation are supported by
+ * this javascript implementation.
+ */
+Date.ext.unsupported = { };
+
+
+/**
+ * \brief Formats the date according to the specified format.
+ * \param fmt  The format to format the date in.  This may be a combination of the following:
+ * \copydoc formats
+ *
+ * \return     A string representation of the date formatted based on the passed in parameter
+ * \sa http://www.php.net/strftime for documentation on format specifiers
+*/
+Date.prototype.strftime=function(fmt)
+{
+       // Fix locale if declared locale hasn't been defined
+       // After the first call this condition should never be entered unless someone changes the locale
+       if(!(this.locale in Date.ext.locales))
+       {
+               if(this.locale.replace(/-[a-zA-Z]+$/, '') in Date.ext.locales)
+               {
+                       this.locale = this.locale.replace(/-[a-zA-Z]+$/, '');
+               }
+               else
+               {
+                       this.locale = 'en-GB';
+               }
+       }
+
+       var d = this;
+       // First replace aggregates
+       while(fmt.match(/%[cDhnrRtTxXzZ]/))
+       {
+               fmt = fmt.replace(/%([cDhnrRtTxXzZ])/g, function(m0, m1)
+                               {
+                                       var f = Date.ext.aggregates[m1];
+                                       return (f == 'locale' ? Date.ext.locales[d.locale][m1] : f);
+                               });
+       }
+
+
+       // Now replace formats - we need a closure so that the date object gets passed through
+       var str = fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g, function(m0, m1) 
+                       {
+                               var f = Date.ext.formats[m1];
+                               if(typeof(f) == 'string') {
+                                       return d[f]();
+                               } else if(typeof(f) == 'function') {
+                                       return f.call(d, d);
+                               } else if(typeof(f) == 'object' && typeof(f[0]) == 'string') {
+                                       return Date.ext.util.xPad(d[f[0]](), f[1]);
+                               } else {
+                                       return m1;
+                               }
+                       });
+       d=null;
+       return str;
+};
+
+/**
+ * \mainpage strftime for Javascript
+ *
+ * \section toc Table of Contents
+ * - \ref intro_sec
+ * - <a class="el" href="strftime.js">Download full source</a> / <a class="el" href="strftime-min.js">minified</a>
+ * - \subpage usage
+ * - \subpage format_specifiers
+ * - \subpage localisation
+ * - \link strftime.js API Documentation \endlink
+ * - \subpage demo
+ * - \subpage changelog
+ * - \subpage faq
+ * - <a class="el" href="http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html">Feedback</a>
+ * - \subpage copyright_licence
+ *
+ * \section intro_sec Introduction
+ *
+ * C and PHP developers have had access to a built in strftime function for a long time.
+ * This function is an easy way to format dates and times for various display needs.
+ *
+ * This library brings the flexibility of strftime to the javascript Date object
+ *
+ * Use this library if you frequently need to format dates in javascript in a variety of ways.  For example,
+ * if you have PHP code that writes out formatted dates, and want to mimic the functionality using
+ * progressively enhanced javascript, then this library can do exactly what you want.
+ *
+ *
+ *
+ *
+ * \page usage Example usage
+ *
+ * \section usage_sec Usage
+ * This library may be used as follows:
+ * \code
+ *     var d = new Date();
+ *
+ *     var ymd = d.strftime('%Y/%m/%d');
+ *     var iso = d.strftime('%Y-%m-%dT%H:%M:%S%z');
+ *
+ * \endcode
+ *
+ * \subsection examples Examples
+ * 
+ * To get the current time in hours and minutes:
+ * \code
+ *     var d = new Date();
+ *     d.strftime("%H:%M");
+ * \endcode
+ *
+ * To get the current time with seconds in AM/PM notation:
+ * \code
+ *     var d = new Date();
+ *     d.strftime("%r");
+ * \endcode
+ *
+ * To get the year and day of the year for August 23, 2009:
+ * \code
+ *     var d = new Date('2009/8/23');
+ *     d.strftime("%Y-%j");
+ * \endcode
+ *
+ * \section demo_sec Demo
+ *
+ * Try your own examples on the \subpage demo page.  You can use any of the supported
+ * \subpage format_specifiers.
+ *
+ *
+ *
+ *
+ * \page localisation Localisation
+ * You can localise strftime by implementing the short and long forms for days of the
+ * week and months of the year, and the localised aggregates for the preferred date
+ * and time representation for your locale.  You need to add your locale to the
+ * Date.ext.locales object.
+ *
+ * \section localising_fr Localising for french
+ *
+ * For example, this is how we'd add French language strings to the locales object:
+ * \dontinclude index.html
+ * \skip Generic french
+ * \until };
+ * The % format specifiers are all defined in \ref formats.  You can use any of those.
+ *
+ * This locale definition may be included in your own source file, or in the HTML file
+ * including \c strftime.js, however it must be defined \em after including \c strftime.js
+ *
+ * The above definition includes generic french strings and formats that are used in France.
+ * Other french speaking countries may have other representations for dates and times, so we
+ * need to override this for them.  For example, Canadian french uses a Y-m-d date format,
+ * while French french uses d.m.Y.  We fix this by defining Canadian french to be the same
+ * as generic french, and then override the format specifiers for \c x for the \c fr-CA locale:
+ * \until End french
+ *
+ * You can now use any of the French locales at any time by setting \link Date.prototype.locale Date.locale \endlink
+ * to \c "fr", \c "fr-FR", \c "fr-CA", or any other french dialect:
+ * \code
+ *     var d = new Date("2008/04/22");
+ *     d.locale = "fr";
+ *
+ *     d.strftime("%A, %d %B == %x");
+ * \endcode
+ * will return:
+ * \code
+ *     mardi, 22 avril == 22.04.2008
+ * \endcode
+ * While changing the locale to "fr-CA":
+ * \code
+ *     d.locale = "fr-CA";
+ *
+ *     d.strftime("%A, %d %B == %x");
+ * \endcode
+ * will return:
+ * \code
+ *     mardi, 22 avril == 2008-04-22
+ * \endcode
+ *
+ * You can use any of the format specifiers defined at \ref formats
+ *
+ * The locale for all dates defaults to the value of the \c lang attribute of your HTML document if
+ * it is set, or to \c "en" otherwise.
+ * \note
+ * Your locale definitions \b MUST be added to the locale object before calling
+ * \link Date.prototype.strftime Date.strftime \endlink.
+ *
+ * \sa \ref formats for a list of format specifiers that can be used in your definitions
+ * for c, x and X.
+ *
+ * \section locale_names Locale names
+ *
+ * Locale names are defined in RFC 1766. Typically, a locale would be a two letter ISO639
+ * defined language code and an optional ISO3166 defined country code separated by a -
+ * 
+ * eg: fr-FR, de-DE, hi-IN
+ *
+ * \sa http://www.ietf.org/rfc/rfc1766.txt
+ * \sa http://www.loc.gov/standards/iso639-2/php/code_list.php
+ * \sa http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
+ * 
+ * \section locale_fallback Locale fallbacks
+ *
+ * If a locale object corresponding to the fully specified locale isn't found, an attempt will be made
+ * to fall back to the two letter language code.  If a locale object corresponding to that isn't found
+ * either, then the locale will fall back to \c "en".  No warning will be issued.
+ *
+ * For example, if we define a locale for de:
+ * \until };
+ * Then set the locale to \c "de-DE":
+ * \code
+ *     d.locale = "de-DE";
+ *
+ *     d.strftime("%a, %d %b");
+ * \endcode
+ * In this case, the \c "de" locale will be used since \c "de-DE" has not been defined:
+ * \code
+ *     Di, 22 Apr
+ * \endcode
+ *
+ * Swiss german will return the same since it will also fall back to \c "de":
+ * \code
+ *     d.locale = "de-CH";
+ *
+ *     d.strftime("%a, %d %b");
+ * \endcode
+ * \code
+ *     Di, 22 Apr
+ * \endcode
+ *
+ * We need to override the \c a specifier for Swiss german, since it's different from German german:
+ * \until End german
+ * We now get the correct results:
+ * \code
+ *     d.locale = "de-CH";
+ *
+ *     d.strftime("%a, %d %b");
+ * \endcode
+ * \code
+ *     Die, 22 Apr
+ * \endcode
+ *
+ * \section builtin_locales Built in locales
+ *
+ * This library comes with pre-defined locales for en, en-GB, en-US and en-AU.
+ *
+ * 
+ *
+ *
+ * \page format_specifiers Format specifiers
+ * 
+ * \section specifiers Format specifiers
+ * strftime has several format specifiers defined by the Open group at 
+ * http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
+ *
+ * PHP added a few of its own, defined at http://www.php.net/strftime
+ *
+ * This javascript implementation supports all the PHP specifiers
+ *
+ * \subsection supp Supported format specifiers:
+ * \copydoc formats
+ * 
+ * \subsection unsupportedformats Unsupported format specifiers:
+ * \copydoc unsupported
+ *
+ *
+ *
+ *
+ * \page demo strftime demo
+ * <div style="float:right;width:45%;">
+ * \copydoc formats
+ * </div>
+ * \htmlinclude index.html
+ *
+ *
+ *
+ *
+ * \page faq FAQ
+ * 
+ * \section how_tos Usage
+ *
+ * \subsection howtouse Is there a manual on how to use this library?
+ *
+ * Yes, see \ref usage
+ *
+ * \subsection wheretoget Where can I get a minified version of this library?
+ *
+ * The minified version is available <a href="strftime-min.js" title="Minified strftime.js">here</a>.
+ *
+ * \subsection which_specifiers Which format specifiers are supported?
+ *
+ * See \ref format_specifiers
+ *
+ * \section whys Why?
+ *
+ * \subsection why_lib Why this library?
+ *
+ * I've used the strftime function in C, PHP and the Unix shell, and found it very useful
+ * to do date formatting.  When I needed to do date formatting in javascript, I decided
+ * that it made the most sense to just reuse what I'm already familiar with.
+ *
+ * \subsection why_another Why another strftime implementation for Javascript?
+ *
+ * Yes, there are other strftime implementations for Javascript, but I saw problems with
+ * all of them that meant I couldn't use them directly.  Some implementations had bad
+ * designs.  For example, iterating through all possible specifiers and scanning the string
+ * for them.  Others were tied to specific libraries like prototype.
+ *
+ * Trying to extend any of the existing implementations would have required only slightly
+ * less effort than writing this from scratch.  In the end it took me just about 3 hours
+ * to write the code and about 6 hours battling with doxygen to write these docs.
+ *
+ * I also had an idea of how I wanted to implement this, so decided to try it.
+ *
+ * \subsection why_extend_date Why extend the Date class rather than subclass it?
+ *
+ * I tried subclassing Date and failed.  I didn't want to waste time on figuring
+ * out if there was a problem in my code or if it just wasn't possible.  Adding to the
+ * Date.prototype worked well, so I stuck with it.
+ *
+ * I did have some worries because of the way for..in loops got messed up after json.js added
+ * to the Object.prototype, but that isn't an issue here since {} is not a subclass of Date.
+ *
+ * My last doubt was about the Date.ext namespace that I created.  I still don't like this,
+ * but I felt that \c ext at least makes clear that this is external or an extension.
+ *
+ * It's quite possible that some future version of javascript will add an \c ext or a \c locale
+ * or a \c strftime property/method to the Date class, but this library should probably
+ * check for capabilities before doing what it does.
+ *
+ * \section curiosity Curiosity
+ *
+ * \subsection how_big How big is the code?
+ *
+ * \arg 26K bytes with documentation
+ * \arg 4242 bytes minified using <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a>
+ * \arg 1477 bytes minified and gzipped
+ *
+ * \subsection how_long How long did it take to write this?
+ *
+ * 15 minutes for the idea while I was composing this blog post:
+ * http://tech.bluesmoon.info/2008/04/javascript-date-functions.html
+ *
+ * 3 hours in one evening to write v1.0 of the code and 6 hours the same
+ * night to write the docs and this manual.  As you can tell, I'm fairly
+ * sleepy.
+ *
+ * Versions 1.1 and 1.2 were done in a couple of hours each, and version 1.3
+ * in under one hour.
+ *
+ * \section contributing Contributing
+ *
+ * \subsection how_to_rfe How can I request features or make suggestions?
+ *
+ * You can leave a comment on my blog post about this library here:
+ * http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html
+ *
+ * \subsection how_to_contribute Can I/How can I contribute code to this library?
+ *
+ * Yes, that would be very nice, thank you.  You can do various things.  You can make changes
+ * to the library, and make a diff against the current file and mail me that diff at
+ * philip@bluesmoon.info, or you could just host the new file on your own servers and add
+ * your name to the copyright list at the top stating which parts you've added.
+ *
+ * If you do mail me a diff, let me know how you'd like to be listed in the copyright section.
+ *
+ * \subsection copyright_signover Who owns the copyright on contributed code?
+ *
+ * The contributor retains copyright on contributed code.
+ *
+ * In some cases I may use contributed code as a template and write the code myself.  In this
+ * case I'll give the contributor credit for the idea, but will not add their name to the
+ * copyright holders list.
+ *
+ *
+ *
+ *
+ * \page copyright_licence Copyright & Licence
+ *
+ * \section copyright Copyright
+ * \dontinclude strftime.js
+ * \skip Copyright
+ * \until rights
+ *
+ * \section licence Licence
+ * \skip This code
+ * \until SUCH DAMAGE.
+ *
+ *
+ *
+ * \page changelog ChangeLog
+ *
+ * \par 1.3 - 2008/06/17:
+ * - Fixed padding issue with negative timezone offsets in %r
+ *   reported and fixed by Mikko <mikko.heimola@iki.fi>
+ * - Added support for %P
+ * - Internationalised %r, %p and %P
+ *
+ * \par 1.2 - 2008/04/27:
+ * - Fixed support for c (previously it just returned toLocaleString())
+ * - Add support for c, x and X
+ * - Add locales for en-GB, en-US and en-AU
+ * - Make en-GB the default locale (previous was en)
+ * - Added more localisation docs
+ *
+ * \par 1.1 - 2008/04/27:
+ * - Fix bug in xPad which wasn't padding more than a single digit
+ * - Fix bug in j which had an off by one error for days after March 10th because of daylight savings
+ * - Add support for g, G, U, V and W
+ *
+ * \par 1.0 - 2008/04/22:
+ * - Initial release with support for a, A, b, B, c, C, d, D, e, H, I, j, m, M, p, r, R, S, t, T, u, w, y, Y, z, Z, and %
+ */
diff --git a/js/strptime.js b/js/strptime.js
new file mode 100644 (file)
index 0000000..5ccfdc2
--- /dev/null
@@ -0,0 +1,112 @@
+// http://www.logilab.org/blogentry/6731
+// modified to parse formats with unsupported chars and make compatible with IE7
+
+var _DATE_FORMAT_REGXES = {
+    'Y': new RegExp('^-?[0-9]+'),
+    'd': new RegExp('^[0-9]{1,2}'),
+    'm': new RegExp('^[0-9]{1,2}'),
+    'H': new RegExp('^[0-9]{1,2}'),
+    'M': new RegExp('^[0-9]{1,2}')
+}
+
+/*
+ * _parseData does the actual parsing job needed by `strptime`
+ */
+function _parseDate(datestring, format) {
+  var skip0 = new RegExp('^0*[0-9]+');
+  var skipRE = new RegExp('^.+?(\\s|$)');
+  var parsed = {};  
+  for (var i1=0,i2=0;i1<format.length;i1++,i2++) {
+    var c1 = format.charAt(i1);
+    var c2 = datestring.charAt(i2);   
+    
+    if (c1 == '%') {
+        c1 = format.charAt(++i1);
+        
+        var data;        
+        if(c1 in _DATE_FORMAT_REGXES) {
+          data = _DATE_FORMAT_REGXES[c1].exec(datestring.substring(i2));          
+        } else {
+          // skip text that are not parsed
+          skip = skipRE.exec(datestring.substring(i2));
+          //alert(datestring.substring(i2) + ':' + skip);
+          i2 += skip[0].length-2;          
+          continue;
+        }
+        if (!data || !data.length) {          
+          return null;
+        }
+        data = data[0];
+        i2 += data.length-1;
+        var value = parseInt(data, 10);
+        if (isNaN(value)) {
+          
+          return null;
+        }
+        parsed[c1] = value;
+        continue;
+    }
+    if (c1 != c2) {      
+      return null;
+    }
+  }  
+  return parsed;
+}
+
+/*
+ * basic implementation of strptime. The only recognized formats
+ * defined in _DATE_FORMAT_REGEXES (i.e. %Y, %d, %m, %H, %M)
+ */
+function strptime(datestring, format) {
+    var parsed = _parseDate(datestring, format);    
+    if (!parsed) {
+    return null;
+    }
+    
+    // create initial date (!!! year=0 means 1900 !!!)
+    var date = new Date(0, 0, 1, 0, 0);
+    date.setFullYear(0); // reset to year 0
+    if (parsed.Y) {
+    date.setFullYear(parsed.Y);
+    }
+    if (parsed.m) {
+    if (parsed.m < 1 || parsed.m > 12) {
+        return null;
+    }
+    // !!! month indexes start at 0 in javascript !!!
+    date.setMonth(parsed.m - 1);
+    }
+    if (parsed.d) {
+    if (parsed.m < 1 || parsed.m > 31) {
+        return null;
+    }
+    date.setDate(parsed.d);
+    }
+    if (parsed.H) {
+    if (parsed.H < 0 || parsed.H > 23) {
+        return null;
+    }
+    date.setHours(parsed.H);
+    }
+    if (parsed.M) {
+    if (parsed.M < 0 || parsed.M > 59) {
+        return null;
+    }
+    date.setMinutes(parsed.M);
+    }
+    return date;
+}
+
+// and now monkey patch the Timeline's parser ...
+/*
+ * provide our own custom date parser since the default one only understands
+ * iso8601 and gregorian dates
+ */
+/*
+ * Timeline.NativeDateUnit.getParser = function(format) { if (typeof format ==
+ * "string") { if (format.indexOf('%') != -1) { return function(datestring) { if
+ * (datestring) { return strptime(datestring, format); } return null; }; }
+ * format = format.toLowerCase(); } if (format == "iso8601" || format == "iso
+ * 8601") { return Timeline.DateTime.parseIso8601DateTime; } return
+ * Timeline.DateTime.parseGregorianDateTime; };
+ */
diff --git a/mobile/access_denied.php b/mobile/access_denied.php
new file mode 100644 (file)
index 0000000..27ba6e8
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+
+$errors->add($i18n->getKey('error.access_denied'));  
+if ($auth->isAuthenticated()) $GLOBALS['SMARTY']->assign('authenticated', true); // Used in header.tpl for menu display.
+
+$smarty->assign('title', $i18n->getKey('label.error'));
+$smarty->assign('content_page_name', 'mobile/access_denied.tpl');
+$smarty->display('mobile/index.tpl');
+?>
\ No newline at end of file
diff --git a/mobile/index.php b/mobile/index.php
new file mode 100644 (file)
index 0000000..de48595
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+
+// Redirects for admin and client roles.
+if ($auth->isAuthenticated()) {
+  if ($user->isAdmin()) {
+    header('Location: ../admin_teams.php');
+    exit();
+  }
+  else if ($user->isClient()) {
+    header('Location: ../reports.php');
+    exit();    
+  }
+}
+// Redirect to time.php or mobile/time.php for other roles.
+?>
+
+<html>
+  <script src="../js/strftime.js"></script>
+  <script>
+    location.href = "time.php?date="+(new Date()).strftime('<?php print DB_DATEFORMAT;?>');
+  </script>
+  <noscript>
+    <p>Anuko Time Tracker is a simple, easy to use, open source, web-based time tracking system.</p>
+    <p>Your browser does not support JavaScript. Time Tracker will not work without it.</p>
+  </noscript>
+</html>
diff --git a/mobile/login.php b/mobile/login.php
new file mode 100644 (file)
index 0000000..f1c171a
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+import('form.Form');
+import('ttTeamHelper');
+import('ttUser');
+
+if ($request->getMethod() == 'POST') {
+  $cl_login = $request->getParameter('login');
+} else {
+  $cl_login = @$_COOKIE['tt_login'];
+}
+$cl_password = $request->getParameter('password');
+
+$form = new Form('loginForm');
+$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'100','name'=>'login','style'=>'width: 220px;','value'=>$cl_login));
+$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'50','name'=>'password','style'=>'width: 220px;','aspassword'=>true,'value'=>$cl_password));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_login click.
+$form->addInput(array('type'=>'submit','name'=>'btn_login','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.login')));
+
+if ($request->getMethod() == 'POST') {
+  // Validate user input.
+  if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login'));
+  if (!ttValidString($cl_password)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password'));
+       
+  if ($errors->isEmpty()) {
+
+    // Use the "limit" plugin if we have one. Ignore include errors.
+    // The "limit" plugin is not required for normal operation of the Time Tracker.
+    @include('../plugins/limit/access_check.php');
+      
+    if ($auth->doLogin($cl_login, $cl_password)) {
+
+      // Set current user date (as determined by user browser) into session.
+      $current_user_date = $request->getParameter('browser_today', null);
+      if ($current_user_date)
+        $_SESSION['date'] = $current_user_date;
+
+      // Remember user login in a cookie.
+      setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/');
+      
+      $user = new ttUser(null, $auth->getUserId());
+      // Redirect, depending on user role.
+      if ($user->isAdmin()) {
+        header('Location: ../admin_teams.php');
+        exit();
+      }
+      else if ($user->isClient()) {
+        header('Location: ../reports.php');
+        exit();        
+      }
+      else {
+        header('Location: time.php');
+        exit();
+      }
+    } else
+      $errors->add($i18n->getKey('error.auth'));
+  }
+}
+
+if(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams())
+  $errors->add($i18n->getKey('error.no_teams'));
+
+// Determine whether to show login hint. It is currently used only for Windows LDAP authentication.
+$show_hint = ('ad' == $GLOBALS['AUTH_MODULE_PARAMS']['type']);
+
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('show_hint', $show_hint);
+$smarty->assign('onload', 'onLoad="document.loginForm.'.(!$cl_login?'login':'password').'.focus()"');
+$smarty->assign('title', $i18n->getKey('title.login'));
+$smarty->assign('content_page_name', 'mobile/login.tpl');
+$smarty->display('mobile/index.tpl');
+?>
\ No newline at end of file
diff --git a/mobile/time.php b/mobile/time.php
new file mode 100644 (file)
index 0000000..b6887c9
--- /dev/null
@@ -0,0 +1,308 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+import('form.Form');
+import('ttUserHelper');
+import('ttTeamHelper');
+import('ttClientHelper');
+import('ttTimeHelper');
+import('DateAndTime');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+  header('Location: access_denied.php');
+  exit();
+}
+
+// Initialize and store date in session.
+$cl_date = $request->getParameter('date', @$_SESSION['date']);
+$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date);
+if($selected_date->isError())
+  $selected_date = new DateAndTime(DB_DATEFORMAT);
+if(!$cl_date)
+  $cl_date = $selected_date->toString(DB_DATEFORMAT);
+$_SESSION['date'] = $cl_date;
+  
+// Determine previous and next dates for simple navigation.
+$prev_date = date('Y-m-d', strtotime('-1 day', strtotime($cl_date)));
+$next_date = date('Y-m-d', strtotime('+1 day', strtotime($cl_date)));
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+  require_once('../plugins/CustomFields.class.php');
+  $custom_fields = new CustomFields($user->team_id);
+  $smarty->assign('custom_fields', $custom_fields);
+}
+
+// Initialize variables.
+$cl_start = trim($request->getParameter('start'));
+$cl_finish = trim($request->getParameter('finish'));
+$cl_duration = trim($request->getParameter('duration'));
+$cl_note = trim($request->getParameter('note'));
+// Custom field.
+$cl_cf_1 = trim($request->getParameter(('cf_1'), ($request->getMethod()=='POST'? null : @$_SESSION['cf_1'])));
+$_SESSION['cf_1'] = $cl_cf_1;
+$cl_billable = 1;
+if (in_array('iv', explode(',', $user->plugins))) {
+  if ($request->getMethod() == 'POST') {
+    $cl_billable = $request->getParameter('billable');
+    $_SESSION['billable'] = (int) $cl_billable;
+  } else 
+    if (isset($_SESSION['billable']))
+      $cl_billable = $_SESSION['billable'];
+}
+$cl_client = $request->getParameter('client', ($request->getMethod()=='POST'? null : @$_SESSION['client']));
+$_SESSION['client'] = $cl_client;
+$cl_project = $request->getParameter('project', ($request->getMethod()=='POST'? null : @$_SESSION['project']));
+$_SESSION['project'] = $cl_project;
+$cl_task = $request->getParameter('task', ($request->getMethod()=='POST'? null : @$_SESSION['task']));
+$_SESSION['task'] = $cl_task;
+
+// Elements of timeRecordForm.
+$form = new Form('timeRecordForm');
+
+// Dropdown for clients in MODE_TIME. Use all active clients.
+if (MODE_TIME == $user->tracking_mode && in_array('cl', explode(',', $user->plugins))) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$active_clients,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+    ));
+  // Note: in other modes the client list is filtered to relevant clients only. See below.
+}
+
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  // Dropdown for projects assigned to user.
+  $project_list = $user->getAssignedProjects();
+  $form->addInput(array('type'=>'combobox',
+    'onchange'=>'fillTaskDropdown(this.value);',
+    'name'=>'project',
+    'style'=>'width: 250px;',
+    'value'=>$cl_project,
+    'data'=>$project_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+  ));
+
+  // Dropdown for clients if the clients plugin is enabled.
+  if (in_array('cl', explode(',', $user->plugins))) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    // We need an array of assigned project ids to do some trimming. 
+    foreach($project_list as $project)
+      $projects_assigned_to_user[] = $project['id'];
+
+    // Build a client list out of active clients. Use only clients that are relevant to user.
+    // Also trim their associated project list to only assigned projects (to user).
+    foreach($active_clients as $client) {
+         $projects_assigned_to_client = explode(',', $client['projects']);
+         $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user);
+         if ($intersection) {
+           $client['projects'] = implode(',', $intersection);
+           $client_list[] = $client;
+         }
+    }
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$client_list,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+    ));
+  }
+}
+
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  $task_list = ttTeamHelper::getActiveTasks($user->team_id);
+  $form->addInput(array('type'=>'combobox',
+    'name'=>'task',
+    'style'=>'width: 250px;',
+    'value'=>$cl_task,
+    'data'=>$task_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+  ));
+}
+if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+  $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');"));
+  $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');"));
+}
+if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) {
+  // Make the start and finish fields read-only.
+  $form->getElement('start')->setEnable(false);
+  $form->getElement('finish')->setEnable(false);       
+}
+if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+  $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');"));
+$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height: 60px;','value'=>$cl_note));
+if (in_array('iv', explode(',', $user->plugins)))
+  $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click.
+$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit')));
+  
+// If we have custom fields - add controls for them.
+if ($custom_fields && $custom_fields->fields[0]) {
+  // Only one custom field is supported at this time.
+  if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) {
+       $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1));
+  } else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) {
+    $form->addInput(array('type'=>'combobox','name'=>'cf_1',
+      'style'=>'width: 250px;',
+      'value'=>$cl_cf_1,
+      'data'=>$custom_fields->options,
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+    ));
+  }
+}
+
+// Determine lock date. Time entries earlier than lock date cannot be created or modified. 
+$lock_interval = $user->lock_interval;
+$lockdate = 0;
+if ($lock_interval > 0) {
+  $lockdate = new DateAndTime();
+  $lockdate->decDay($lock_interval);
+}
+
+// Submit.
+if ($request->getMethod() == 'POST') {
+  if ($request->getParameter('btn_submit')) {
+
+    // Validate user input.
+    if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+      $errors->add($i18n->getKey('error.client'));
+    if ($custom_fields) {
+      if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']);
+    }
+    if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+      if (!$cl_project) $errors->add($i18n->getKey('error.project'));
+    }
+    if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+      if (!$cl_task) $errors->add($i18n->getKey('error.task'));
+    }
+    if (!$cl_duration) {
+      if ('0' == $cl_duration)
+        $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+      else if ($cl_start || $cl_finish) {
+        if (!ttTimeHelper::isValidTime($cl_start))
+          $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start'));
+        if ($cl_finish) {
+          if (!ttTimeHelper::isValidTime($cl_finish))
+            $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish'));
+          if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish))
+            $errors->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start'));
+        }
+      } else {
+       if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+          $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start'));
+          $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish'));
+       }
+        if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+          $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration'));
+      }
+    } else {
+      if (!ttTimeHelper::isValidDuration($cl_duration))
+        $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+    }
+    if (!ttValidString($cl_note, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.note'));
+    // Finished validating user input.
+    
+    // Prohibit creating entries in future.
+    if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) {
+      $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null));
+      if ($selected_date->after($browser_today))
+        $errors->add($i18n->getKey('error.future_date'));
+    }
+    
+    // Prohibit creating time entries in locked interval.
+    if($lockdate && $selected_date->before($lockdate))
+      $errors->add($i18n->getKey('error.period_locked'));
+    
+    // Prohibit creating another uncompleted record.
+    if ($errors->isEmpty()) {
+      if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == '')))
+        $errors->add($i18n->getKey('error.uncompleted_exists')." <a href = 'time_edit.php?id=".$not_completed_rec['id']."'>".$i18n->getKey('error.goto_uncompleted')."</a>");
+    }
+    
+    // Prohibit creating an overlapping record.
+    if ($errors->isEmpty()) {
+      if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish))
+        $errors->add($i18n->getKey('error.overlap'));
+    }  
+          
+    if ($errors->isEmpty()) {
+      $id = ttTimeHelper::insert(array(
+        'date' => $cl_date,
+        'user_id' => $user->getActiveUser(),
+        'client' => $cl_client,
+        'project' => $cl_project,
+        'task' => $cl_task,
+        'start' => $cl_start,
+        'finish' => $cl_finish,
+        'duration' => $cl_duration,
+        'note' => $cl_note,
+        'billable' => $cl_billable));
+               
+      // Insert a custom field if we have it.
+      $result = true;
+      if ($id && $custom_fields && $cl_cf_1) {
+        if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+          $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1);
+        else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+          $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null);
+      }
+
+      if ($id && $result) {
+        header('Location: time.php');
+        exit();
+      }
+      $errors->add($i18n->getKey('error.db'));
+    }
+  }
+}
+
+$smarty->assign('next_date', $next_date);
+$smarty->assign('prev_date', $prev_date);
+$smarty->assign('time_records', ttTimeHelper::getRecords($user->getActiveUser(), $cl_date));
+$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date));
+$smarty->assign('client_list', $client_list);
+$smarty->assign('project_list', $project_list);
+$smarty->assign('task_list', $task_list);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="fillDropdowns()"');
+$smarty->assign('timestring', $selected_date->toString($user->date_format));
+$smarty->assign('title', $i18n->getKey('title.time'));
+$smarty->assign('content_page_name', 'mobile/time.tpl');
+$smarty->display('mobile/index.tpl');
+?>
\ No newline at end of file
diff --git a/mobile/time_delete.php b/mobile/time_delete.php
new file mode 100644 (file)
index 0000000..eb4ac2b
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+import('form.Form');
+import('ttUserHelper');
+import('ttTimeHelper');
+import('DateAndTime');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+  header('Location: access_denied.php');
+  exit();
+}
+
+// Use Custom Fields plugin if we have one.
+// if (file_exists("plugins/CustomFields.class.php")) {
+//   require_once("plugins/CustomFields.class.php");
+//   $custom_fields = new CustomFields($user->team_id);
+// }
+
+$cl_id = $request->getParameter('id');
+$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser());
+
+// Prohibit deleting invoiced records.
+if ($time_rec['invoice_id']) die($i18n->getKey('error.sys'));
+  
+// Escape comment for presentation.
+$time_rec['comment'] = htmlspecialchars($time_rec['comment']);
+        
+if ($request->getMethod() == 'POST') {
+  if ($request->getParameter('delete_button'))  {  // Delete button pressed.
+  
+    // Determine if it's okay to delete the record.
+    $item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']);
+
+    // Determine lock date.
+    $lock_interval = $user->lock_interval;
+    $lockdate = 0;
+    if ($lock_interval > 0) {
+      $lockdate = new DateAndTime();
+      $lockdate->decDay($lock_interval);
+    }
+    // Determine if the record is uncompleted.
+    $uncompleted = ($time_rec['duration'] == '0:00');
+       
+    if($lockdate && $item_date->before($lockdate) && !$uncompleted) {
+      $errors->add($i18n->getKey('error.period_locked'));
+    }
+           
+    if ($errors->isEmpty()) {
+       
+      // Delete the record.
+      $result = ttTimeHelper::delete($cl_id, $user->getActiveUser());
+
+      if ($result) {
+        header('Location: time.php');
+        exit();
+      } else {
+        $errors->add($i18n->getKey('error.db'));
+      }
+    }
+  }
+  if ($request->getParameter('cancel_button')) { // Cancel button pressed.
+       header('Location: time.php');
+       exit();
+  }
+}
+               
+$form = new Form('timeRecordForm');
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete')));
+$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel')));
+$smarty->assign('time_rec', $time_rec);
+$smarty->assign('forms', array($form->getName() => $form->toArray()));
+$smarty->assign('title', $i18n->getKey('title.delete_time_record'));
+$smarty->assign('content_page_name', 'mobile/time_delete.tpl');
+$smarty->display('mobile/index.tpl');
+?>
\ No newline at end of file
diff --git a/mobile/time_edit.php b/mobile/time_edit.php
new file mode 100644 (file)
index 0000000..440aeef
--- /dev/null
@@ -0,0 +1,350 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+// | LIBERAL FREEWARE LICENSE: This source code document may be used
+// | by anyone for any purpose, and freely redistributed alone or in
+// | combination with other software, provided that the license is obeyed.
+// |
+// | There are only two ways to violate the license:
+// |
+// | 1. To redistribute this code in source form, with the copyright
+// |    notice or license removed or altered. (Distributing in compiled
+// |    forms without embedded copyright notices is permitted).
+// |
+// | 2. To redistribute modified versions of this code in *any* form
+// |    that bears insufficient indications that the modifications are
+// |    not the work of the original author(s).
+// |
+// | This license applies to this document only, not any other software
+// | that it may be combined with.
+// |
+// +----------------------------------------------------------------------+
+// | Contributors:
+// | https://www.anuko.com/time_tracker/credits.htm
+// +----------------------------------------------------------------------+
+
+require_once('../initialize.php');
+import('form.Form');
+import('ttUserHelper');
+import('ttTeamHelper');
+import('ttClientHelper');
+import('ttTimeHelper');
+import('DateAndTime');
+
+// Access check.
+if (!ttAccessCheck(right_data_entry)) {
+  header('Location: access_denied.php');
+  exit();
+}
+
+// Use custom fields plugin if it is enabled.
+if (in_array('cf', explode(',', $user->plugins))) {
+  require_once('../plugins/CustomFields.class.php');
+  $custom_fields = new CustomFields($user->team_id);
+  $smarty->assign('custom_fields', $custom_fields);
+}
+
+$cl_id = $request->getParameter('id');
+
+// Get the time record we are editing.
+$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser());
+
+// Prohibit editing invoiced records.
+if ($time_rec['invoice_id']) die($i18n->getKey('error.sys'));
+
+$item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']);
+  
+// Initialize variables.
+$cl_start = $cl_finish = $cl_duration = $cl_date = $cl_note = $cl_project = $cl_task = $cl_billable = null;
+if ($request->getMethod() == 'POST') {
+  $cl_start = trim($request->getParameter('start'));
+  $cl_finish = trim($request->getParameter('finish'));
+  $cl_duration = trim($request->getParameter('duration'));
+  $cl_date = $request->getParameter('date');
+  $cl_note = trim($request->getParameter('note'));
+  $cl_cf_1 = trim($request->getParameter('cf_1'));
+  $cl_client = $request->getParameter('client');
+  $cl_project = $request->getParameter('project');
+  $cl_task = $request->getParameter('task');
+  $cl_billable = 1;
+  if (in_array('iv', explode(',', $user->plugins)))
+    $cl_billable = $request->getParameter('billable');
+} else {
+  $cl_client = $time_rec['client_id'];
+  $cl_project = $time_rec['project_id'];
+  $cl_task = $time_rec['task_id'];
+  $cl_start = $time_rec['start'];
+  $cl_finish = $time_rec['finish'];
+  $cl_duration = $time_rec['duration'];
+  $cl_date     = $item_date->toString($user->date_format);    
+  $cl_note = $time_rec['comment'];
+    
+  // If we have custom fields - obtain values for them.
+  if ($custom_fields) {
+    // Get custom field value for time record.
+       $fields = $custom_fields->get($time_rec['id']);
+       if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+      $cl_cf_1 = $fields[0]['value'];
+    else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+      $cl_cf_1 = $fields[0]['option_id'];
+  }
+  
+  $cl_billable = $time_rec['billable'];
+  
+  // Add an info message to the form if we are editing an uncompleted record.
+  if (($cl_start == $cl_finish) && ($cl_duration == '0:00')) {
+    $cl_finish = '';
+    $cl_duration = '';
+    $messages->add($i18n->getKey('form.time_edit.uncompleted'));
+  }
+}
+  
+// Initialize elements of 'timeRecordForm'.
+$form = new Form('timeRecordForm');
+
+// Dropdown for clients in MODE_TIME. Use all active clients.
+if (MODE_TIME == $user->tracking_mode && in_array('cl', explode(',', $user->plugins))) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$active_clients,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+    ));
+  // Note: in other modes the client list is filtered to relevant clients only. See below.
+}
+
+if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  // Dropdown for projects assigned to user.
+  $project_list = $user->getAssignedProjects();
+  $form->addInput(array('type'=>'combobox',
+    'onchange'=>'fillTaskDropdown(this.value);',
+    'name'=>'project',
+    'style'=>'width: 250px;',
+    'value'=>$cl_project,
+    'data'=>$project_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+  ));
+
+  // Dropdown for clients if the clients plugin is enabled.
+  if (in_array('cl', explode(',', $user->plugins))) {
+    $active_clients = ttTeamHelper::getActiveClients($user->team_id, true);
+    // We need an array of assigned project ids to do some trimming. 
+    foreach($project_list as $project)
+      $projects_assigned_to_user[] = $project['id'];
+
+    // Build a client list out of active clients. Use only clients that are relevant to user.
+    // Also trim their associated project list to only assigned projects (to user).
+    foreach($active_clients as $client) {
+         $projects_assigned_to_client = explode(',', $client['projects']);
+         $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user);
+         if ($intersection) {
+           $client['projects'] = implode(',', $intersection);
+           $client_list[] = $client;
+         }
+    }
+    $form->addInput(array('type'=>'combobox',
+      'onchange'=>'fillProjectDropdown(this.value);',
+      'name'=>'client',
+      'style'=>'width: 250px;',
+      'value'=>$cl_client,
+      'data'=>$client_list,
+      'datakeys'=>array('id', 'name'),
+      'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+    ));
+  }
+}
+
+if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+  $task_list = ttTeamHelper::getActiveTasks($user->team_id);
+  $form->addInput(array('type'=>'combobox',
+    'name'=>'task',
+    'style'=>'width: 250px;',
+    'value'=>$cl_task,
+    'data'=>$task_list,
+    'datakeys'=>array('id','name'),
+    'empty'=>array(''=>$i18n->getKey('dropdown.select'))
+  ));
+}
+  
+// Add other controls.
+if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+  $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');"));
+  $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');"));
+}
+if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) {
+  // Make the start and finish fields read-only.
+  $form->getElement('start')->setEnable(false);
+  $form->getElement('finish')->setEnable(false);       
+}
+if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+  $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');"));
+$form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date));
+$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height: 60px;','value'=>$cl_note));
+// If we have custom fields - add controls for them.
+if ($custom_fields && $custom_fields->fields[0]) {
+  // Only one custom field is supported at this time.
+  if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) {
+       $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1));
+  } else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) {
+    $form->addInput(array('type'=>'combobox',
+      'name'=>'cf_1',
+      'style'=>'width: 250px;',
+      'value'=>$cl_cf_1,
+      'data'=>$custom_fields->options,
+      'empty' => array('' => $i18n->getKey('dropdown.select'))
+    ));
+  }
+}
+// Hidden control for record id.
+$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id));
+if (in_array('iv', explode(',', $user->plugins)))
+  $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable));
+$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save click.
+$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save')));
+$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete')));
+
+if ($request->getMethod() == 'POST') {
+
+  // Validate user input.
+  if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client)
+    $errors->add($i18n->getKey('error.client'));
+  if ($custom_fields) {
+    if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']);
+  }
+  if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+    if (!$cl_project) $errors->add($i18n->getKey('error.project'));            
+  }
+  if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) {
+    if (!$cl_task) $errors->add($i18n->getKey('error.task'));
+  }
+  if (!$cl_duration) {
+    if ('0' == $cl_duration)
+      $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+    else if ($cl_start || $cl_finish) {
+      if (!ttTimeHelper::isValidTime($cl_start))
+        $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start'));
+      if ($cl_finish) {
+        if (!ttTimeHelper::isValidTime($cl_finish))
+          $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish'));
+        if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish))
+          $errors->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start'));
+      }
+    } else {
+      if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) {
+        $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start'));
+        $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish'));
+      }
+      if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type))
+        $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration'));
+    }
+  } else {
+    if (!ttTimeHelper::isValidDuration($cl_duration))
+      $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration'));
+  }
+  if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date'));
+  if (!ttValidString($cl_note, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.note'));
+  // Finished validating user input.
+    
+  // Determine lock date.
+  $lock_interval = $user->lock_interval;
+  $lockdate = 0;
+  if ($lock_interval > 0) {
+    $lockdate = new DateAndTime();
+    $lockdate->decDay($lock_interval);
+  }
+
+  // This is a new date for the time record.
+  $new_date = new DateAndTime($user->date_format, $cl_date);
+  
+  // Prohibit creating entries in future.
+  if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) {
+    $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null));
+    if ($new_date->after($browser_today))
+      $errors->add($i18n->getKey('error.future_date'));
+  }
+
+  // Save record.
+  if ($request->getParameter('btn_save')) {
+    // We need to:
+    // 1) Prohibit saving locked time entries in any form.
+    // 2) Prohibit saving completed unlocked entries into locked interval.
+    // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+      
+    // Now, step by step.
+    if ($errors->isEmpty()) {
+      // 1) Prohibit saving locked time entries in any form.
+      if($lockdate && $item_date->before($lockdate))
+        $errors->add($i18n->getKey('error.period_locked'));        
+      // 2) Prohibit saving completed unlocked entries into locked interval.
+      if($errors->isEmpty() && $lockdate && $new_date->before($lockdate))
+        $errors->add($i18n->getKey('error.period_locked'));        
+      // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists.
+      $uncompleted = ($cl_finish == '' && $cl_duration == '');
+      if ($uncompleted) {
+        $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser());
+        if ($not_completed_rec && ($time_rec['id'] <> $not_completed_rec['id'])) {
+          // We have another not completed record.
+          $errors->add($i18n->getKey('error.uncompleted_exists')." <a href = 'time_edit.php?id=".$not_completed_rec['id']."'>".$i18n->getKey('error.goto_uncompleted')."</a>");
+        }
+      }
+    }
+    
+    // Prohibit creating an overlapping record.
+    if ($errors->isEmpty()) {
+      if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id))
+        $errors->add($i18n->getKey('error.overlap'));
+    }
+
+    // Now, an update.
+    if ($errors->isEmpty()) {
+      $res = ttTimeHelper::update(array(
+          'id'=>$cl_id,  
+          'date'=>$new_date->toString(DB_DATEFORMAT),
+          'user_id'=>$user->getActiveUser(),
+          'client'=>$cl_client,
+          'project'=>$cl_project,
+          'task'=>$cl_task,
+          'start'=>$cl_start,
+          'finish'=>$cl_finish,
+          'duration'=>$cl_duration,
+          'note'=>$cl_note,
+          'billable'=>$cl_billable));
+       
+      // If we have custom fields - update values.
+      if ($res && $custom_fields) {
+        if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT)
+          $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], null, $cl_cf_1);
+        else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)
+          $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], $cl_cf_1, null);
+      }
+      if ($res)
+      {
+        header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT));
+        exit();
+      }
+    }
+  }
+      
+  if ($request->getParameter('btn_delete')) {
+    header("Location: time_delete.php?id=$cl_id");
+    exit();
+  }
+} // End of if ($request->getMethod() == 'POST')
+
+$smarty->assign('client_list', $client_list);
+$smarty->assign('project_list', $project_list);
+$smarty->assign('task_list', $task_list);
+$smarty->assign('forms', array($form->getName()=>$form->toArray()));
+$smarty->assign('onload', 'onLoad="fillDropdowns()"');
+$smarty->assign('title', $i18n->getKey('title.edit_time_record'));
+$smarty->assign('content_page_name', 'mobile/time_edit.tpl');
+$smarty->display('mobile/index.tpl');
+?>
\ No newline at end of file
diff --git a/plugins/CustomFields.class.php b/plugins/CustomFields.class.php
new file mode 100644 (file)
index 0000000..6eff554
--- /dev/null
@@ -0,0 +1,313 @@
+<?php
+// +----------------------------------------------------------------------+
+// | Anuko Time Tracker
+// +----------------------------------------------------------------------+
+// | Copyright (c) Anuko International Ltd. (https://www.anuko.com)
+// +----------------------------------------------------------------------+
+
+class CustomFields {
+
+  // Definitions of custom field types.
+
+  const TYPE_TEXT = 1;     // A text field.
+  const TYPE_DROPDOWN = 2; // A dropdown field with pre-defined values.
+
+  var $fields = array();  // Array of custom fields for team.
+  var $options = array(); // Array of options for a dropdown custom field.
+  
+  // Constructor.
+  function CustomFields($team_id) {
+       $mdb2 = getConnection();
+       
+       // Get fields.
+       $sql = "select id, type, label, required from tt_custom_fields where team_id = $team_id and status = 1 and type > 0";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $this->fields[] = array('id'=>$val['id'],'type'=>$val['type'],'label'=>$val['label'],'required'=>$val['required'],'value'=>'');
+      }
+    }
+      
+    // If we have a dropdown obtain options for it.
+    if ((count($this->fields) > 0) && ($this->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)) {
+      $sql = "select id, value from tt_custom_field_options where field_id = ".$this->fields[0]['id']." order by value";
+      $res = $mdb2->query($sql);
+      if (!is_a($res, 'PEAR_Error')) {
+        while ($val = $res->fetchRow()) {
+          $this->options[$val['id']] = $val['value'];
+        }
+      }
+    }
+  }
+  
+  function insert($log_id, $field_id, $option_id, $value) {
+       
+       $mdb2 = getConnection();    
+    $sql = "insert into tt_custom_field_log (log_id, field_id, option_id, value) values($log_id, $field_id, ".$mdb2->quote($option_id).", ".$mdb2->quote($value).")";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+
+  function update($log_id, $field_id, $option_id, $value) {
+       if (!$field_id)
+         return true; // Nothing to update.
+       
+       // Remove older custom field values, if any.
+       $res = $this->delete($log_id);
+       if (!$res)
+         return false;
+         
+       if (!$value && !$option_id)
+         return true; // Do not insert NULL values.
+         
+    return $this->insert($log_id, $field_id, $option_id, $value);
+  }
+  
+  function delete($log_id) {
+       
+       $mdb2 = getConnection();    
+    $sql = "update tt_custom_field_log set status = NULL where log_id = $log_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  function get($log_id) {
+       $fields = array();
+       
+       $mdb2 = getConnection();    
+    $sql = "select id, field_id, option_id, value from tt_custom_field_log where log_id = $log_id and status = 1";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+       $fields[] = $val;
+      }
+      return $fields;
+    }
+    return false;
+  }
+
+  // insertOption adds a new option to a custom field.
+  static function insertOption($field_id, $option_name) {
+       
+       $mdb2 = getConnection();
+       
+       // Check if the option exists.
+       $id = 0;
+       $sql = "select id from tt_custom_field_options where field_id = $field_id and value = ".$mdb2->quote($option_name);
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error'))
+         return false;
+    if ($val = $res->fetchRow()) $id = $val['id'];
+    
+    // Insert option.
+    if (!$id) {
+      $sql = "insert into tt_custom_field_options (field_id, value) values($field_id, ".$mdb2->quote($option_name).")";
+      $affected = $mdb2->exec($sql);
+      if (is_a($affected, 'PEAR_Error'))
+           return false;
+    }
+    return true;
+  }
+  
+  // updateOption updates option name.
+  static function updateOption($id, $option_name) {
+       
+    $mdb2 = getConnection();
+       
+    $sql = "update tt_custom_field_options set value = ".$mdb2->quote($option_name)." where id = $id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // delete Option deletes an option and all custom field log entries that used it.
+  static function deleteOption($id) {
+       global $user;
+       $mdb2 = getConnection();
+       
+       $field_id = CustomFields::getFieldIdForOption($id);
+       
+       // First make sure that the field is ours.
+       $sql = "select team_id from tt_custom_fields where id = $field_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error'))
+         return false;
+       $val = $res->fetchRow();
+       if ($user->team_id != $val['team_id'])
+         return false;
+         
+    // Delete log entries with this option.
+    $sql = "update tt_custom_field_log set status = NULL where field_id = $field_id and value = ".$mdb2->quote($id);
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+         return false;                 
+               
+       // Delete the option.
+       $sql = "delete from tt_custom_field_options where id = $id";
+       $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // getOptions returns an array of options for a custom field.
+  static function getOptions($field_id) {
+       global $user;
+       $mdb2 = getConnection();
+       $options = array();
+       
+       // First make sure that the field is ours.
+       $sql = "select team_id from tt_custom_fields where id = $field_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error'))
+         return false;
+       $val = $res->fetchRow();
+       if ($user->team_id != $val['team_id'])
+         return false;
+       
+    // Get options.
+    $sql = "select id, value from tt_custom_field_options where field_id = $field_id order by value";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $options[$val['id']] = $val['value'];
+      }
+      return $options;
+    }
+    return false;
+  }
+  
+  // getOptiondName returns an option name for a custom field.
+  static function getOptionName($id) {
+    global $user;
+       $mdb2 = getConnection();
+
+       $field_id = CustomFields::getFieldIdForOption($id);
+       
+       // First make sure that the field is ours.
+       $sql = "select team_id from tt_custom_fields where id = $field_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error'))
+         return false;
+       $val = $res->fetchRow();
+       if ($user->team_id != $val['team_id'])
+         return false;
+         
+       // Get option name.
+       $sql = "select value from tt_custom_field_options where id = $id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      $name = $val['value'];
+      return $name;
+    }
+    return false;
+  }
+  
+  // getFields returns an array of custom fields for team.
+  static function getFields() {
+       global $user;
+       $mdb2 = getConnection();    
+       
+       $fields = array();
+       $sql = "select id, type, label from tt_custom_fields where team_id = $user->team_id and status = 1 and type > 0";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      while ($val = $res->fetchRow()) {
+        $fields[] = array('id'=>$val['id'],'type'=>$val['type'],'label'=>$val['label']);
+      }
+      return $fields;
+    }
+    return false;
+  }
+
+  // getField returns a custom field.
+  static function getField($id) {
+       global $user;
+       $mdb2 = getConnection();    
+       
+       $sql = "select label, type, required from tt_custom_fields where id = $id and team_id = $user->team_id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      if (!$val)
+        return false;
+      return $val;
+    }
+    return false;
+  }
+  
+  // getFieldIdForOption returns field id from an associated option id.
+  static function getFieldIdForOption($option_id) {
+       $mdb2 = getConnection();    
+       
+       $sql = "select field_id from tt_custom_field_options where id = $option_id";
+    $res = $mdb2->query($sql);
+    if (!is_a($res, 'PEAR_Error')) {
+      $val = $res->fetchRow();
+      $field_id = $val['field_id'];
+      return $field_id;
+    }
+    return false;
+  }
+  
+  // The insertField inserts a custom field for team.
+  static function insertField($field_name, $field_type, $required) {
+       
+       global $user;
+       
+       $mdb2 = getConnection();
+       
+    $sql = "insert into tt_custom_fields (team_id, type, label, required, status) values($user->team_id, $field_type, ".$mdb2->quote($field_name).", $required, 1)";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+
+  // The updateField updates custom field for team.
+  static function updateField($id, $name, $type, $required) {
+       
+       global $user;
+       
+       $mdb2 = getConnection();
+       
+    $sql = "update tt_custom_fields set label = ".$mdb2->quote($name).", type = $type, required = $required where id = $id and team_id = $user->team_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+  
+  // The deleteField deletes a custom field, its options and log entries for team.
+  static function deleteField($field_id) {
+       
+       // Our overall intention is to keep the code simple and manageable.
+       // If a users wishes to delete a field, we will delete all its options and log entries.
+       // Otherwise we have to do conditional queries depending on field status (this complicates things).
+       
+       global $user;
+       $mdb2 = getConnection();
+       
+       // First make sure that the field is ours so that we can safely delete it.
+       $sql = "select team_id from tt_custom_fields where id = $field_id";
+       $res = $mdb2->query($sql);
+       if (is_a($res, 'PEAR_Error'))
+         return false;
+       $val = $res->fetchRow();
+       if ($user->team_id != $val['team_id'])
+         return false;
+       
+       // Mark log entries as deleted.
+       $sql = "update tt_custom_field_log set status = NULL where field_id = $field_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+         return false;
+
+       // Delete field options.
+       $sql = "delete from tt_custom_field_options where field_id = $field_id";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+         return false;         
+
+       // Delete the field.
+       $sql = "delete from tt_custom_fields where id = $field_id and team_id = $user->team_id";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
+}
+?>
\ No newline at end of file