<?php
/* vim: set ai tabstop=4: */
// $Date: 2002/04/09 01:29:38 $
// $Revision: 1.4 $
// +----------------------------------------------------------------------+
// | CONFIG MANAGER 0.1.2 - 09-Apr-2002 |
// +----------------------------------------------------------------------+
// | Author: Keyvan Minoukadeh - keyvan@k1m.com - http://www.k1m.com |
// +----------------------------------------------------------------------+
// | PHP class for managing plain text config files. |
// +----------------------------------------------------------------------+
// | This program is free software; you can redistribute it and/or |
// | modify it under the terms of the GNU General Public License |
// | as published by the Free Software Foundation; either version 2 |
// | of the License, or (at your option) any later version. |
// | |
// | This program is distributed in the hope that it will be useful, |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
// | GNU General Public License for more details. |
// +----------------------------------------------------------------------+
if (defined('CONFIGMAN_DIR')) {
require_once(CONFIGMAN_DIR.'class.config_base.php');
}
/**
* Config manager web editor class
*
* Use this class to modify your config files through a web form
* This is an extension to the base config class.
*
* @author Keyvan Minoukadeh <keyvan@k1m.com>
* @version 0.1.2
*/
class config_webedit extends config_base
{
/**
* show section names
*/
var $show_sections = true;
/**
* show string quotes
*/
var $show_quotes = true;
/**
* show static existing value
*/
var $show_existing = true;
/**
* show comments
*/
var $show_comments = true;
/**
* show types
*/
var $show_types = true;
/**
* allow type change ($this->show_types must be true)
*/
var $allow_type_change = false;
// If you've made a stylesheet for your page,
// enter name of each CSS class to use for each
// element.
/**
* CSS class names
*
* main table class - <table>
* type drop down class - <select>
* type text - <div>
* var name class - <div>
* value input class - <input type='text'...>
* error text class - <span>
* existing static value - <span>
* comment class - <div>
* section name class - <td>
* submit button class - <input type='submit'...>
* reset button class - <input type='reset'...>
*/
var $css = array('main' => 'configmanMain',
'type' => 'configmanType',
'type_text' => 'configmanTypeText',
'var' => 'configmanVar',
'value' => 'configmanValue',
'error' => 'configmanError',
'existing' => 'configmanExisting',
'comment' => 'configmanComment',
'section' => 'configmanSection',
'submit' => 'configmanSubmit',
'reset' => 'configmanReset');
/**
* Comment line separator
*/
var $comment_separator = "<br />";
/**
* Confirmation text to appear when the 'Revert back' button is clicked
*
* Note: this will be entered in a javascript function surrounded by single quotes,
* so make sure you escape properly
*/
var $reset_confirm_text = "This will revert all values back, any changes you\\'ve made will be lost. Are you sure you want to continue?";
/**
* row color 1
*/
var $row_color_1 = "#DAEBFC";
/**
* row color 2
*/
var $row_color_2 = "#C1DDF9";
/////////////
// methods
/////////////
/**
* Constructor
*
* @param string $config config file to use
*/
function config_webedit($config)
{
// run base constructor
$this->config_base($config);
}
/**
* Build form
*
* @param string $section Section to display (empty string: all sections)
* @param string $post_action URL to post to (<form action=''...>) default: current page
* @return string string containing the form
*/
function build_form($section='', $post_action=null)
{
$func_name = 'build_form';
clearstatcache();
if (is_null($post_action)) {
if (function_exists('version_compare') && version_compare(phpversion(), '4.1.0', '>=')) {
$post_action = $_SERVER['PHP_SELF'];
} else {
$post_action = $GLOBALS['PHP_SELF'];
}
}
if ($this->is_file_valid()) {
$config_size = filesize($this->config);
$config_mtime = filemtime($this->config);
$config = file($this->config);
} else {
$this->_error("Config file does not exist, is unreadable or above max size limit");
return false;
}
if (!empty($section)) {
if ($this->debug) $this->_debug("$func_name: Building web form for config section '$section'");
$this_section = false;
} else {
if ($this->debug) $this->_debug("$func_name: Building web form for all config sections");
$this_section = true;
}
$diff_color = true;
$form = array();
$cur_comment = array();
$form[] = "<form method=\"post\" name=\"CONFIGMAN_FORM\" action=\"{$post_action}\">";
$form[] = "<input type=\"hidden\" name=\"CONFIGMAN_INFO[action]\" value=\"modify\" />";
$form[] = "<input type=\"hidden\" name=\"CONFIGMAN_INFO[size]\" value=\"{$config_size}\" />";
$form[] = "<input type=\"hidden\" name=\"CONFIGMAN_INFO[mtime]\" value=\"{$config_mtime}\" />";
$form[] = "<table border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"{$this->css['main']}\">";
foreach ($config as $num => $line) {
$line = trim($line);
// match comment
if ($this_section && $this->show_comments && preg_match('!^'.preg_quote($this->comment).'(.*)$!', $line, $match)) {
if (trim($match[1]) != '') {
$cur_comment[] = trim($match[1]);
}
continue;
}
// match var
if ($this_section && preg_match('!^'.$this->regex_type.'?('.$this->regex_var.')(\.'.$this->regex_assoc.')?\s*'.
preg_quote($this->separator).'\s*(.*)$!i', $line, $match)) {
// decide which bg color to use
$diff_color = ($diff_color ? false : true);
if ($diff_color) {
$bg_color = $this->row_color_1;
} else {
$bg_color = $this->row_color_2;
}
// add comment row
if ($this->show_comments && count($cur_comment)) {
$form[] = "<tr bgcolor=\"{$bg_color}\">\n\t<td><div class=\"{$this->css['comment']}\">".implode($this->comment_separator, $cur_comment)."</div></td>\n</tr>";
// reset comments captured
$cur_comment = array();
}
// start row
$form[] = "<tr bgcolor=\"{$bg_color}\">\n\t<td>";
$form[] = "\t<table border=\"0\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">";
$form[] = "\t<tr>";
// add var name
$form[] = "\t\t<td width=\"25%\" valign=\"top\"><div class=\"{$this->css['var']}\">{$match[2]}{$match[3]}</div></td>";
// add drop down type
if (!empty($match[1])) {
if ($this->show_types && $this->allow_type_change) {
$form[] = "\t\t<td width=\"10%\" align=\"center\" valign=\"top\"><select name=\"CONFIGMAN[{$num}][type]\" class=\"{$this->css['type']}\">";
foreach ($this->type_prefix as $prefix => $type) {
if ($prefix == $match[1]) {
$form[] = "\t\t\t<option value=\"{$prefix}\" selected=\"selected\">{$type}</option>";
} else {
$form[] = "\t\t\t<option value=\"{$prefix}\">{$type}</option>";
}
}
$form[] = "\t\t</select></td>";
// add type name
} elseif ($this->show_types) {
$form[] = "\t\t<td width=\"10%\" align=\"center\" valign=\"top\"><div class=\"{$this->css['type_text']}\">".
"<input type=\"hidden\" name=\"CONFIGMAN[{$num}][type]\" value=\"{$match[1]}\" />".
$this->type_prefix[$match[1]]."</div></td>";
// empty cell
} else {
$form[] = "\t\t<td width=\"10%\">".
"<input type=\"hidden\" name=\"CONFIGMAN[{$num}][type]\" value=\"{$match[1]}\" />".
" </td>";
}
} else {
$form[] = "\t\t<td width=\"10%\"> </td>";
}
// valid quote usage?
if (preg_match('!^(?'.'>(["\'])?)(.*)(?(1)\1)$!', $match[4], $value_match)) {
$valid_value = true;
$display_value = $value_match[2];
} else {
$valid_value = false;
$display_value = $match[4];
}
// show quotes?
if ($this->show_quotes && $valid_value) {
$cur_quote = htmlspecialchars($value_match[1]);
} else {
$cur_quote = '';
}
// show static existing values
if ($this->show_existing) {
$cur_existing = htmlspecialchars($match[4]);
} else {
$cur_existing = '';
}
// add value input field
$form[] = "\t\t<td width=\"65%\" valign=\"top\"><input type=\"text\" name=\"CONFIGMAN[{$num}][value]\" value=\"{$cur_quote}".
htmlspecialchars($display_value)."{$cur_quote}\" class=\"{$this->css['value']}\" /> ".
"<span class=\"{$this->css['existing']}\"> {$cur_existing}</span>";
if (!$valid_value) {
$form[] = "\t\t<br /><span class=\"{$this->css['error']}\">Invalid value (opening AND closing quotes required)</span>";
}
$form[] = "\t\t</td>";
$form[] = "\t</tr></table>\n\t</td>\n</tr>";
continue;
}
// match section
if (preg_match('!^\[('.$this->regex_section.')\]!', $line, $match)) {
$match[1] = trim($match[1]);
if (empty($section) || ($match[1] === $section)) {
$this_section = true;
if ($this->show_sections) {
$form[] = "<tr>\n\t<td> </td>\n</tr>";
$form[] = "<tr>\n\t<td class=\"{$this->css['section']}\">{$match[1]}</td>\n</tr>";
}
} else {
$this_section = false;
}
// reset any comments captured
$cur_comment = array();
continue;
}
}
// submit and reset buttons
$form[] = "<tr>\n\t<td> <br />".
"<input type=\"submit\" name=\"submit\" value=\"Update!\" class=\"{$this->css['submit']}\" />".
" ".
"<input type=\"reset\" name=\"reset\" value=\"Revert back\" class=\"{$this->css['reset']}\" ".
"onclick=\"return confirm('{$this->reset_confirm_text}')\" />".
"</td>\n</tr>";
$form[] = "</table>\n</form>";
return implode("\n", $form);
}
/**
* Update config
*
* @param array $configman Result of form post ($CONFIGMAN array)
* @param array $configman_info Result of form post ($CONFIGMAN_INFO array)
* @return bool true on success, false otherwise
*/
function update($configman, $configman_info)
{
$func_name = 'update';
if (empty($configman_info['action']) || empty($configman_info['size']) || empty($configman_info['mtime'])) {
if ($this->debug) $this->_debug("$func_name: Required info not included in array");
return false;
}
// get rid of magic crap
$this->strip_magic_slashes($configman);
$this->strip_magic_slashes($configman_info);
if ($this->is_file_valid() && is_writable($this->config)) {
// check action
if ($configman_info['action'] !== 'modify') {
if ($this->debug) $this->_debug("$func_name: Incorrect action");
return false;
}
// check file size
if ((int)$configman_info['size'] !== filesize($this->config)) {
if ($this->debug) $this->_debug("$func_name: Config file sizes do not match");
return false;
}
// check modification time
if ((int)$configman_info['mtime'] !== filemtime($this->config)) {
if ($this->debug) $this->_debug("$func_name: Config file has already been modified (modification time mismatch)");
return false;
}
$config = file($this->config);
} else {
$this->_error("Config file does not exist or is unreadable or unwritable");
return false;
}
$new = array();
foreach ($config as $num => $line) {
$line = rtrim($line);
if (!isset($configman[$num])) {
$new[] = $line;
continue;
}
if (preg_match('!^(\s*)'.$this->regex_type.'?('.$this->regex_var.')(\.'.$this->regex_assoc.')?(\s*'.
preg_quote($this->separator).'\s*)(.*)$!i', $line, $match)) {
// set new type
if (isset($configman[$num]['type'])) {
$new_type = $configman[$num]['type'];
} else {
$new_type = $match[2];
}
// set new value
$new_val = $configman[$num]['value'];
$new[] = $match[1].$new_type.$match[3].$match[4].$match[5].$new_val;
continue;
}
$new[] = $line;
}
$fp = @fopen($this->config, "w");
if (!$fp) {
if ($this->debug) $this->_debug("$func_name: Could not create/access file");
return false;
} else {
// exclusive lock
flock($fp, 2);
$result = @fwrite($fp, implode("\n", $new));
// release lock
flock($fp, 3);
fclose($fp);
if (!$result) {
if ($this->debug) $this->_debug("$func_name: Could not write to file");
return false;
} else {
if ($this->debug) $this->_debug("$func_name: Config file written");
// touch file with modification time of main config file (used for comparison)
touch($this->config);
return true;
}
}
// should not reach here
return false;
}
}
?> |