<?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: horde/lib/Config.php,v 1.24 2003/04/10 03:24:02 chuck Exp $
 *
 * Copyright 2002-2003 Chuck Hagenbuch <chuck@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@horde.org>
 * @version $Revision: 1.24 $
 * @since   Horde 3.0
 * @package horde.config
 */
class Horde_Config {

    var $_app;
    var $_xmlConfigTree = null;
    var $_phpConfig;
    var $_currentConfig = array();

    function Horde_Config($app)
    {
        $this->_app = $app;
    }

    function readXMLConfig()
    {
        if (is_null($this->_xmlConfigTree)) {
            global $registry;
            $path = $registry->getParam('fileroot', $this->_app) . '/config';

            @include $path . '/conf.php';
            if (isset($conf)) {
                $this->_currentConfig = $conf;
            }
            $this->_xmlConfigTree = array();

            $doc = domxml_open_file($path . '/conf.xml');
            $root = $doc->root();
            if ($root->has_child_nodes()) {
                $this->_parseLevel($this->_xmlConfigTree, $root->child_nodes(), '');
            }
        }

        return $this->_xmlConfigTree;
    }

    function generatePHPConfig($formvars)
    {
        $this->readXMLConfig();
        $this->_phpConfig = "<?php\n";
        $this->_generatePHPConfig($this->_xmlConfigTree, '', $formvars);

        return $this->_phpConfig;
    }

    function _generatePHPConfig($section, $prefix, $formvars)
    {
        foreach ($section as $name => $configitem) {
            $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name;
            $configname = $prefixedname;
            if (isset($configitem['type'])) {
                $val = $formvars->getVarWasset($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':
                    $value = (int)$val;
                    break;

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

                case 'text':
                default:
                    $value = $val;
                    if ($value != 'true' && $value != 'false') {
                        $value = "'" . $this->_quote($value) . "'";
                    }
                    break;
                }

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

    function _parseLevel(&$conf, $children, $ctx)
    {
        foreach ($children as $node) {
            if ($node->type != XML_ELEMENT_NODE) {
                continue;
            }
            $name = $node->get_attribute('name');
            $desc = $node->get_attribute('desc');
            $required = !($node->get_attribute('required') == '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' => $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 'configenum':
                $values = $this->_getEnumValues($node);
                $conf[$name] = array('type' => 'enum',
                                     'required' => $required,
                                     'values' => $values,
                                     'desc' => $desc,
                                     'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)));
                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);
                $conf[$name] = array('type' => 'multienum',
                                     'required' => $required,
                                     'values' => $values,
                                     'desc' => $desc,
                                     'default' => $this->_makeKeyValuePairs($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)));
                break;

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

    function _makeKeyValuePairs($array)
    {
        $out = array();
        foreach ($array as $val) {
            $out[$val] = $val;
        }
        return $out;
    }

    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;
        }
    }

    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);
    }

    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;
    }

    function _handleSpecials($node)
    {
        switch ($node->get_attribute('name')) {
        case 'list-horde-apps':
            global $registry;
            return $this->_makeKeyValuePairs($registry->listApps(array('hidden', 'notoolbar', 'active')));
            break;
        }

        return array();
    }

    function _quote($string)
    {
        return str_replace("'", "\'", $string);
    }

}

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

    var $_xmlConfig;

    function ConfigForm(&$vars, $app)
    {
        parent::Horde_Form($vars);

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

    function _buildVariables($config, $prefix = '')
    {
        if (!is_array($config)) {
            return;
        }
        foreach ($config as $name => $configitem) {
            $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name;
            $varname = $prefixedname;
            if (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();
                }
                $v = &$this->addVariable($configitem['desc'], $varname, $type, $required, false, null, $var_params);
                if (array_key_exists('default', $configitem)) {
                    $v->setDefault($configitem['default']);
                }
            } else {
                $this->_buildVariables($configitem, $prefixedname);
            }
        }
    }

}
