steffen: server/kolab-horde-framework/kolab-horde-framework/Horde/Horde Config.php, NONE, 1.1 Help.php, NONE, 1.1 Registry.php, NONE, 1.1

cvs at intevation.de cvs at intevation.de
Fri Oct 14 16:33:07 CEST 2005


Author: steffen

Update of /kolabrepository/server/kolab-horde-framework/kolab-horde-framework/Horde/Horde
In directory doto:/tmp/cvs-serv28903/kolab-horde-framework/kolab-horde-framework/Horde/Horde

Added Files:
	Config.php Help.php Registry.php 
Log Message:
Separated Horde Framework from kolab-resource-handlers

--- NEW FILE: Config.php ---
<?php
/**
 * The Config:: package provides a framework for managing the
 * configuration of Horde applications, writing conf.php files from
 * conf.xml source files, generating user interfaces, etc.
 *
 * $Horde: framework/Horde/Horde/Config.php,v 1.69 2004/04/20 21:42:18 jan Exp $
 *
 * Copyright 2002-2004 Chuck Hagenbuch <chuck at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package Horde_Framework
 */
class Horde_Config {

    /**
     * The name of the configured application.
     *
     * @var string $_app
     */
    var $_app;

    /**
     * The XML tree of the configuration file traversed to an
     * associative array.
     *
     * @var array $_xmlConfigTree
     */
    var $_xmlConfigTree = null;

    /**
     * The content of the generated configuration file.
     *
     * @var string $_phpConfig
     */
    var $_phpConfig;

    /**
     * The content of the old configuration file.
     *
     * @var string $_oldConfig
     */
    var $_oldConfig;

    /**
     * The manual configuration in front of the generated
     * configuration.
     *
     * @var string $_preConfig
     */
    var $_preConfig;

    /**
     * The manual configuration after the generated configuration.
     *
     * @var string $_preConfig
     */
    var $_postConfig;

    /**
     * The current $conf array of the configured application.
     *
     * @var array $_currentConfig
     */
    var $_currentConfig = array();

    /**
     * The CVS version tag of the conf.xml file which will be copied
     * into the conf.php file.
     *
     * @var string $_versionTag
     */
    var $_versionTag = '';

    /**
     * The line marking the begin of the generated configuration.
     *
     * @var string $_configBegin
     */
    var $_configBegin = "/* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */\n";

    /**
     * The line marking the end of the generated configuration.
     *
     * @var string $_configEnd
     */
    var $_configEnd = "/* CONFIG END. DO NOT CHANGE ANYTHING IN OR BEFORE THIS LINE. */\n";

    /**
     * Constructor.
     *
     * @param string $app  The name of the application to be configured.
     */
    function Horde_Config($app)
    {
        $this->_app = $app;
    }

    /**
     * Reads the application's conf.xml file and builds an associative
     * array from its XML tree.
     *
     * @return array  An associative array representing the configuration tree.
     */
    function readXMLConfig()
    {
        if (is_null($this->_xmlConfigTree)) {
            require_once 'Horde/Text.php';

            global $registry;
            $path = $registry->getParam('fileroot', $this->_app) . '/config';

            /* Fetch the current conf.php contents. */
            @eval($this->getPHPConfig());
            if (isset($conf)) {
                $this->_currentConfig = $conf;
            }

            /* Set up the domxml document. */
            $this->_xmlConfigTree = array();
            $doc = domxml_open_file($path . '/conf.xml');

            /* Check if there is a CVS version tag and store it. */
            $node = $doc->first_child();
            while (!empty($node)) {
                if ($node->node_type() == XML_COMMENT_NODE) {
                    if (preg_match('/\$.*?conf\.xml,v .*? .*\$/', $node->node_value(), $match)) {
                        $this->_versionTag = $match[0] . "\n";
                        break;
                    }
                }
                $node = $node->next_sibling();
            }

            /* Parse remaining config file. */
            $root = $doc->root();
            if ($root->has_child_nodes()) {
                $this->_parseLevel($this->_xmlConfigTree, $root->child_nodes(), '');
            }
        }

        return $this->_xmlConfigTree;
    }

    /**
     * Returns the file content of the current configuration file.
     *
     * @return string  The unparsed configuration file content.
     */
    function getPHPConfig()
    {
        if (is_null($this->_oldConfig)) {
            global $registry;
            $path = $registry->getParam('fileroot', $this->_app) . '/config';
            $size = @filesize($path . '/conf.php');
            if ($size && is_resource($fp = @fopen($path . '/conf.php', 'r'))) {
                $this->_oldConfig = @fread($fp, $size);
                $this->_oldConfig = preg_replace('/<\?php\n?/', '', $this->_oldConfig);
                $pos = strpos($this->_oldConfig, $this->_configBegin);
                if ($pos !== false) {
                    $this->_preConfig = substr($this->_oldConfig, 0, $pos);
                    $this->_oldConfig = substr($this->_oldConfig, $pos);
                }
                $pos = strpos($this->_oldConfig, $this->_configEnd);
                if ($pos !== false) {
                    $this->_postConfig = substr($this->_oldConfig, $pos + strlen($this->_configEnd));
                    $this->_oldConfig = substr($this->_oldConfig, 0, $pos);
                }
            } else {
                $this->_oldConfig = '';
            }
        }
        return $this->_oldConfig;
    }
            
    /**
     * Generates the content of the application's configuration file.
     *
     * @param Variables $formvars  The processed configuration form data.
     *
     * @return string  The content of the generated configuration file.
     */
    function generatePHPConfig($formvars)
    {
        $this->readXMLConfig();
        $this->getPHPConfig();

        $this->_phpConfig = "<?php\n";
        $this->_phpConfig .= $this->_preConfig;
        $this->_phpConfig .= $this->_configBegin;
        if (!empty($this->_versionTag)) {
            $this->_phpConfig .= '// ' . $this->_versionTag;
        }
        $this->_generatePHPConfig($this->_xmlConfigTree, '', $formvars);
        $this->_phpConfig .= $this->_configEnd;
        $this->_phpConfig .= $this->_postConfig;

        return $this->_phpConfig;
    }

    /**
     * Generates the configuration file items for a part of the configuration
     * tree.
     *
     * @access private
     *
     * @param array $section  An associative array containing the part of the
     *                        traversed XML configuration tree that should be
     *                        processed.
     * @param string $prefix  A configuration prefix determining the current
     *                        position inside the configuration file. This
     *                        prefix will be translated to keys of the $conf
     *                        array in the generated configuration file.
     * @param Variables $formvars  The processed configuration form data.
     */
    function _generatePHPConfig($section, $prefix, $formvars)
    {
        foreach ($section as $name => $configitem) {
            $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name;
            $configname = $prefixedname;
            $quote = !isset($configitem['quote']) || $configitem['quote'] !== false;
            if ($configitem == 'placeholder') {
                $this->_phpConfig .= '$conf[\'' . str_replace('|', '\'][\'', $prefix) . "'] = array();\n";
            } elseif (isset($configitem['switch'])) {
                $val = $formvars->getExists($configname, $wasset);
                if (!$wasset) {
                    $val = isset($configitem['default']) ? $configitem['default'] : null;
                }
                if (isset($configitem['switch'][$val])) {
                    $value = $val;
                    if ($quote && $value != 'true' && $value != 'false') {
                        $value = "'" . $value . "'";
                    }
                    $this->_generatePHPConfig($configitem['switch'][$val]['fields'], $prefix, $formvars);
                }
            } elseif (isset($configitem['_type'])) {
                $val = $formvars->getExists($configname, $wasset);
                if (!$wasset) {
                    $val = isset($configitem['default']) ? $configitem['default'] : null;
                }

                $type = $configitem['_type'];
                switch ($type) {
                case 'multienum':
                    if (is_array($val)) {
                        $encvals = array();
                        foreach ($val as $v) {
                            $encvals[] = $this->_quote($v);
                        }
                        $arrayval = "'" . implode('\', \'', $encvals) . "'";
                        if ($arrayval == "''") {
                            $arrayval = '';
                        }
                    } else {
                        $arrayval = '';
                    }
                    $value = 'array(' . $arrayval . ')';
                    break;

                case 'boolean':
                    if (is_bool($val)) {
                        $value = $val ? 'true' : 'false';
                    } else {
                        $value = ($val == 'on') ? 'true' : 'false';
                    }
                    break;

                case 'stringlist':
                    $values = explode(',', $val);
                    if (!is_array($values)) {
                        $value = "array('" . $this->_quote(trim($values)) . "')";
                    } else {
                        $encvals = array();
                        foreach ($values as $v) {
                            $encvals[] = $this->_quote(trim($v));
                        }
                        $arrayval = "'" . implode('\', \'', $encvals) . "'";
                        if ($arrayval == "''") {
                            $arrayval = '';
                        }
                        $value = 'array(' . $arrayval . ')';
                    }
                    break;

                case 'int':
                    if ($val != '') {
                        $value = (int)$val;
                    }
                    break;

                case 'octal':
                    $value = sprintf('0%o', octdec($val));
                    break;

                case 'header':
                case 'description':
                    break;

                default:
                    if ($val != '') {
                        $value = $val;
                        if ($quote && $value != 'true' && $value != 'false') {
                            $value = "'" . $this->_quote($value) . "'";
                        }
                    }
                    break;
                }
            } else {
                $this->_generatePHPConfig($configitem, $prefixedname, $formvars);
            }

            if (isset($value)) {
                $this->_phpConfig .= '$conf[\'' . str_replace('|', '\'][\'', $configname) . '\'] = ' . $value . ";\n";
            }
            unset($value);
        }
    }

    /**
     * Parses one level of the configuration XML tree into the associative
     * array containing the traversed configuration tree.
     *
     * @access private
     *
     * @param array &$conf     The already existing array where the processed
     *                         XML tree portion should be appended to.
     * @param array $children  An array containing the XML nodes of the level
     *                         that should be parsed.
     * @param string $ctx      A string representing the current position
     *                         (context prefix) inside the configuration XML
     *                         file.
     */
    function _parseLevel(&$conf, $children, $ctx)
    {
        foreach ($children as $node) {
            if ($node->type != XML_ELEMENT_NODE) {
                continue;
            }
            $name = $node->get_attribute('name');
            $desc = Text::linkUrls($node->get_attribute('desc'));
            $required = !($node->get_attribute('required') == 'false');
            $quote = !($node->get_attribute('quote') == 'false');
            if (!empty($ctx)) {
                $curctx = $ctx . '|' . $name;
            } else {
                $curctx = $name;
            }

            switch ($node->tagname) {
            case 'configdescription':
                if (empty($name)) {
                    $name = md5(microtime());
                }
                $conf[$name] = array('_type' => 'description',
                                     'desc' => Text::linkUrls($this->_default($curctx, $this->_getNodeOnlyText($node))));
                break;

            case 'configheader':
                if (empty($name)) {
                    $name = md5(microtime());
                }
                $conf[$name] = array('_type' => 'header',
                                     'desc' => $this->_default($curctx, $this->_getNodeOnlyText($node)));
                break;

            case 'configswitch':
                if ($quote) {
                    $default = $this->_default($curctx, $this->_getNodeOnlyText($node));
                } else {
                    $default = $this->_defaultRaw($curctx, $this->_getNodeOnlyText($node));
                }
                $conf[$name] = array('desc' => $desc,
                                     'switch' => $this->_getSwitchValues($node, $ctx),
                                     'default' => $default);
                break;

            case 'configenum':
                $values = $this->_getEnumValues($node);
                if ($quote) {
                    $default = $this->_default($curctx, $this->_getNodeOnlyText($node));
                } else {
                    $default = $this->_defaultRaw($curctx, $this->_getNodeOnlyText($node));
                }
                $conf[$name] = array('_type' => 'enum',
                                     'required' => $required,
                                     'quote' => $quote,
                                     'values' => $values,
                                     'desc' => $desc,
                                     'default' => $default);
                break;

            case 'configlist':
                $default = $this->_default($curctx, null);
                if ($default === null) {
                    $default = $this->_getNodeOnlyText($node);
                } else {
                    $default = implode(', ', $default);
                }
                $conf[$name] = array('_type' => 'stringlist',
                                     'required' => $required,
                                     'desc' => $desc,
                                     'default' => $default);
                break;

            case 'configmultienum':
                $values = $this->_getEnumValues($node);
                require_once 'Horde/Array.php';
                $conf[$name] = array('_type' => 'multienum',
                                     'required' => $required,
                                     'values' => $values,
                                     'desc' => $desc,
                                     'default' => Horde_Array::valuesToKeys($this->_default($curctx,
                                                                                            explode(',', $this->_getNodeOnlyText($node)))));
                break;

            case 'configpassword':
                $conf[$name] = array('_type' => 'password',
                                     'required' => $required,
                                     'desc' => $desc,
                                     'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)));
                break;

            case 'configstring':
                $conf[$name] = array('_type' => 'text',
                                     'required' => $required,
                                     'desc' => $desc,
                                     'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)));
                if ($conf[$name]['default'] === false) {
                    $conf[$name]['default'] = 'false';
                } else if ($conf[$name]['default'] === true) {
                    $conf[$name]['default'] = 'true';
                }
                break;

            case 'configboolean':
                $default = $this->_getNodeOnlyText($node);
                if (empty($default) || $default === 'false') {
                    $default = false;
                } else {
                    $default = true;
                }
                $conf[$name] = array('_type' => 'boolean',
                                     'required' => $required,
                                     'desc' => $desc,
                                     'default' => $this->_default($curctx, $default));
                break;

            case 'configinteger':
                $values = $this->_getEnumValues($node);
                $conf[$name] = array('_type' => 'int',
                                     'required' => $required,
                                     'values' => $values,
                                     'desc' => $desc,
                                     'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)));
                if ($node->get_attribute('octal') == 'true' &&
                    $conf[$name]['default'] != '') {
                    $conf[$name]['_type'] = 'octal';
                    $conf[$name]['default'] = sprintf('0%o', $this->_default($curctx, octdec($this->_getNodeOnlyText($node))));
                }
                break;

            case 'configphp':
                $conf[$name] = array('_type' => 'php',
                                     'required' => $required,
                                     'quote' => false,
                                     'desc' => $desc,
                                     'default' => $this->_defaultRaw($curctx, $this->_getNodeOnlyText($node)));
                break;

            case 'configsection':
                $conf[$name] = array();
                $cur = &$conf[$name];
                if ($node->has_child_nodes()) {
                    $this->_parseLevel($cur, $node->child_nodes(), $curctx);
                }
                break;

            case 'configtab':
                $key = md5(microtime());
                $conf[$key] = array('tab' => $name,
                                    'desc' => $desc);
                if ($node->has_child_nodes()) {
                    $this->_parseLevel($conf, $node->child_nodes(), $ctx);
                }
                break;

            case 'configplaceholder':
                $conf[md5(microtime())] = 'placeholder';
                break;

            default:
                $conf[$name] = array();
                $cur = &$conf[$name];
                if ($node->has_child_nodes()) {
                    $this->_parseLevel($cur, $node->child_nodes(), $curctx);
                }
                break;
            }
        }
    }

    /**
     * Returns a certain value from the current configuration array or
     * a default value, if not found.
     *
     * @access private
     *
     * @param string $ctx     A string representing the key of the
     *                        configuration array to return.
     * @param mixed $default  The default value to return if the key wasn't
     *                        found.
     *
     * @return mixed  Either the value of the configuration array's requested
     *                key or the default value if the key wasn't found.
     */
    function _default($ctx, $default)
    {
        $ctx = explode('|', $ctx);
        $ptr = $this->_currentConfig;
        for ($i = 0; $i < count($ctx); $i++) {
            if (!isset($ptr[$ctx[$i]])) {
                return $default;
            } else {
                $ptr = $ptr[$ctx[$i]];
            }
        }
        if (is_string($ptr)) {
            return String::convertCharset($ptr, 'iso-8859-1');
        } else {
            return $ptr;
        }
    }

    /**
     * Returns a certain value from the current configuration file or
     * a default value, if not found.
     * It does NOT return the actual value, but the PHP expression as used
     * in the configuration file.
     *
     * @access private
     *
     * @param string $ctx     A string representing the key of the
     *                        configuration array to return.
     * @param mixed $default  The default value to return if the key wasn't
     *                        found.
     *
     * @return mixed  Either the value of the configuration file's requested
     *                key or the default value if the key wasn't found.
     */
    function _defaultRaw($ctx, $default)
    {
        $ctx = explode('|', $ctx);
        $pattern = '/^\$conf\[\'' . implode("'\]\['", $ctx) . '\'\] = (.*);$/m';
        if (preg_match($pattern, $this->getPHPConfig(), $matches)) {
            return $matches[1];
        }
        return $default;
    }

    /**
     * Returns the content of all text node children of the specified node.
     *
     * @access private
     *
     * @param DomNode $node  A DomNode object whose text node children to return.
     *
     * @return string  The concatenated values of all text nodes.
     */
    function _getNodeOnlyText($node)
    {
        $text = '';
        if (!$node->has_child_nodes()) {
            return $node->get_content();
        }
        foreach ($node->children() as $tnode) {
            if ($tnode->type == XML_TEXT_NODE) {
                $text .= $tnode->content;
            }
        }

        return trim($text);
    }

    /**
     * Returns an associative array containing all possible values of the
     * specified <configenum> tag.
     * The keys contain the actual enum values while the values contain their
     * corresponding descriptions.
     *
     * @access private
     *
     * @param DomNode $node  The DomNode representation of the <configenum> tag
     *                       whose values should be returned.
     *
     * @return array  An associative array with all possible enum values.
     */
    function _getEnumValues($node)
    {
        $values = array();
        if (!$node->has_child_nodes()) {
            return array();
        }
        foreach ($node->children() as $vnode) {
            if ($vnode->type == XML_ELEMENT_NODE &&
                $vnode->tagname == 'values') {
                if (!$vnode->has_child_nodes()) {
                    return array();
                }
               foreach ($vnode->children() as $value) {
                    if ($value->type == XML_ELEMENT_NODE) {
                        if ($value->tagname == 'configspecial') {
                            return $this->_handleSpecials($value);
                        } elseif ($value->tagname == 'value') {
                            $text = $value->get_content();
                            $desc = $value->get_attribute('desc');
                            if (!empty($desc)) {
                                $values[$text] = $desc;
                            } else {
                                $values[$text] = $text;
                            }
                        }
                    }
                }
            }
        }
        return $values;
    }

    /**
     * Returns a multidimensional associative array representing the specified
     * <configswitch> tag.
     *
     * @access private
     *
     * @param DomNode &$node  The DomNode representation of the <configswitch>
     *                        tag to process.
     *
     * @return array  An associative array representing the node.
     */
    function _getSwitchValues(&$node, $curctx)
    {
        if (!$node->has_child_nodes()) {
            return array();
        }
        $values = array();
        foreach ($node->children() as $case) {
            if ($case->type == XML_ELEMENT_NODE) {
                $name = $case->get_attribute('name');
                $values[$name] = array();
                $values[$name]['desc'] = $case->get_attribute('desc');
                $values[$name]['fields'] = array();
                if ($case->has_child_nodes()) {
                    $this->_parseLevel($values[$name]['fields'], $case->child_nodes(), $curctx);
                }
            }
        }
        return $values;
    }

    /**
     * Returns an associative array containing the possible values of a
     * <configspecial> tag as used inside of enum configurations.
     *
     * @access private
     *
     * @param DomNode $node  The DomNode representation of the <configspecial>
     *                       tag.
     *
     * @return array  An associative array with the possible values.
     */
    function _handleSpecials($node)
    {
        switch ($node->get_attribute('name')) {
        case 'list-horde-apps':
            global $registry;
            require_once 'Horde/Array.php';
            $apps = Horde_Array::valuesToKeys($registry->listApps(array('hidden', 'notoolbar', 'active')));
            asort($apps);
            return $apps;
            break;

        case 'list-horde-languages':
            global $nls;
            return $nls['languages'];
            break;

        case 'list-client-fields':
            global $registry;
            $f = array();
            if ($registry->hasMethod('clients/getClientSource')) {
                $addressbook = $registry->call('clients/getClientSource');
                $fields = $registry->call('clients/fields', array($addressbook));
                if (!is_a($fields, 'PEAR_Error')) {
                    foreach ($fields as $field) {
                        $f[$field['name']] = $field['label'];
                    }
                }
            }
            return $f;
            break;
        }

        return array();
    }

    /**
     * Returns the specified string with escaped single quotes
     *
     * @access private
     *
     * @param string $string  A string to escape.
     *
     * @return string  The specified string with single quotes being escaped.
     */
    function _quote($string)
    {
        return str_replace("'", "\'", $string);
    }

}

/**
 * A Horde_Form:: form that implements a user interface for the config
 * system.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package Horde_Framework
 */
class ConfigForm extends Horde_Form {

    /**
     * Contains the Horde_Config object that this form represents.
     *
     * @var Horde_Config $_xmlConfig
     */
    var $_xmlConfig;

    /**
     * Contains the Variables object of this form.
     *
     * @var Variables $_vars
     */
    var $_vars;

    /**
     * Constructor.
     *
     * @param Variables &$vars  The Variables object of this form.
     * @param string $app             The name of the application that this
     *                                configuration form is for.
     */
    function ConfigForm(&$vars, $app)
    {
        parent::Horde_Form($vars);

        $this->_xmlConfig = &new Horde_Config($app);
        $this->_vars = &$vars;
        $config = $this->_xmlConfig->readXMLConfig();
        $this->addHidden('', 'app', 'text', true);
        $this->_buildVariables($config);
    }

    /**
     * Builds the form based on the specified level of the configuration tree.
     *
     * @access private
     *
     * @param array $config   The portion of the configuration tree for that
     *                        the form fields should be created.
     * @param string $prefix  A string representing the current position
     *                        inside the configuration tree.
     */
    function _buildVariables($config, $prefix = '')
    {
        if (!is_array($config)) {
            return;
        }
        foreach ($config as $name => $configitem) {
            $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name;
            $varname = $prefixedname;
            $description = null;
            if ($configitem == 'placeholder') {
                continue;
            } elseif (isset($configitem['tab'])) {
                $this->setSection($configitem['tab'], $configitem['desc']);
            } elseif (isset($configitem['switch'])) {
                $selected = $this->_vars->getExists($varname, $wasset);
                $var_params = array();
                $select_option = true;
                foreach ($configitem['switch'] as $option => $case) {
                    $var_params[$option] = $case['desc'];
                    if ($option == $configitem['default']) {
                        $select_option = false;
                        if (!$wasset) {
                            $selected = $option;
                        }
                    }
                }
                $v = &$this->addVariable($configitem['desc'], $varname, 'enum', true, false, null, array($var_params, $select_option));
                if (array_key_exists('default', $configitem)) {
                    $v->setDefault($configitem['default']);
                }
                $v_action = &Horde_Form_Action::factory('reload');
                $v->setAction($v_action);
                if (isset($selected) && isset($configitem['switch'][$selected])) {
                    $this->_buildVariables($configitem['switch'][$selected]['fields'], $prefix);
                }
            } elseif (isset($configitem['_type'])) {
                $required = (isset($configitem['required'])) ? $configitem['required'] : true;
                $type = $configitem['_type'];
                if ($type == 'multienum' || $type == 'header' ||
                    $type == 'description') {
                    $required = false;
                }
                if ($type == 'multienum' || $type == 'enum') {
                    $var_params = array($configitem['values']);
                } else {
                    $var_params = array();
                }
                if ($type == 'php') {
                    $type = 'text';
                    $description = 'Enter a valid PHP expression.';
                }
                $v = &$this->addVariable($configitem['desc'], $varname, $type, $required, false, $description, $var_params);
                if (isset($configitem['default'])) {
                    $v->setDefault($configitem['default']);
                }
            } else {
                $this->_buildVariables($configitem, $prefixedname);
            }
        }
    }

}

--- NEW FILE: Help.php ---
<?php

/** @constant HELP_SOURCE_RAW Raw help in the string. */
define('HELP_SOURCE_RAW', 0);

/** @constant HELP_SOURCE_FILE Help text is in a file. */
define('HELP_SOURCE_FILE', 1);

/** @constant HELP_SOURCE_DB Help comes from a database. */
define('HELP_SOURCE_DB', 2);

/**
 * The Help:: class provides an interface to the online help subsystem.
 *
 * $Horde: framework/Horde/Horde/Help.php,v 1.57 2004/04/07 14:43:08 chuck Exp $
 *
 * Copyright 1999-2004 Jon Parise <jon at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Jon Parise <jon at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 1.3
 * @package Horde_Framework
 */
class Help {

    /**
     * Handle for the XML parser object.
     *
     * @var object $_parser
     */
    var $_parser = 0;

    /**
     * String buffer to hold the XML help source.
     *
     * @var string $_buffer
     */
    var $_buffer = '';

    /**
     * String containing the ID of the requested help entry.
     *
     * @var string $_reqEntry
     */
    var $_reqEntry = '';

    /**
     * String containing the ID of the current help entry.
     *
     * @var string $_curEntry
     */
    var $_curEntry = '';

    /**
     * String containing the formatted output.
     *
     * @var string $_output
     */
    var $_output = '';

    /**
     * Boolean indicating whether we're inside a <help> block.
     *
     * @var boolean $_inHelp
     */
    var $_inHelp = false;

    /**
     * Boolean indicating whether we're inside the requested block.
     *
     * @var boolean $_inBlock
     */
    var $_inBlock = false;

    /**
     * Boolean indicating whether we're inside a <title> block.
     *
     * @var boolean $_inTitle
     */
    var $_inTitle = false;

    /**
     * Hash containing an index of all of the help entries.
     *
     * @var array $_entries
     */
    var $_entries = array();

    /**
     * String containing the charset of the XML data source.
     *
     * @var string $_charset
     */
    var $_charset = 'iso-8859-1';

    /**
     * Hash of user-defined function handlers for the XML elements.
     *
     * @var array $_handlers
     */
    var $_handlers = array(
        'help'     =>  '_helpHandler',
        'entry'    =>  '_entryHandler',
        'title'    =>  '_titleHandler',
        'heading'  =>  '_headingHandler',
        'para'     =>  '_paraHandler',
        'ref'      =>  '_refHandler',
        'eref'     =>  '_erefHandler',
        'href'     =>  '_hrefHandler',
        'b'        =>  '_bHandler',
        'i'        =>  '_iHandler',
    );


    /**
     * Constructor
     *
     * @access public
     *
     * @param integer $source       The source of the XML help data, based
     *                              on the HELP_SOURCE_* constants.
     * @param optional string $arg  Source-dependent argument for this Help
     *                              instance.
     */
    function Help($source, $arg = null)
    {
        global $language, $nls;

        if (!Util::extensionExists('xml')) {
            Horde::fatal(PEAR::raiseError('The XML functions are not available. Rebuild PHP with --with-xml.'), __FILE__, __LINE__, false);
        }

        $this->_charset = array_key_exists($language, $nls['charsets']) ? $nls['charsets'][$language] : $nls['defaults']['charset'];

        /* Populate $this->_buffer based on $source. */
        switch ($source) {
        case HELP_SOURCE_RAW:
            $this->_buffer = $arg;
            break;

        case HELP_SOURCE_FILE:
            if (!(@file_exists($arg[0]) && ($fp = @fopen($arg[0], 'r')) && ($fs = @filesize($arg[0])) ||
                 @file_exists($arg[1]) && ($fp = @fopen($arg[1], 'r')) && ($fs = @filesize($arg[1])))) {
                $this->_buffer = '';
            } else {
                $this->_buffer = fread($fp, $fs);
                fclose($fp);
            }
            break;

        default:
            $this->_buffer = '';
            break;
        }
    }

    /**
     * Initialzes the XML parser.
     *
     * @access private
     *
     * @return boolean  Returns true on success, false on failure.
     */
    function _init()
    {
        /* Create a new parser and set its default properties. */
        $this->_parser = xml_parser_create();
        xml_set_object($this->_parser, $this);
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
        xml_set_element_handler($this->_parser, '_startElement', '_endElement');
        xml_set_character_data_handler($this->_parser, '_defaultHandler');

        return ($this->_parser != 0);
    }

    /**
     * Cleans up the Help class resources.
     *
     * @access public
     *
     * @return boolean  Returns true on success, false on failure.
     */
    function cleanup()
    {
        $this->_buffer = '';
        return xml_parser_free($this->_parser);
    }

    /**
     * Looks up the requested entry in the XML help buffer.
     *
     * @access public
     *
     * @param string $entry  String containing the entry ID.
     */
    function lookup($entry)
    {
        $this->_output = '';
        $this->_reqEntry = String::upper($entry);
        if (!$this->_parser) {
            $this->_init();
        }
        xml_parse($this->_parser, $this->_buffer, true);
    }

    /**
     * Returns a hash of all of the topics in this help buffer.
     *
     * @access public
     *
     * @return array  Hash of all of the topics in this buffer.
     */
    function topics()
    {
        if (!$this->_parser) {
            $this->_init();
        }
        xml_parse($this->_parser, $this->_buffer, true);

        return $this->_entries;
    }

    /**
     * Display the contents of the formatted output buffer.
     *
     * @access public
     */
    function display()
    {
        echo $this->_output;
    }

    /**
     * User-defined function callback for start elements.
     *
     * @access private
     *
     * @param object $parser  Handle to the parser instance.
     * @param string $name    The name of this XML element.
     * @param array $attrs    List of this element's attributes.
     */
    function _startElement($parser, $name, $attrs)
    {
        /* Call the assigned handler for this element, if one is
         * available. */
        if (in_array($name, array_keys($this->_handlers))) {
            call_user_func(array(&$this, $this->_handlers[$name]), true, $attrs);
        }
    }

    /**
     * User-defined function callback for end elements.
     *
     * @access private
     *
     * @param object $parser  Handle to the parser instance.
     * @param string $name    The name of this XML element.
     */
    function _endElement($parser, $name)
    {
        /* Call the assigned handler for this element, if one is available. */
        if (in_array($name, array_keys($this->_handlers))) {
            call_user_func(array(&$this, $this->_handlers[$name]), false);
        }
    }

    /**
     * User-defined function callback for character data.
     *
     * @access private
     *
     * @param object $parser  Handle to the parser instance.
     * @param string $data    String of character data.
     */
    function _defaultHandler($parser, $data)
    {
        $data = String::convertCharset($data, $this->_charset);
        if ($this->_inTitle) {
            $this->_entries[$this->_curEntry] .= $data;
        }
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= htmlspecialchars($data);
        }
    }

    /**
     * XML element handler for the <help> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _helpHandler($startTag, $attrs = array())
    {
        $this->_inHelp = ($startTag) ? true : false;
    }

    /**
     * XML element handler for the <entry> tag.
     * Attributes: id
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes.
     */
    function _entryHandler($startTag, $attrs = array())
    {
        if (!$startTag) {
            $this->_inBlock = false;
        } else {
            $id = String::upper($attrs['id']);
            $this->_curEntry = $id;
            $this->_entries[$id] = '';
            $this->_inBlock = ($id == $this->_reqEntry);
        }
    }

    /**
     * XML element handler for the <title> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _titleHandler($startTag, $attrs = array())
    {
        $this->_inTitle = $startTag;
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= ($startTag) ? '<h3>' : '</h3>';
        }
    }

    /**
     * XML element handler for the <heading> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _headingHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= ($startTag) ? '<h4>' : '</h4>';
        }
    }

    /**
     * XML element handler for the <para> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _paraHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= ($startTag) ? '<p>' : '</p>';
        }
    }

    /**
     * XML element handler for the <ref> tag.
     * Required attributes: ENTRY, MODULE
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes.
     */
    function _refHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            if ($startTag) {
                $url = Util::addParameter(Horde::selfURL(), 'show', 'entry');
                $url = Util::addParameter($url, 'module', $attrs['MODULE']);
                $url = Util::addParameter($url, 'topic',  $attrs['ENTRY']);
                $this->_output .= Horde::link($url, null, 'helplink');
            } else {
                $this->_output .= '</a>';
            }
        }
    }

    /**
     * XML element handler for the <eref> tag.
     * Required elements: URL
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes.
     */
    function _erefHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            if ($startTag) {
                $this->_output .= Horde::link($attrs['URL'], null, 'helplink', '_blank');
            } else {
                $this->_output .= '</a>';
            }
        }
    }

    /**
     * XML element handler for the <href> tag.
     * Required elements: url, app.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes.
     */
    function _hrefHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            if ($startTag) {
                global $registry;
                $url = Horde::url($registry->getParam('webroot', $attrs['app']) . '/' . $attrs['url']);
                $this->_output .= Horde::link($url, null, 'helplink', '_blank');
            } else {
                $this->_output .= '</a>';
            }
        }
    }

    /**
     * XML element handler for the <b> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _bHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= ($startTag) ? '<b>' : '</b>';
        }
    }

    /**
     * XML element handler for the <i> tag.
     *
     * @access private
     *
     * @param boolean $startTag      Boolean indicating whether this instance
     *                               is a start tag.
     * @param optional array $attrs  Additional element attributes (Not used).
     */
    function _iHandler($startTag, $attrs = array())
    {
        if ($this->_inHelp && $this->_inBlock) {
            $this->_output .= ($startTag) ? '<i>' : '</i>';
        }
    }

    /**
     * Includes the JavaScript necessary to create a new pop-up help
     * window.
     *
     * @access public
     */
    function javascript()
    {
        global $conf, $registry;

        if ($conf['user']['online_help'] && Help::_useJS()) {
            Horde::addScriptFile('open_help_win.js', 'horde');
        }
    }

    /**
     * Generates the HTML link that will pop up a help window for the
     * requested topic.
     *
     * @access public
     *
     * @param string $module  The name of the current Horde module.
     * @param string $topic   The help topic to be displayed.
     *
     * @return string  The HTML to create the help link.
     */
    function link($module, $topic)
    {
        global $conf, $registry;

        if (!$conf['user']['online_help']) {
            return ' ';
        }

        if (Help::_useJS()) {
            $html = Horde::link('', _("Help"), '', '', "open_help_win('$module', '$topic'); return false;");
        } else {
            $url = Horde::url($registry->getParam('webroot', 'horde') . '/services/help/', true);
            $url = Util::addParameter($url, 'module', $module);
            $url = Util::addParameter($url, 'topic', $topic);
            $html = Horde::link($url, '', '', '_hordehelpwin');
        }
        $html .= Horde::img('help.gif', _("Help"), 'width="16" height="16" align="middle"', $registry->getParam('graphics', 'horde')) . '</a>';

        return $html;
    }

    /**
     * Should we use javascript for the popup help windows?
     *
     * @access private
     *
     * @return boolean  True if javascript can be used for the help windows.
     */
    function _useJS()
    {
        static $use_js;

        if (!isset($use_js)) {
            require_once 'Horde/Browser.php';
            $browser = &Browser::singleton();
            $use_js = $browser->hasFeature('javascript');
        }

        return $use_js;
    }

    /**
     * Generates the URL that will pop up a help window for the list
     * of topics.
     *
     * @access public
     *
     * @param string $module  The name of the current Horde module.
     *
     * @return string  The HTML to create the help link.
     */
    function listLink($module)
    {
        global $conf;

        if (!$conf['user']['online_help']) {
            return false;
        } elseif (Help::_useJS()) {
            return "javascript:open_help_win('$module');";
        } else {
            global $registry;
            $url = Horde::url($registry->getParam('webroot', 'horde') . '/services/help/', true);
            $url = Util::addParameter($url, 'module', $module);
            $url = Util::addParameter($url, 'show', 'topics');
            return $url;
        }
    }

}

--- NEW FILE: Registry.php ---
<?php

require_once 'PEAR.php';
require_once 'Horde.php';

/** @constant HORDE_SESSION_NONE  Do not start a session. */
define('HORDE_SESSION_NONE', 1);

/** @constant HORDE_SESSION_READONLY Do not write changes to session. */
define('HORDE_SESSION_READONLY', 2);

/**
 * The Registry:: class provides a set of methods for communication
 * between Horde applications and keeping track of application
 * configuration information.
 *
 * $Horde: framework/Horde/Horde/Registry.php,v 1.207 2004/05/24 16:08:32 jwm Exp $
 *
 * Copyright 1999-2004 Chuck Hagenbuch <chuck at horde.org>
 * Copyright 1999-2004 Jon Parise <jon at horde.org>
 * Copyright 1999-2004 Anil Madhavapeddy <anil at recoil.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck at horde.org>
 * @author  Jon Parise <jon at horde.org>
 * @author  Anil Madhavapeddy <anil at recoil.org>
 * @version $Revision: 1.1 $
 * @since   Horde 1.3
 * @package Horde_Framework
 */
class Registry {

    /**
     * Hash storing all of the known services and callbacks.
     *
     * @var array $_apiCache
     */
    var $_apiCache = array();

    /**
     * Hash storing all known data types.
     *
     * @var array $_typeCache
     */
    var $_typeCache = array();

    /**
     * Hash storing all of the registered interfaces that applications
     * provide.
     *
     * @var array $_interfaces
     */
    var $_interfaces = array();

    /**
     * Hash storing information on each registry-aware application.
     *
     * @var array $applications
     */
    var $applications = array();

    /**
     * Stack of in-use applications.
     *
     * @var array $_appStack
     */
    var $_appStack = array();

    /**
     * Quick pointer to the current application.
     *
     * @var $_currentApp
     */
    var $_currentApp = null;

    /**
     * Cache of $prefs objects
     *
     * @var array $_prefsCache
     */
    var $_prefsCache = array();

    /**
     * Cache of application configurations.
     *
     * @var array $_confCache
     */
    var $_confCache = array();

    /**
     * Returns a reference to the global Registry object, only
     * creating it if it doesn't already exist.
     *
     * This method must be invoked as: $registry = &Registry::singleton()
     *
     * @param optional integer $session_flags  Any session flags.
     *
     * @return object Registry  The Horde Registry instance.
     */
    function &singleton($session_flags = 0)
    {
        static $registry;

        if (!isset($registry)) {
            $registry = new Registry($session_flags);
        }

        return $registry;
    }

    /**
     * Create a new registry instance. Should never be called except
     * by &Registry::singleton().
     *
     * @param optional integer $session_flags  Any session flags.
     *
     * @access private
     */
    function Registry($session_flags = 0)
    {
        /* Import and global Horde's configuration values. */
        $this->importConfig('horde');

        /* Start a session. */
        if ($session_flags & HORDE_SESSION_NONE) {
            /* Never start a session if the session flags include
               HORDE_SESSION_NONE. */
            $_SESSION = array();
        } else {
            Horde::setupSessionHandler();
            @session_start();
            if ($session_flags & HORDE_SESSION_READONLY) {
                /* Close the session immediately so no changes can be
                   made but values are still available. */
                @session_write_close();
            }
        }

        /* Read the registry configuration file. */
        require_once HORDE_BASE . '/config/registry.php';

        /* Initialize the localization routines and variables. */
        require_once 'Horde/NLS.php';
        NLS::setLang();
        NLS::setTextdomain('horde', HORDE_BASE . '/locale', NLS::getCharset());
        String::setDefaultCharset(NLS::getCharset());

        /* Stop system if Horde is inactive. */
        if ($this->applications['horde']['status'] == 'inactive') {
            Horde::fatal(_("This system is currently deactivated."), __FILE__, __LINE__);
        }

        /* Scan for all APIs provided by each app, and set other
         * common defaults like templates and graphics. */
        foreach (array_keys($this->applications) as $appName) {
            $app = &$this->applications[$appName];
            if (($app['status'] == 'heading') ||
                ($app['status'] == 'inactive') ||
                ($app['status'] == 'admin' && !Auth::isAdmin())) {
                continue;
            }
            if (isset($app['provides'])) {
                if (is_array($app['provides'])) {
                    foreach ($app['provides'] as $interface) {
                        $this->_interfaces[$interface] = $appName;
                    }
                } else {
                    $this->_interfaces[$app['provides']] = $appName;
                }
            }
            if (!isset($app['templates']) && isset($app['fileroot'])) {
                $app['templates'] = $app['fileroot'] . '/templates';
            }
            if (!isset($app['graphics']) && isset($app['webroot'])) {
                $app['graphics'] = $app['webroot'] . '/graphics';
            }
        }

        /* Create the global Perms object. */
        if (isset($GLOBALS['conf']['datatree']['driver'])) {
            $GLOBALS['perms'] = &Perms::singleton();
        }

        /* Attach javascript notification listener. */
        $notification = &Notification::singleton();
        $notification->attach('javascript');

        /* Register access key logger for translators. */
        if (@$GLOBALS['conf']['log_accesskeys']) {
            register_shutdown_function(create_function('', 'Horde::getAccessKey(null, null, true);'));
        }
    }

    /**
     * Return a list of the installed and registered applications.
     *
     * @since Horde 2.2
     *
     * @access public
     *
     * @param optional array   $filter  An array of the statuses that should be
     *                                  returned. Defaults to non-hidden.
     * @param optional boolean $assoc   Associative array with app names as keys.
     * @param optional integer $perm    The permission level to check for in the list.
     *                                  Defaults to PERMS_SHOW.
     *
     * @return array  List of apps registered with Horde. If no
     *                applications are defined returns an empty array.
     */
    function listApps($filter = null, $assoc = false, $perm = PERMS_SHOW)
    {
        $apps = array();
        if (is_null($filter)) {
            $filter = array('notoolbar', 'active');
        }

        foreach ($this->applications as $app => $params) {
            if (in_array($params['status'], $filter) &&
                (defined('AUTH_HANDLER') || Auth::isAdmin() || ($GLOBALS['perms']->exists($app) ?
                                                                $GLOBALS['perms']->hasPermission($app, Auth::getAuth(), $perm) :
                                                                Auth::getAuth()))) {
                $assoc ? $apps[$app] = $app : $apps[] = $app;
            }
        }

        return $apps;
    }

    /**
     * Returns all available registry APIs.
     *
     * @access public
     *
     * @return array  The API list.
     */
    function listAPIs()
    {
        $apis = array();

        foreach (array_keys($this->_interfaces) as $interface) {
            @list($api, ) = explode('/', $interface);
            $apis[] = $api;
        }

        return array_unique($apis);
    }

    /**
     * Fills the registry's API cache with the available services.
     *
     * @access private
     */
    function _fillAPICache()
    {
        if (!empty($this->_apiCache)) {
            return;
        }

        $status = array('active', 'notoolbar', 'hidden');
        if (Auth::isAdmin()) {
            $status[] = 'admin';
        }
        $apps = $this->listApps($status);
        foreach ($apps as $app) {
            if (!isset($this->_apiCache[$app])) {
                $_services = $_types = null;
                @include_once $this->getParam('fileroot', $app) . '/lib/api.php';
                if (!isset($_services)) {
                    $this->_apiCache[$app] = array();
                } else {
                    $this->_apiCache[$app] = $_services;
                }
                if (isset($_types)) {
                    $this->_typeCache = array_merge($_types, $this->_typeCache);
                }
            }
        }
    }

    /**
     * Returns all of the available registry methods, or alternately
     * only those for a specified API.
     *
     * @access public
     *
     * @param optional string $api  Defines the API for which the methods
     *                              shall be returned.
     *
     * @return array  The method list.
     */
    function listMethods($api = null)
    {
        $methods = array();

        $this->_fillAPICache();

        foreach (array_keys($this->applications) as $app) {
            if (isset($this->applications[$app]['provides'])) {
                $provides = $this->applications[$app]['provides'];
                if (!is_array($provides)) {
                    $provides = array($provides);
                }
            } else {
                $provides = array();
            }

            foreach ($provides as $method) {
                if (strstr($method, '/')) {
                    if (is_null($api) ||
                        (substr($method, 0, strlen($api)) == $api)) {
                        $methods[] = $method;
                    }
                } elseif (is_null($api) || ($method == $api)) {
                    if (isset($this->_apiCache[$app])) {
                        foreach (array_keys($this->_apiCache[$app]) as $service) {
                            $methods[] = $method . '/' . $service;
                        }
                    }
                }
            }
        }

        return array_unique($methods);
    }

    /**
     * Returns all of the available registry data types.
     *
     * @access public
     *
     * @return array  The data type list.
     */
    function listTypes()
    {
        $this->_fillAPICache();
        return $this->_typeCache;
    }

    /**
     * Returns a method's signature.
     *
     * @access public
     *
     * @param string $method  The full name of the method to check for.
     *
     * @return array  A two dimensional array. The first element contains an
     *                array with the parameter names, the second one the return
     *                type.
     */
    function getSignature($method)
    {
        if (!($app = $this->hasMethod($method))) {
            return;
        }

        $this->_fillAPICache();

        @list(, $function) = explode('/', $method);
        if (isset($this->_apiCache[$app][$function]['type']) &&
            isset($this->_apiCache[$app][$function]['args'])) {
            return array($this->_apiCache[$app][$function]['args'], $this->_apiCache[$app][$function]['type']);
        }
    }

    /**
     * Determine if a method has been registered with the registry.
     *
     * @access public
     *
     * @param string $method        The full name of the method to check for.
     * @param optional string $app  Only check this application.
     *
     * @return mixed  The application implementing $method if we have it,
     *                false if the method doesn't exist.
     */
    function hasMethod($method, $app = null)
    {
        if (is_null($app)) {
            @list($interface, $call) = explode('/', $method);
            if (!empty($this->_interfaces[$method])) {
                $app = $this->_interfaces[$method];
            } elseif (!empty($this->_interfaces[$interface])) {
                $app = $this->_interfaces[$interface];
            } else {
                return false;
            }
        } else {
            $call = $method;
        }

        $this->_fillAPICache();

        return !empty($this->_apiCache[$app][$call]) ? $app : false;
    }

    /**
     * Return the hook corresponding to the default package that
     * provides the functionality requested by the $method
     * parameter. $method is a string consisting of
     * "packagetype/methodname".
     *
     * @access public
     *
     * @param string $method        The method to call.
     * @param optional array $args  Arguments to the method.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function call($method, $args = array())
    {
        @list($interface, $call) = explode('/', $method);

        if (!empty($this->_interfaces[$method])) {
            $app = $this->_interfaces[$method];
        } elseif (!empty($this->_interfaces[$interface])) {
            $app = $this->_interfaces[$interface];
        } else {
            return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.');
        }

        return $this->callByPackage($app, $call, $args);
    }

    /**
     * Output the hook corresponding to the specific package named.
     *
     * @access public
     *
     * @param string $app           The application being called.
     * @param string $call          The method to call.
     * @param optional array $args  Arguments to the method.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function callByPackage($app, $call, $args = array())
    {
        /* Note: calling hasMethod() makes sure that we've cached
         * $app's services and included the API file, so we don't try
         * to do it again explicitly in this method. */
        if (!$this->hasMethod($call, $app)) {
            return PEAR::raiseError(sprintf(_("The method %s is not defined in the API for %s."), $call, $app));
        }

        /* Make sure that the function actually exists. */
        $function = '_' . $app . '_' . $call;
        if (!function_exists($function)) {
            return PEAR::raiseError('The function implementing ' . $call . ' (' . $function . ') is not defined in ' . $app . '\'s API.');
        }

        $checkPerms = isset($this->_apiCache[$app][$call]['checkperms']) ?
                      $this->_apiCache[$app][$call]['checkperms'] : true;

        /* Switch application contexts now, if necessary, before
         * including any files which might do it for us. Return an
         * error immediately if pushApp() fails. */
        $pushed = $this->pushApp($app, $checkPerms);
        if (is_a($pushed, 'PEAR_Error')) {
            return $pushed;
        }

        $res = call_user_func_array($function, $args);

        /* If we changed application context in the course of this call,
         * undo that change now. */
        if ($pushed === true) {
            $this->popApp();
        }

        return $res;
    }

    /**
     * Return the hook corresponding to the default package that
     * provides the functionality requested by the $method
     * parameter. $method is a string consisting of
     * "packagetype/methodname".
     *
     * @access public
     *
     * @param string $method         The method to link to.
     * @param optional array $args   Arguments to the method.
     * @param optional mixed $extra  Extra, non-standard arguments to the
     *                               method.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function link($method, $args = array(), $extra = '')
    {
        @list($interface, $call) = explode('/', $method);

        if (!empty($this->_interfaces[$method])) {
            $app = $this->_interfaces[$method];
        } elseif (!empty($this->_interfaces[$interface])) {
            $app = $this->_interfaces[$interface];
        } else {
            return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.');
        }

        return $this->linkByPackage($app, $call, $args, $extra);
    }

    /**
     * Output the hook corresponding to the specific package named.
     *
     * @access public
     *
     * @param string $app            The application being called.
     * @param string $call           The method to link to.
     * @param optional array $args   Arguments to the method.
     * @param optional mixed $extra  Extra, non-standard arguments to the
     *                               method.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function linkByPackage($app, $call, $args = array(), $extra = '')
    {
        /* Note: calling hasMethod makes sure that we've cached $app's
           services and included the API file, so we don't try to do it
           it again explicitly in this method. */
        if (!$this->hasMethod($call, $app)) {
            return PEAR::raiseError('The method "' . $call . '" is not defined in ' . $app . '\'s API.');
        }

        /* Make sure the link is defined. */
        if (empty($this->_apiCache[$app][$call]['link'])) {
            return PEAR::raiseError('The link ' . $call . ' is not defined in ' . $app . '\'s API.');
        }

        /* Initial link value. */
        $link = $this->_apiCache[$app][$call]['link'];

        /* Fill in html-encoded arguments. */
        foreach ($args as $key => $val) {
            $link = str_replace('%' . $key . '%', htmlentities($val), $link);
        }
        if (isset($this->applications[$app]['webroot'])) {
            $link = str_replace('%application%', $this->getParam('webroot', $app), $link);
        }

        /* Replace htmlencoded arguments that haven't been specified with
           an empty string (this is where the default would be substituted
           in a stricter registry implementation). */
        $link = preg_replace('|%.+%|U', '', $link);

        /* Fill in urlencoded arguments. */
        require_once 'Horde/String.php';
        foreach ($args as $key => $val) {
            $link = str_replace('|' . String::lower($key) . '|', urlencode($val), $link);
        }

        /* Append any extra, non-standard arguments. */
        if (is_array($extra)) {
            $extra_args = '';
            foreach ($extra as $key => $val) {
                $extra_args .- '&' . urlencode($key) . '=' . urlencode($val);
            }
        } else {
            $extra_args = $extra;
        }
        $link = str_replace('|extra|', $extra_args, $link);

        /* Replace html-encoded arguments that haven't been specified with
           an empty string (this is where the default would be substituted
           in a stricter registry implementation). */
        $link = preg_replace('|\|.+\||U', '', $link);

        return $link;
    }

    /**
     * Replace any %application% strings with the filesystem path to
     * the application.
     *
     * @access public
     *
     * @param string $path          The application string.
     * @param optional string $app  The application being called.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function applicationFilePath($path, $app = null)
    {
        if (is_null($app)) {
            $app = $this->_currentApp;
        }

        if (!isset($this->applications[$app])) {
            return PEAR::raiseError(sprintf(_("'%s' is not configured in the Horde Registry."), $app));
        }

        return str_replace('%application%', $this->applications[$app]['fileroot'], $path);
    }

    /**
     * Replace any %application% strings with the web path to the
     * application.
     *
     * @access public
     *
     * @param string $path          The application string.
     * @param optional string $app  The application being called.
     *
     * @return  TODO
     *          Returns PEAR_Error on error.
     */
    function applicationWebPath($path, $app = null)
    {
        if (!isset($app)) {
            $app = $this->_currentApp;
        }

        return str_replace('%application%', $this->applications[$app]['webroot'], $path);
    }

    /**
     * Set the current application, adding it to the top of the Horde
     * application stack. If this is the first application to be
     * pushed, retrieve session information as well.
     *
     * pushApp() also reads the application's configuration file and
     * sets up its global $conf hash.
     *
     * @access public
     *
     * @param string  $app         The name of the application to push.
     * @param boolean $checkPerms  Make sure that the current user has
     *                             permissions to the application being
     *                             loaded. Defaults to true. Should ONLY
     *                             be disabled by system scripts (cron jobs,
     *                             etc.) and scripts that handle login.
     *
     * @return boolean  Whether or not the _appStack was modified.
     *                  Return PEAR_Error on error.
     */
    function pushApp($app, $checkPerms = true)
    {
        if ($app == $this->_currentApp) {
            return false;
        }

        /* Bail out if application is not present or inactive. */
        if (!isset($this->applications[$app]) ||
            $this->applications[$app]['status'] == 'inactive' ||
            ($this->applications[$app]['status'] == 'admin' && !Auth::isAdmin())) {
            Horde::fatal($app . ' is not activated', __FILE__, __LINE__);
        }

        /* If permissions checking is requested, return an error if
         * the current user does not have read perms to the
         * application being loaded. We allow access:
         *
         *  - To all admins.
         *  - To all authenticated users if no permission is set on $app.
         *  - To anyone who is allowed by an explicit ACL on $app. */
        if ($checkPerms &&
            (!Auth::isAdmin() && ($GLOBALS['perms']->exists($app) ?
                                  !$GLOBALS['perms']->hasPermission($app, Auth::getAuth(), PERMS_READ) :
                                  !Auth::getAuth()))) {
            Horde::logMessage(sprintf('User %s does not have READ permission for %s', Auth::getAuth(), $app), __FILE__, __LINE__, PEAR_LOG_DEBUG);
            return PEAR::raiseError(sprintf(_("User %s is not authorised for %s."), Auth::getAuth(), $this->applications[$app]['name']), 'permission_denied');
        }

        /* Import this application's configuration values. */
        $success = $this->importConfig($app);
        if (is_a($success, 'PEAR_Error')) {
            return $success;
        }

        /* Load preferences after the configuration has been loaded to
         * make sure the prefs file has all the information it
         * needs. */
        $this->loadPrefs($app);

        /* Reset the language in case there is a different one
         * selected in the preferences. */
        $language = '';
        if (isset($this->_prefsCache[$app]) &&
            isset($this->_prefsCache[$app]->_prefs['language'])) {
            $language = $this->_prefsCache[$app]->getValue('language');
        }
        NLS::setLang($language);
        NLS::setTextdomain($app, $this->applications[$app]['fileroot'] . '/locale', NLS::getCharset());

        if (isset($this->_prefsCache[$app]) &&
            isset($this->_prefsCache[$app]->_prefs['theme'])) {
            $theme = $this->_prefsCache[$app]->getValue('theme');
            if (!empty($theme) &&
                (@include HORDE_BASE . '/config/themes/' . $theme . '.php') &&
                isset($theme_icons) &&
                in_array($app, $theme_icons)) {
                $this->applications[$app]['graphics'] .= '/themes/' . $theme;
            }
        }

        /* Once we're sure this is all successful, push the new app
         * onto the app stack. */
        array_push($this->_appStack, $app);
        $this->_currentApp = $app;

        return true;
    }

    /**
     * Remove the current app from the application stack, setting the
     * current app to whichever app was current before this one took
     * over.
     *
     * @access public
     *
     * @return string  The name of the application that was popped.
     */
    function popApp()
    {
        /* Pop the current application off of the stack. */
        $previous = array_pop($this->_appStack);

        /* Import the new active application's configuration values
           and set the gettext domain and the preferred language. */
        $this->_currentApp = count($this->_appStack) ?  end($this->_appStack) : null;
        if ($this->_currentApp) {
            $this->importConfig($this->_currentApp);
            $this->loadPrefs($this->_currentApp);
            $language = $GLOBALS['prefs']->getValue('language');
            if (isset($language)) {
                NLS::setLang($language);
            }
            NLS::setTextdomain($this->_currentApp, $this->applications[$this->_currentApp]['fileroot'] . '/locale', NLS::getCharset());
        }

        return $previous;
    }

    /**
     * Return the current application - the app at the top of the
     * application stack.
     *
     * @access public
     *
     * @return string The current application.
     */
    function getApp()
    {
        return $this->_currentApp;
    }

    /**
     * Reads the configuration values for the given application and
     * imports them into the global $conf variable.
     *
     * @access public
     *
     * @param string $app  The name of the application.
     *
     * @return boolean  True on success, PEAR_Error on error.
     */
    function importConfig($app)
    {
        /* Don't make config files global $registry themselves. */
        global $registry;

        /* Cache config values so that we don't re-read files on every
           popApp() call. */
        if (!isset($this->_confCache[$app])) {
            if (!isset($this->_confCache['horde'])) {
                $conf = array();
                $success = @include HORDE_BASE . '/config/conf.php';
                if (!$success) {
                    return PEAR::raiseError('Failed to import Horde configuration.');
                }

                /* Initial Horde-wide settings. */
                /* Set the error reporting level in accordance with the
                   config settings. */
                error_reporting($conf['debug_level']);

                /* Set the maximum execution time in accordance with the
                   config settings. */
                @set_time_limit($conf['max_exec_time']);

                /* Set the umask according to config settings. */
                if (isset($conf['umask'])) {
                    umask($conf['umask']);
                }
            } else {
                $conf = $this->_confCache['horde'];
            }

            if ($app !== 'horde') {
                $success = @include $this->applications[$app]['fileroot'] . '/config/conf.php';
                if (!$success) {
                    return PEAR::raiseError('Failed to import application configuration for ' . $app);
                }
            }

            $this->_confCache[$app] = &$conf;
        }

        $GLOBALS['conf'] = &$this->_confCache[$app];
        return true;
    }

    /**
     * Loads the preferences for the current user for the current
     * application and imports them into the global $prefs variable.
     *
     * @access public
     *
     * @param string $app  The name of the application.
     */
    function loadPrefs($app = null)
    {
        require_once 'Horde/Prefs.php';

        if (!isset($app)) {
            $app = $this->_currentApp;
        }

        /* If there is no logged in user, return an empty Prefs::
         * object with just default preferences. */
        if (!Auth::getAuth()) {
            $prefs = &Prefs::factory('none', $app, '', '', null, false);
            $prefs->retrieve();

            $GLOBALS['prefs'] = &$prefs;
            return;
        }

        /* Cache prefs objects so that we don't re-load them on every
         * popApp() call. */
        if (!isset($this->_prefsCache[$app])) {
            global $conf;
            $prefs = &Prefs::factory($conf['prefs']['driver'], $app,
                                     Auth::getAuth(), Auth::getCredential('password'));
            $prefs->retrieve();

            $this->_prefsCache[$app] = &$prefs;
        }

        $GLOBALS['prefs'] = &$this->_prefsCache[$app];
    }

    /**
     * Return the requested configuration parameter for the specified
     * application. If no application is specified, the value of
     * $this->_currentApp (the current application) is used. However,
     * if the parameter is not present for that application, the
     * Horde-wide value is used instead. If that is not present, we
     * return null.
     *
     * @access public
     *
     * @param string $parameter     The configuration value to retrieve.
     * @param optional string $app  The application to get the value for.
     *
     * @return string  The requested parameter, or null if it is not set.
     */
    function getParam($parameter, $app = null)
    {
        if (is_null($app)) {
            $app = $this->_currentApp;
        }

        if (isset($this->applications[$app][$parameter])) {
            $pval = $this->applications[$app][$parameter];
        } else {
            $pval = isset($this->applications['horde'][$parameter]) ? $this->applications['horde'][$parameter] : null;
        }

        if ($parameter == 'name') {
            return _($pval);
        } else {
            return $pval;
        }
    }

    /**
     * Query the initial page for an application - the webroot, if
     * there is no initial_page set, and the initial_page, if it is
     * set.
     *
     * @access public
     *
     * @param optional string $app  The name of the application.
     *
     * @return string  URL pointing to the inital page of the application.
     *                 Returns PEAR_Error on error.
     */
    function getInitialPage($app = null)
    {
        if (is_null($app)) {
            $app = $this->_currentApp;
        }

        if (!isset($this->applications[$app])) {
            return PEAR::raiseError(sprintf(_("'%s' is not configured in the Horde Registry."), $app));
        }
        return $this->applications[$app]['webroot'] . '/' . (isset($this->applications[$app]['initial_page']) ? $this->applications[$app]['initial_page'] : '');
    }

}





More information about the commits mailing list