Configuration plugin

by Chris Smith

experimental

This plugin allows wiki administrators to easily alter its configuration settings online from the comfort of their favourite webbrowser.

Usage Notes

Settings are shown with different backgrounds to highlight their source. A blue background is used to show default values (conf/dokuwiki.php). A white background to show local changes (conf/local.php) and a light red background to show protected settings (conf/local.protected.php).

On saving changed settings this plugin will copy the current local settings file (conf/local.php) to conf/local.php.bak and save the updated settings to conf/local.php. It will never make any changes to the default settings stored in conf/dokuwiki.php. However, any settings found in conf/local.php will override the default settings as explained in the manual.

The plugin adds the following lines to the top of conf/local.php when it updates it …

/*
 * Dokuwiki's Main Configuration File - Local Settings 
 * Auto-generated by config plugin 
 * Run for user: <username>
 * Date: <current date/time, rfc 2822 format (day, dd MMM YYYY hh:mm:ss TZ)>
 */

Protected Settings

You can protect certain settings by placing them in conf/local.protected.php. This plugin adds the following line to the bottom of conf/local.php

@include(DOKU_CONF.'local.protected.php');

— thereby ensuring the protected settings are included and will override any previously set values from conf/dokuwiki.php and conf/local.php. Any settings found in this file will be displayed by the plugin surrounded in light-red to indicated their protected status. Editing of protected values is disabled.

Installation

Plugin sources: zip format (15k), tar.gz format (12k), darcs repository

The first two links may be used with the plugin manager and the third with the darcs plugin.

To install the plugin manually, download the package from either of the first two links to your plugin folder, lib/plugins and extract its contents. That will create a new plugin folder, lib/plugins/config, and install the plugin.

The folder will contain:

admin.php                     the main plugin script
style.css                     styles to assist in display of settings data

settings/                     folder containing scripts & data to manipulate dokuwiki settings
settings/config.class.php     contains configuration class and generic setting classes
settings/extra.class.php      contains additional setting classes specific to some dokuwiki settings
settings/config.metadata.php  metadata describing dokuwiki's configuration settings

lang/                         localisation root folder
lang/xx/                      folder for language xx, at least en (english) will be present
lang/xx/intro.txt
lang/xx/lang.php              localised language strings

The plugin is now installed.

Details

The plugin has been structured to work to separate knowledge of Dokuwiki settings from the scripts which handle the settings.

  • admin.php provides the interface to dokuwiki's admin menu, the user and instantiates the configuration object.
  • settings/config.class.php contains the configuration class and several generic setting classes. It reads the metadata which describes the settings (settings/config.metadata.php) and loads dokuwiki's settings into objects capable of understanding each setting.
  • settings/extra.class.php provides some extra setting classes where a particular setting can not be handled by one of the generic classes.

Of the remaining files,

  • style.css has styles to assist in displaying settings, particular to indicated errors;
  • intro.txt & lang.php contain locale dependent strings displayed to the user. These two can be translated these into your local language. The translations should be stored in a sub-folder of lang with the same code you have chosen for your Dokuwiki installation.

admin.php

admin.php

<?php
/**
 * Configuration Manager admin plugin
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Christopher Smith <chris@jalakai.co.uk>
 */
 
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'admin.php');
 
define('CM_KEYMARKER','____');            // used for settings with multiple dimensions of array indices
 
define('PLUGIN_SELF',dirname(__FILE__).'/');
define('PLUGIN_METADATA',PLUGIN_SELF.'settings/config.metadata.php');
 
require_once(PLUGIN_SELF.'settings/config.class.php');  // main configuration class and generic settings classes
require_once(PLUGIN_SELF.'settings/extra.class.php');   // settings classes specific to these settings
 
/**
 * All DokuWiki plugins to extend the admin function
 * need to inherit from this class
 */
class admin_plugin_config extends DokuWiki_Admin_Plugin {
 
    var $_file = PLUGIN_METADATA;
    var $_config = null;
    var $_input = null;
    var $_changed = false;          // set to true if configuration has altered
    var $_error = false;
    var $_session_started = false;
 
    /**
     * return some info
     */
    function getInfo(){
 
      return array(
        'author' => 'Christopher Smith',
        'email'  => 'chris@jalakai.co.uk',
        'date'   => '2005-11-11',
        'name'   => 'Configuration Manager',
        'desc'   => "Manage Dokuwiki's Configuration Settings",
        'url'    => 'http://wiki.splitbrain.org/plugin:config',
      );
    }
 
    function getMenuSort() { return 100; }
 
    /**
     * handle user request
     */
    function handle() {
 
      if (!$this->_restore_session()) return $this->_close_session();
      if (!isset($_REQUEST['save']) || ($_REQUEST['save'] != 1)) return $this->_close_session();
 
      if (is_null($this->_config)) { $this->_config = new configuration($this->_file); }
 
      // don't go any further if the configuration is locked
      if ($this->_config->_locked) return $this->_close_session();
 
      $this->_input = $_REQUEST['config'];
 
      while (list($key) = each($this->_config->setting)) {
        $input = isset($this->_input[$key]) ? $this->_input[$key] : NULL;
        if ($this->_config->setting[$key]->update($input)) {
          $this->_changed = true;
        }
        if ($this->_config->setting[$key]->error()) $this->_error = true;
      }
 
      if ($this->_changed  && !$this->_error) {
        $this->_config->save_settings($this->getPluginName());
 
        // save state & force a page reload to get the new settings to take effect
        $_SESSION['PLUGIN_CONFIG'] = array('state' => 'updated', 'time' => time());
        $this->_close_session();
        header("Location: ".wl($ID)."?do=admin&page=config");
        exit();
      }
 
      $this->_close_session();
    }
 
    /**
     * output appropriate html
     */
    function html() { 
      global $lang;  
 
      if (is_null($this->_config)) { $this->_config = new configuration($this->_file); }
 
      print $this->locale_xhtml('intro');
 
      ptln('<div id="configmanager">');
 
      if ($this->_config->locked)
        ptln('<p class="info">'.$this->getLang('locked').'</p>');
      elseif ($this->_error) 
        ptln('<p class="error">'.$this->getLang('error').'</p>');
      elseif ($this->_changed)
        ptln('<p class="ok">'.$this->getLang('updated').'</p>');
 
      ptln('<form action="'.wl($id).'" method="post">');
      ptln('  <table class="inline">');
 
      foreach($this->_config->setting as $setting) {
 
        list($label,$input) = $setting->html($this, $this->_error);
 
        $class = $setting->is_default() ? ' class="default"' : ($setting->is_protected() ? ' class="protected"' : '');        
        $error = $setting->error() ? ' class="error"' : '';
 
        ptln('    <tr'.$class.'>');
        ptln('      <td>'.$label.'</td>');
        ptln('      <td'.$error.'>'.$input.'</td>');
        ptln('    </tr>');
      }
 
      ptln('  </table>');
 
      ptln('<p>');
      ptln('  <input type="hidden" name="do"     value="admin" />');
      ptln('  <input type="hidden" name="page"   value="config" />');
 
      if (!$this->_config->locked) {
        ptln('  <input type="hidden" name="save"   value="1" />');
        ptln('  <input type="submit" name="submit" value="'.$lang['btn_save'].'" />');
        ptln('  <input type="reset" value="'.$lang['btn_reset'].'" />');
      }
 
      ptln('</p>');
 
      ptln('</form>');
      ptln('</div>');
    }
 
    /**
     * @return boolean   true - proceed with handle, false - don't proceed
     */
    function _restore_session() {
 
      // dokuwiki closes the session before act_dispatch. $_SESSION variables are all set, 
      // however they can't be changed without starting the session again      
      if (!headers_sent()) {
        session_start();
        $this->_session_started = true;
      }      
 
      if (!isset($_SESSION['PLUGIN_CONFIG'])) return true;
 
      $session = $_SESSION['PLUGIN_CONFIG'];
      unset($_SESSION['PLUGIN_CONFIG']);
 
      // still valid?
      if (time() - $session['time'] > 120) return true;
 
      switch ($session['state']) {
        case 'updated' :
          $this->_changed = true;
          return false;
      }
 
      return true;
    }
 
    function _close_session() {
      if ($this->_session_started) session_write_close();
    }
 
}

settings/config.class.php

settings/config.class.php

<?php
/*
 *  Configuration Class and generic setting classes
 *
 *  @author  Chris Smith <chris@jalakai.co.uk>
 */
 
if (!class_exists('configuration')) {
  class configuration {
 
    var $_name = 'conf';           // name of the config variable found in the files (overridden by $config['varname'])
    var $_format = 'php';          // format of the config file, supported formats - php (overridden by $config['format'])
    var $_heading = '';            // heading string written at top of config file - don't include comment indicators
    var $_loaded = false;          // set to true after configuration files are loaded     
    var $_metadata = array();      // holds metadata describing the settings
    var $setting = array();        // array of setting objects
    var $locked = false;           // configuration is considered locked if it can't be updated
 
    // filenames, these will be eval()'d prior to use so maintain any constants in output
    var $_default_file  = '';
    var $_local_file = '';
    var $_protected_file = '';
 
    /**
     *  constructor
     */
    function configuration($datafile) {
 
        if (!@file_exists($datafile)) {
          msg('No configuration metadata found at - '.htmlspecialchars($datafile),-1);
          return;
        }
        include($datafile);
 
        if (isset($config['varname'])) $this->_name = $config['varname'];
        if (isset($config['format'])) $this->_format = $config['format'];
        if (isset($config['heading'])) $this->_heading = $config['heading'];
 
        if (isset($file['default'])) $this->_default_file = $file['default'];
        if (isset($file['local'])) $this->_local_file = $file['local'];
        if (isset($file['protected'])) $this->_protected_file = $file['protected'];
 
        $this->locked = $this->_is_locked();
 
        $this->_metadata = $meta;
 
        $this->retrieve_settings();
    }
 
    function retrieve_settings() {
 
        if (!$this->_loaded) {
          $default = $this->_read_config($this->_default_file);
          $local = $this->_read_config($this->_local_file);
          $protected = $this->_read_config($this->_protected_file);
 
          $keys = array_merge(array_keys($this->_metadata),array_keys($default), array_keys($local), array_keys($protected));
          $keys = array_unique($keys);
 
          foreach ($keys as $key) {
            if (isset($this->_metadata[$key])) {
              $class = $this->_metadata[$key][0];
              $class = ($class && class_exists('setting_'.$class)) ? 'setting_'.$class : 'setting';
 
              $param = $this->_metadata[$key];
              array_shift($param);
            } else {
              $class = 'setting';
              $param = NULL;
            }
 
            $this->setting[$key] = new $class($key,$param);    
            $this->setting[$key]->initialize($default[$key],$local[$key],$protected[$key]);    
          }
 
          $this->_loaded = true;
        }        
    }
 
    function save_settings($id, $header='', $backup=true) {
 
      if ($this->locked) return false;
 
      $file = eval('return '.$this->_local_file.';');
 
      // backup current file (remove any existing backup)
      if (@file_exists($file) && $backup) {
        if (@file_exists($file.'.bak')) @unlink($file.'.bak');
        if (!@rename($file, $file.'.bak')) return false;
      }
 
      if (!$fh = @fopen($file, 'wb')) {
        @rename($file.'.bak', $file);     // problem opening, restore the backup
        return false;
      }      
 
      if (empty($header)) $header = $this->_heading;
 
      $out = '<'.'?php'."\n".
             "/*\n".
             " * ".$header." \n".
             " * Auto-generated by ".$id." plugin \n".
             " * Run for user: ".$_SERVER['REMOTE_USER']."\n".
             " * Date: ".date('r')."\n".
             " */\n\n";
 
      foreach ($this->setting as $setting) {
        $out .= $setting->out($this->_name, $this->_format);
      }
 
      if ($this->_protected_file) {
        $out .= "\n@include(".$this->_protected_file.");\n";        
      }
      $out .= "\n// end auto-generated content\n";
 
      @fwrite($fh, $out);
      fclose($fh);
      return true;
    }
 
    /**
     * return an array of config settings
     */
    function _read_config($file) {
 
      if (!$file) return array();
 
      $config = array();
      $file = eval('return '.$file.';');
 
      if ($this->_format == 'php') {
 
        $contents = php_strip_whitespace($file);
        $pattern = '/\$'.$this->_name.'\[[\'"]([^=]+)[\'"]\]=(.*?);/';
 
        preg_match_all($pattern,$contents,$matches=array(),PREG_SET_ORDER);
 
        for ($i=0; $i<count($matches); $i++) {
 
          // correct issues with the incoming data
          // FIXME ... for now merge multi-dimensional array indices using _
          $key = preg_replace('/.\]\[./',CM_KEYMARKER,$matches[$i][1]);
 
          // remove quotes from quoted strings & unescape escaped data (FIXME)
          $value = preg_replace('/^(\'|")(.*)(?<!\\\\)\1$/','$2',$matches[$i][2]);
          $value = strtr($value, array('\\\\'=>'\\','\\\''=>'\'','\\"'=>'"'));
 
          $config[$key] = $value;
        }
      }
 
      return $config;
    }
 
    // configuration is considered locked if there is no local settings filename
    // or the directory its in is not writable or the file exists and is not writable
    function _is_locked() {
      if (!$this->_local_file) return true;
 
      $local = eval('return '.$this->_local_file.';');
 
      if (!is_writable(dirname($local))) return true;
      if (file_exists($local) && !is_writable($local)) return true;
 
      return false;
    }
 
    /**
     *  not used ... conf's contents are an array!
     *  reduce any multidimensional settings to one dimension using CM_KEYMARKER
     */
    function _flatten($conf,$prefix='') {
 
        $out = array();
 
        foreach($conf as $key => $value) {
          if (!is_array($value)) {
            $out[$prefix.$key] = $value;
            continue;
          }    
 
          $tmp = $this->_flatten($value,$prefix.$key.CM_KEYMARKER);
          $out = array_merge($out,$tmp);
        }
 
        return $out;
    }    
  }
}
 
if (!class_exists('setting')) {
  class setting {
 
    var $_key = '';
    var $_default = NULL;
    var $_local = NULL;
    var $_protected = NULL;
 
    var $_pattern = '';
    var $_error = false;            // only used by those classes which error check
    var $_input = NULL;             // only used by those classes which error check
 
    function setting($key, $params=NULL) {
        $this->_key = $key;
 
        if (is_array($params)) {
          foreach($params as $property => $value) {
            $this->$property = $value;
          }
        }
    }
 
    /**
     *  recieves current values for the setting $key
     */
    function initialize($default, $local, $protected) {
        if (isset($default)) $this->_default = $default;
        if (isset($local)) $this->_local = $local;
        if (isset($protected)) $this->_protected = $protected;
    }
 
    /*
     *  update setting with user provided value $input
     *  if value fails error check, save it
     *
     *  FIXME value must be cleaned and validated
     */
    function update($input) {
        if (is_null($input)) return false;
        if ($this->is_protected()) return false;
 
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        if ($this->_pattern && !preg_match($this->_pattern,$input)) {
          $this->_error = true;
          $this->_input = $input;
          return false;
        }
 
        $this->_local = $input;
        return true;
    }
 
    /**
     *  @return   array(string $label_html, string $input_html)
     */
    function html(&$plugin, $echo=false) {
 
        $value = '';
        $disable = '';
 
        if ($this->is_protected()) {
          $value = $this->_protected;
          $disable = 'disabled="disabled"';
        } else {
          if ($echo && $this->_error) {
            $value = $this->_input;
          } else {
            $value = is_null($this->_local) ? $this->_default : $this->_local;
          }
        }    
 
        $key = htmlspecialchars($this->_key);
        $value = htmlspecialchars($value);    
 
        $label = '<label for="config_'.$key.'">'.$this->prompt($plugin).'</label>';
        $input = '<input id="config_'.$key.'" name="config['.$key.']" type="text" class="text" value="'.$value.'" '.$disable.'/>';
        return array($label,$input);        
    }
 
    /**
     *  generate string to save setting value to file according to $fmt
     */
    function out($var, $fmt='php') {
 
      if ($this->is_protected()) return '';
      if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
 
      if ($fmt=='php') {
        // translation string needs to be improved FIXME        
        $tr = array("\n"=>'\n', "\r"=>'\r', "\t"=>'\t', "\\" => '\\\\', "'" => '\\\'');
 
        $out =  '$'.$var."['".$this->_out_key()."'] = '".strtr($this->_local, $tr)."';\n";
        return $out;
      }
    }
 
    function prompt(&$plugin) {
        $prompt = $plugin->getLang($this->_key);
        if (!$prompt) $prompt = str_replace(array('____','_'),' ',$this->_key);
        return htmlspecialchars($prompt);
    }
 
    function is_protected() { return !is_null($this->_protected); }
    function is_default() { return !$this->is_protected() && is_null($this->_local); }
    function error() { return $this->_error; }
 
    function _out_key() { return str_replace(CM_KEYMARKER,"']['",$this->_key); }        
  }
}
 
if (!class_exists('setting_password')) {
  class setting_password extends setting {
 
    function update($input) {
        if ($this->is_protected()) return false;
        if (!$input) return false;
 
        if ($this->_pattern && !preg_match($this->_pattern,$input)) {
          $this->_error = true;
          $this->_input = $input;
          return false;
        }
 
        $this->_local = $input;
        return true;
    }
 
    function html(&$plugin, $echo=false) {
 
        $value = '';
        $disable = $this->is_protected() ? 'disabled="disabled"' : '';
 
        $key = htmlspecialchars($this->_key);
 
        $label = '<label for="config_'.$key.'">'.$this->prompt($plugin).'</label>';
        $input = '<input id="config_'.$key.'" name="config['.$key.']" type="password" class="text" value="" '.$disable.'/>';
        return array($label,$input);        
    }
  }
}
 
if (!class_exists('setting_email')) {
  class setting_email extends setting {
    var $_pattern = '#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i';
  }
}
 
if (!class_exists('setting_numeric')) {
  class setting_numeric extends setting {
    var $_pattern = '/^[-+\/*0-9 ]*$/';
 
    function out($var, $fmt='php') {
 
      if ($this->is_protected()) return '';
      if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
 
      if ($fmt=='php') {
        $out .=  '$'.$var."['".$this->_out_key()."'] = ".$this->_local.";\n";
        return $out;
      }
    }
  }
}
 
if (!class_exists('setting_onoff')) {
  class setting_onoff extends setting_numeric {
 
    function html(&$plugin) {
        $value = '';
        $disable = '';
 
        if ($this->is_protected()) {
          $value = $this->_protected;
          $disable = ' disabled="disabled"';
        } else {
          $value = is_null($this->_local) ? $this->_default : $this->_local;
        }    
 
        $key = htmlspecialchars($this->_key);
        $checked = ($value) ? ' checked="checked"' : '';    
 
        $label = '<label for="config_'.$key.'">'.$this->prompt($plugin).'</label>';
        $input = '<div class="input"><input id="config_'.$key.'" name="config['.$key.']" type="checkbox" class="checkbox" value="1"'.$checked.$disable.'/></div>';
        return array($label,$input);        
    }
 
    function update($input) {
        if ($this->is_protected()) return false;
 
        $input = ($input) ? 1 : 0;        
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        $this->_local = $input;
        return true;        
    }
  }
}
 
if (!class_exists('setting_mulitchoice')) {
  class setting_multichoice extends setting {
    var $_choices = array();
 
    function html(&$plugin) {
        $value = '';
        $disable = '';
        $nochoice = '';
 
        if ($this->is_protected()) {
          $value = $this->_protected;
          $disable = ' disabled="disabled"';
        } else {
          $value = is_null($this->_local) ? $this->_default : $this->_local;          
        }
 
        // ensure current value is included
        if (!in_array($value, $this->_choices)) {
            $this->_choices[] = $value;
        }
        // disable if no other choices
        if (!$this->is_protected() && count($this->_choices) <= 1) {
          $disable = ' disabled="disabled"';
          $nochoice = $plugin->getLang('nochoice');
        }    
 
        $key = htmlspecialchars($this->_key);
 
        $label = '<label for="config_'.$key.'">'.$this->prompt($plugin).'</label>';
 
        $input = "<div class=\"input\">\n";
        $input .= '<select id="config_'.$key.'" name="config['.$key.']"'.$disable.'>'."\n";
        foreach ($this->_choices as $choice) {
            $selected = ($value == $choice) ? ' selected="selected"' : '';
            $option = $plugin->getLang($this->_key.'_o_'.$choice);
            if (!$option) $option = $choice;
 
            $choice = htmlspecialchars($choice);
            $option = htmlspecialchars($option);
            $input .= '  <option value="'.$choice.'"'.$selected.' >'.$option.'</option>'."\n";
        }
        $input .= "</select> $nochoice \n";
        $input .= "</div>\n";
 
        return array($label,$input);        
    }
 
    function update($input) {
        if (is_null($input)) return false;
        if ($this->is_protected()) return false;
 
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        if (!in_array($input, $this->_choices)) return false;
 
        $this->_local = $input;
        return true;
    }
  }
}
 
 
if (!class_exists('setting_dirchoice')) {
  class setting_dirchoice extends setting_multichoice {
 
    var $_dir = '';
 
    function initialize($default,$local,$protected) {
 
      // populate $this->_choices with a list of available templates
      $list = array();
 
      if ($dh = @opendir($this->_dir)) {        
        while (false !== ($entry = readdir($dh))) {
          if ($entry == '.' || $entry == '..') continue;
 
          $file = (is_link($this->_dir.$entry)) ? readlink($this->_dir.$entry) : $entry;        
          if (is_dir($this->_dir.$file)) $list[] = $entry;             
        }
        closedir($dh);
      }
      sort($list);
      $this->_choices = $list;
 
      parent::initialize($default,$local,$protected);
    }
  }    
}
 
 
/**
 *  Provide php_strip_whitespace (php5 function) functionality 
 *
 *  Currently uses token_get_all(), this requires php 4.2+
 *
 *  @author   Chris Smith <chris@jalakai.co.uk>
 */
if (!function_exists('php_strip_whitespace'))  {
 
  if (function_exists('token_get_all')) {
 
    if (!defined('T_ML_COMMENT')) {
      define('T_ML_COMMENT', T_COMMENT);
    } else {
      define('T_DOC_COMMENT', T_ML_COMMENT);
    }
 
    /**
     * modified from original
     * source Google Groups, php.general, by David Otton
     */
    function php_strip_whitespace($file) {
        if (!@is_readable($file)) return '';
 
        $in = join('',@file($file));        
        $out = '';
 
        $tokens = token_get_all($in);
 
        foreach ($tokens as $token) {
          if (is_string ($token)) {
            $out .= $token;
          } else {
            list ($id, $text) = $token;
            switch ($id) {
              case T_COMMENT : // fall thru
              case T_ML_COMMENT : // fall thru
              case T_DOC_COMMENT : // fall thru 
              case T_WHITESPACE : 
                break;
              default : $out .= $text; break;
            }
          }
        }    
        return ($out);
    }
 
  } else {
 
    function is_whitespace($c) { return (strpos("\t\n\r ",$c) !== false); }
    function is_quote($c) { return (strpos("\"'",$c) !== false); }
    function is_escaped($s,$i) {
        $idx = $i-1;
        while(($idx>=0) && ($s{$idx} == '\\')) $idx--;
        return (($i - $idx + 1) % 2);
    }
 
    function is_commentopen($str, $i) {
        if ($str{$i} == '#') return "\n";
        if ($str{$i} == '/') {
          if ($str{$i+1} == '/') return "\n";
          if ($str{$i+1} == '*') return "*/";
        }
 
        return false;
    }
 
    function php_strip_whitespace($file) {
 
        if (!@is_readable($file)) return '';
 
        $contents = join('',@file($file));        
        $out = '';
 
        $state = 0;
        for ($i=0; $i<strlen($contents); $i++) {
          if (!$state && is_whitespace($contents{$i})) continue;
 
          if (!$state && ($c_close = is_commentopen($contents, $i))) {
            $c_open_len = ($contents{$i} == '/') ? 2 : 1;
            $i = strpos($contents, $c_close, $i+$c_open_len)+strlen($c_close)-1;
            continue;
          }          
 
          $out .= $contents{$i};
          if (is_quote($contents{$i})) {
              if (($state == $contents{$i}) && !is_escaped($contents, $i)) { $state = 0; continue; }
            if (!$state) {$state = $contents{$i}; continue; }
          }
        }
 
        return $out;    
    }
  } 
}

settings/extra.class.php

settings/extra.class.php

<?php
/**
 * additional setting classes specific to these settings
 *
 * @author    Chris Smith <chris@jalakai.co.uk>
 */
 
if (!class_exists('setting_sepchar')) {
  class setting_sepchar extends setting_multichoice {
 
    function setting_sepchar($key,$param=NULL) {
        $str = '_-.0123456789abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        for ($i=0;$i<strlen($str);$i++) $this->_choices[] = $str{$i};        
 
        // call foundation class constructor
        $this->setting($key,$param);
    }
  }
}
 
if (!class_exists('setting_savedir')) {
  class setting_savedir extends setting {
 
    function update($input) {
        if ($this->is_protected()) return false;
 
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        if (!init_path($input)) {
          $this->_error = true;
          $this->_input = $input;
          return false;
        }
 
        $this->_local = $input;
        return true;
    }
  }
}
 
if (!class_exists('setting_authtype')) {
  class setting_authtype extends setting {
    var $_pattern = '#^[a-zA-Z0-9_]*$#';
 
    function update($input) {
        if ($this->is_protected()) return false;
 
        $input = trim($input);
 
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        if (preg_match($this->_pattern, $input)) {
          if (@file_exists(DOKU_INC.'inc/auth/'.$input.'.class.php') ||
              @file_exists(DOKU_INC.'inc/auth/'.$input.'.php')) {
            $this->_local = $input;
            return true;
          }
        }
 
        $this->_error = true;
        $this->_input = $input;
        return false;    
    }
  }
}
 
if (!class_exists('setting_im_convert')) {
  class setting_im_convert extends setting {
 
    function update($input) {
        if ($this->is_protected()) return false;
 
        $input = trim($input);
 
        $value = is_null($this->_local) ? $this->_default : $this->_local;
        if ($value == $input) return false;
 
        if ($input && !@file_exists($input)) {
          $this->_error = true;
          $this->_input = $input;
          return false;    
        }
 
        $this->_local = $input;
        return true;
    }
  }
}

settings/config.metadata.php

settings/config.metadata.php

<?php
/**
 * Metadata for configuration manager plugin
 *
 * Note:  This file should be included within a function to ensure it 
 *        doesn't class with the settings it is describing.
 *
 * Format:
 *   $meta[<setting name>] = array(<handler class id>,<param name> => <param value>);
 *
 *   <handler class id>  is the handler class name without the "setting_" prefix
 *
 * Defined classes:
 *   Generic
 *   -------------
 *   ''             - default class ('setting'), text input, minimal input validation, setting output in quotes
 *   'numeric'      - text input, accepts numbers and arithmetic operators, setting output without quotes
 *   'onoff'        - checkbox input, setting output  0|1
 *   'multichoice'  - select input (single choice), setting output with quotes, required _choices parameter
 *   'email'        - text input, input must conform to email address format, setting output in quotes
 *   'password'     - password input, minimal input validation, setting output plain text in quotes
 *   'dirchoice'    - as multichoice, selection choices based on folders found at location specified in _dir
 *                    parameter (required)
 *
 *  Single Setting
 *  --------------
 *   'savedir'     - as 'setting', input tested against initpath() (inc/init.php)
 *   'sepchar'     - as multichoice, selection constructed from string of valid values
 *   'authtype'    - as 'setting', input validated against a valid php file at expected location for auth files
 *   'im_convert'  - as 'setting', input must exist and be an im_convert module
 *
 *  Any setting commented or missing will use 'setting' class - text input, minimal validation, quoted output
 *
 * Defined parameters:
 *   '_pattern'    - string, a preg pattern. input is tested against this pattern before being accepted
 *                   optional all classes, except onoff, multichoice & dirchoice which ignore it
 *   '_choices'    - array of choices. used to populate a selection box. choice will be replaced by a localised
 *                   language string, indexed by  <setting name>_o_<choice>, if one exists
 *                   required by 'multichoice' class, ignored by other classes
 *   '_dir'        - location of directory to be used to populate choice list
 *                   required by 'dirchoice' class, ignored by other classes
 *
 * @author    Chris Smith <chris@jalakai.co.uk>
 */
// ---------------[ settings for settings ]------------------------------
$config['format']  = 'php';      // format of setting files, supported formats: php
$config['varname'] = 'conf';     // name of the config variable, sans $
 
// this string is written at the top of the rewritten settings file,
// !! do not include any comment indicators !!
// this value can be overriden when calling save_settings() method
$config['heading'] = 'Dokuwiki\'s Main Configuration File - Local Settings';
 
// ---------------[ setting files ]--------------------------------------
// these values can be string expressions, they will be eval'd before use
$file['local']     = "DOKU_CONF.'local.php'";            // mandatory (file doesn't have to exist)
$file['default']   = "DOKU_CONF.'dokuwiki.php'";         // optional
$file['protected'] = "DOKU_CONF.'local.protected.php'";  // optional
 
// --------------[ setting metadata ]------------------------------------
// - for description of format and fields see top of file
// - order the settings in the order you wish them to appear
// - any settings not mentioned will come after the last setting listed and 
//   will use the default class with no parameters
 
$meta['title']    = array('');
$meta['start']    = array('');
$meta['savedir']  = array('savedir');
$meta['lang']     = array('dirchoice','_dir' => DOKU_INC.'inc/lang/');
$meta['template'] = array('dirchoice','_dir' => DOKU_INC.'lib/tpl/');
 
$meta['umask']    = array('numeric','_pattern' => '/0[0-7]{3}/');  // only accept octal representation
$meta['dmask']    = array('numeric','_pattern' => '/0[0-7]{3}/');  // only accept octal representation
$meta['basedir']  = array('');
$meta['baseurl']  = array('');
 
$meta['fullpath']    = array('onoff');
$meta['recent']      = array('numeric');
$meta['breadcrumbs'] = array('numeric');
$meta['typography']  = array('onoff');
$meta['htmlok']      = array('onoff');
$meta['phpok']       = array('onoff');
$meta['dformat']     = array('');
$meta['signature']   = array('');
$meta['toptoclevel'] = array('multichoice','_choices' => array(1,2,3,4,5));   // 5 toc levels
$meta['maxtoclevel'] = array('multichoice','_choices' => array(1,2,3,4,5));
$meta['maxseclevel'] = array('multichoice','_choices' => array(1,2,3,4,5));
$meta['camelcase']   = array('onoff');
$meta['deaccent']    = array('onoff');
$meta['useheading']  = array('onoff');
$meta['refcheck']    = array('onoff');
$meta['refshow']     = array('numeric');
 
$meta['usewordblock']= array('onoff');
$meta['indexdelay']  = array('numeric');
$meta['relnofollow'] = array('onoff');
$meta['mailguard']   = array('multichoice','_choices' => array('visible','hex','none'));
 
$meta['useacl']      = array('onoff');
$meta['openregister']= array('onoff');
$meta['autopasswd']  = array('onoff');
$meta['authtype']    = array('authtype');
$meta['passcrypt']   = array('multichoice','_choices' => array('smd5','md5','sha1','ssha','crypt','mysql','my411'));
$meta['defaultgroup']= array('');
$meta['superuser']   = array('');
$meta['profileconfirm'] = array('onoff');
 
$meta['userewrite']  = array('multichoice','_choices' => array(0,1,2));
$meta['useslash']    = array('onoff');
$meta['sepchar']     = array('sepchar');
$meta['canonical']   = array('onoff');
$meta['autoplural']  = array('onoff');
$meta['usegzip']     = array('onoff');
$meta['cachetime']   = array('numeric');
$meta['purgeonadd']  = array('onoff');
$meta['locktime']    = array('numeric');
$meta['notify']      = array('email');
$meta['mailfrom']    = array('email');
$meta['gdlib']       = array('multichoice','_choices' => array(0,1,2));
$meta['im_convert']  = array('im_convert');
$meta['spellchecker']= array('onoff');
$meta['subscribers'] = array('onoff');
$meta['pluginmanager'] = array('onoff');
$meta['rss_type']    = array('multichoice','_choices' => array('rss','rss1','rss2','atom'));
$meta['rss_linkto']  = array('multichoice','_choices' => array('diff','page','rev','current'));
 
$meta['target____wiki']      = array('');
$meta['target____interwiki'] = array('');
$meta['target____extern']    = array('');
$meta['target____media']     = array('');
$meta['target____windows']   = array('');
 
$meta['proxy____host'] = array('','_pattern' => '#^[a-z0-9\-\.+]+?#i');
$meta['proxy____port'] = array('numeric');
$meta['proxy____user'] = array('');
$meta['proxy____pass'] = array('password');
$meta['proxy____ssl']  = array('onoff');
 
$meta['safemodehack'] = array('onoff');
$meta['ftp____host']  = array('','_pattern' => '#^[a-z0-9\-\.+]+?#i');
$meta['ftp____port']  = array('numeric');
$meta['ftp____user']  = array('');
$meta['ftp____pass']  = array('password');
$meta['ftp____root']  = array('');

style.css

style.css

/* plugin:configmanager */
#configmanager {margin: 1em;}
#configmanager p.error {border: 1px solid red; background-color: #c66; color: white; padding: 0.5em; text-align:center}
#configmanager p.ok {border: 1px solid green; background-color: #6c6; color: black; padding: 0.5em; text-align:center}
#configmanager p.info {border: 1px solid #8cacbb; background-color: #dee7ec; color: black; padding: 0.5em; text-align:center}
#configmanager form { }
#configmanager table {margin: 1em 0;}
#configmanager td input.text {width: 30em; border: 1px solid #dee7ec;}
#configmanager td select {border: 1px solid #dee7ec;}
 
#configmanager tr.default .input, 
#configmanager tr.default input,
#configmanager tr.default select {
  background-color: #dee7ec;
}
 
#configmanager tr.protected .input, 
#configmanager tr.protected input,
#configmanager tr.protected select {
  background-color: #ffcccc;
}
 
#configmanager td.error  {background-color: red;}
 
/* end plugin:configmanager */

lang/xx/intro.txt

lang/en/intro.txt

====== Configuration Manager ======

Use this page to control the settings of your Dokuwiki installation.  
For help on individual settings refer to [[doku>wiki:config]].
For more details about this plugin see [[doku>plugin:config]].

Settings shown with a light red background are protected and can not be altered with this plugin.
Settings shown with a blue background are the default values and settings shown with a white 
background have been set locally for this particular installation.  Both blue and white settings
can be altered.

Remember to press the **SAVE** button before leaving this page otherwise your changes will be lost.

lang/xx/lang.php

lang/en/lang.php

<?php
/**
 * english language file
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Christopher Smith <chris@jalakai.co.uk>
 */
 
// settings must be present and set appropriately for the language
$lang['encoding']   = 'utf-8';
$lang['direction']  = 'ltr';
 
// for admin plugins, the menu prompt to be displayed in the admin menu
// if set here, the plugin doesn't need to override the getMenuText() method
$lang['menu']       = 'Configuration Settings ...'; 
 
$lang['error']      = 'Settings not updated due to an invalid value, please review your changes and resubmit.
                       <br />The incorrect value(s) will be shown surrounded by a red border.';
$lang['updated']    = 'Settings updated successfully.';
$lang['nochoice']   = '(no other choices available)';
$lang['locked']     = 'The settings file can not be updated, if this is unintentional, <br />
                       ensure the local settings file name and permissions are correct.';
 
// settings prompts
$lang['umask']       = 'new file permission mask';      //set the umask for new files
$lang['dmask']       = 'new folder permission mask';    //directory mask accordingly
$lang['lang']        = 'language';                      //your language
$lang['basedir']     = 'base directory';     //absolute dir from serveroot - blank for autodetection
$lang['baseurl']     = 'base url';           //URL to server including protocol - blank for autodetect
$lang['savedir']     = 'save directory';     //where to store all the files
$lang['start']       = 'start page name';    //name of start page
$lang['title']       = 'wiki title';         //what to show in the title
$lang['template']    = 'template';           //see tpl directory
$lang['fullpath']    = 'use full path';      //show full path of the document or relative to datadir only? 0|1
$lang['recent']      = 'recent changes';     //how many entries to show in recent
$lang['breadcrumbs'] = 'breadcrumbs';        //how many recent visited pages to show
$lang['typography']  = 'typography';         //convert quotes, dashes and stuff to typographic equivalents? 0|1
$lang['htmlok']      = 'allow embedded html';//may raw HTML be embedded? This may break layout and XHTML validity 0|1
$lang['phpok']       = 'allow embedded php'; //may PHP code be embedded? Never do this on the internet! 0|1
$lang['dformat']     = 'date format';        //dateformat accepted by PHPs date() function
$lang['signature']   = 'signature';          //signature see wiki:langig for details
$lang['toptoclevel'] = 'top toc level';      //Level starting with and below to include in AutoTOC (max. 5)
$lang['maxtoclevel'] = 'max toc level';      //Up to which level include into AutoTOC (max. 5)
$lang['maxseclevel'] = 'max section edit level';   //Up to which level create editable sections (max. 5)
$lang['camelcase']   = 'use camelcase for links';  //Use CamelCase for linking? (I don't like it) 0|1
$lang['deaccent']    = 'deaccent in pagenames';    //convert accented chars to unaccented ones in pagenames?
$lang['useheading']  = 'use first heading';        //use the first heading in a page as its name
$lang['refcheck']    = 'media reference check';    //check for references before deleting media files
$lang['refshow']     = 'media references to show'; //how many references should be shown, 5 is a good value
 
$lang['usewordblock']= 'block spam based on words';  //block spam based on words? 0|1
$lang['indexdelay']  = 'time delay before indexing'; //allow indexing after this time (seconds) default is 5 days
$lang['relnofollow'] = 'use rel no follow';          //use rel="nofollow" for external links?
$lang['mailguard']   = 'obfuscate email addresses';  //obfuscate email addresses against spam harvesters?
 
/* Authentication Options - read http://www.splitbrain.org/dokuwiki/wiki:acl */
$lang['useacl']      = 'use ACL';                //Use Access Control Lists to restrict access?
$lang['openregister']= 'open register';          //Should users to be allowed to register?
$lang['autopasswd']  = 'autogenerate passwords'; //autogenerate passwords and email them to user
$lang['authtype']    = 'authentication backend'; //which authentication backend should be used
$lang['passcrypt']   = 'password encryption';    //Used crypt method (smd5,md5,sha1,ssha,crypt,mysql,my411)
$lang['defaultgroup']= 'default group';          //Default groups new Users are added to
$lang['superuser']   = 'superuser';              //The admin can be user or @group
$lang['profileconfirm'] = 'profile confirm';     //Require current password to langirm changes to user profile
 
/* Advanced Options */
$lang['userewrite']  = 'use nice URLs';             //this makes nice URLs: 0: off 1: .htaccess 2: internal
$lang['useslash']    = 'use slash';                 //use slash instead of colon? only when rewrite is on
$lang['sepchar']     = 'page name word separator';  //word separator character in page names; may be a
$lang['canonical']   = 'use fully canonical URLs';  //Should all URLs use full canonical http://... style?
$lang['autoplural']  = 'auto-plural';               //try (non)plural form of nonexisting files?
$lang['usegzip']     = 'use gzip (for attic)';      //gzip old revisions?
$lang['cachetime']   = 'max. age for cache (sec)';  //maximum age for cachefile in seconds (defaults to a day)
$lang['purgeonadd']  = 'purge cache on add';        //purge cache when a new file is added (needed for up to date links)
$lang['locktime']    = 'max. age for lock files (sec)';  //maximum age for lockfiles (defaults to 15 minutes)
$lang['notify']      = 'notify email address';      //send change info to this email (leave blank for nobody)
$lang['mailfrom']    = 'wiki mail from';            //use this email when sending mails
$lang['gdlib']       = 'GD Lib version';              //the GDlib version (0, 1 or 2) 2 tries to autodetect
$lang['im_convert']  = 'imagemagick path';            //path to ImageMagicks convert (will be used instead of GD)
$lang['spellchecker']= 'enable spellchecker';         //enable Spellchecker (needs PHP >= 4.3.0 and aspell installed)
$lang['subscribers'] = 'enable subscription support'; //enable change notice subscription support
$lang['pluginmanager'] = 'enable plugin manager';     //enable automated plugin management (requires plugin)
$lang['rss_type']    = 'rss feed type';             //type of RSS feed to provide, by default:
$lang['rss_linkto']  = 'rss links to';              //what page RSS entries link to:
 
//Set target to use when creating links - leave empty for same window
$lang['target____wiki']      = 'target for internal links';
$lang['target____interwiki'] = 'target for interwiki links';
$lang['target____extern']    = 'target for external links';
$lang['target____media']     = 'target for media links';
$lang['target____windows']   = 'target for windows links';
 
//Proxy setup - if your Server needs a proxy to access the web set these
$lang['proxy____host'] = 'proxy - host';
$lang['proxy____port'] = 'proxy - port';
$lang['proxy____user'] = 'proxy - user name';
$lang['proxy____pass'] = 'proxy - password';
$lang['proxy____ssl']  = 'proxy - ssl';
 
/* Safemode Hack */
$lang['safemodehack'] = 'enable safemode hack';  //read http://wiki.splitbrain.org/wiki:safemodehack !
$lang['ftp____host'] = 'ftp - host';
$lang['ftp____port'] = 'ftp - port';
$lang['ftp____user'] = 'ftp - user name';
$lang['ftp____pass'] = 'ftp - password';
$lang['ftp____root'] = 'ftp - root directory';
 
/* userewrite options */
$lang['userewrite_o_0'] = 'none';
$lang['userewrite_o_1'] = 'htaccess';
$lang['userewrite_o_2'] = 'dokuwiki';
 
/* gdlib options */
$lang['gdlib_o_0'] = 'version 0.x';
$lang['gdlib_o_1'] = 'version 1.x';
$lang['gdlib_o_2'] = 'autodetect';
 
/* rss_type options */
$lang['rss_type_o_rss']  = 'RSS 0.91';
$lang['rss_type_o_rss1'] = 'RSS 1.0';
$lang['rss_type_o_rss2'] = 'RSS 2.0';
$lang['rss_type_o_atom'] = 'Atom 0.3';
 
/* rss_linkto options */
$lang['rss_linkto_o_diff']    = 'list of differences';
$lang['rss_linkto_o_page']    = 'the revised page';
$lang['rss_linkto_o_rev']     = 'list of revisions';
$lang['rss_linkto_o_current'] = 'the current page';

Revision History

2006-01-24
darcs update only. package release held until after upcoming Dokuwiki update
add new config settings
improved pattern for config.class (thanks Otto)
add support for Dokuwiki’s substitution styles
2005-11-11
:!: Fix for self links.
2005-10-30
Released.

Bugs

To Do

:WIP:
review format specific code
there is still some php format code in general places
there is some general code factored into php format blocks
:TODO:
figure out a better way to handle settings of higher array dimension than 1
:TODO:
add help for individual settings - accessible from the settings page
:TODO:
group settings visually for easier editing
:TODO:
add ability to alter plugin settings (or perhaps add configuration class files to the plugin manager plugin)
:TODO:
add new config settings (develonly)
:TODO:
add support for .ini file format

Comments/Discussion

 
tutorials/config.txt · Last modified: 2006/01/24 06:24 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki