1<?php 2 3// must be run within Dokuwiki 4if (!defined('DOKU_INC')) die(); 5 6if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 7 8if (!defined('DOKU_SETTINGS_DIR')) define('DOKU_SETTINGS_DIR',DOKU_INC.'data/settings'); 9 10require_once('settings/settingshierarchy.class.php'); 11 12 13class helper_plugin_settingstree extends DokuWiki_Plugin { 14 15 private $memcache = false; // memcache false: not initialized, null: not present/usable, object: the helper plugin. 16 private $explorer_helper = null; // mandatory dependency, error if dependency is broken. 17 private $explorer_registered = false; // flag to indicate that the callbacks/options are registered to explorertree or not. 18 private $_settingsHierarchy = array(); // settings hierarchy for a plugin array(pluginname => hierarchy) 19 20 21 function get_explorer(){ 22 if (!$this->explorer_helper){ 23 $this->explorer_helper = plugin_load('helper','explorertree'); 24 if (!$this->explorer_helper){ 25 // what is the dokuwiki way to die with fatal plugin errors? 26 trigger_error('Explorertree is a dependency but not available!',E_USER_ERROR); 27 } 28 } 29 return $this->explorer_helper; 30 } 31 function init_explorertree(){ 32 if (!($e = $this->get_explorer())) return; 33 if (!$this->explorer_registered){ 34 $e->registerRoute('settingstree',array( 35 'init_plugin' => array( // this is the method to register routing, hence this method itself is the 'init_plugin' option. 36 'plugin' => 'settingstree', 37 'type' => 'helper', 38 'method' => 'init_explorertree', 39 ), 40 'vars' => array( 41 'id' => 'settingstree_expolrertree', 42 'class' => 'settingstree_explorer', 43 ), 44 )); 45 $this->explorer_registered = true; 46 } 47 return $e; 48 } 49 50 function cache(){ 51 if ($this->memcache === false){ 52 $this->memcache = plugin_load('helper','memcache'); 53 // we don't want to use cache if it does not give performance upgrade 54 if ($this->memcache && $this->memcache->emulated()){ 55 $this->memcache = null; 56 } 57 settingshierarchy::$cache = $this->memcahce; 58 } 59 return $this->memcahce; 60 } 61 62 63 64 /** 65 * Constructor gets default preferences 66 * 67 * These can be overriden by plugins using this class 68 */ 69 function __construct() { 70 // checks if data/settings directory exists, attempts to create it, or error if it's not possible. 71 if (!file_exists(DOKU_SETTINGS_DIR)){ 72 if (!mkdir(DOKU_SETTINGS_DIR)){ 73 trigger_error("Cannot create settings directory:'".DOKU_SETTINGS_DIR."'!",E_USER_ERROR); 74 } 75 }elseif(!is_dir(DOKU_INC."data/settings")){ 76 trigger_error("The '".DOKU_SETTINGS_DIR."' is not a directory!",E_USER_ERROR); 77 } 78 $this->cache(); 79 settingshierarchy::$helper = $this; 80 81 } 82 83 function getMethods() { 84 $result = array(); 85 $result[] = array( 86 'name' => 'checkSettingsVersion', 87 'desc' => 'Checks if a plugin settings require (re)registering settings, by comparing the version in parameter to the currently stored version.', 88 'parameters' => array( 89 'pluginname' => "string plugin's name that needs to be checked e.g. 'dw2pdf'.", 90 'version' => "integer the version of meta/defaults for settings, that is needed (usually timestamp)", 91 ), 92 'return' => 'boolean (stored_version < parameter_version).' 93 ); 94 $result[] = array( 95 'name' => 'registerSettings', 96 'desc' => 'Register config settings for a plugin.', 97 'parameters' => array( 98 'pluginname' => "string plugin's name that needs to be stored e.g. 'dw2pdf'.", 99 'version' => "integer the version of meta/defaults for settings that is going to be registered (usually timestamp)", 100 'meta' => "array the settings' metas. Same structure as in 'conf/metadata.php', i.e. array('settingsname' => array('onoff'),'settingname2'=>array('string','_pattern'=>'/^[1-5]x[1-5]$/'))", 101 'defaults' => "array the default values. i.e. array('settingsname'=>1,'settingsname2'=>'1x3')", 102 ), 103 ); 104 $result[] = array( 105 'name' => 'showAdmin', 106 'desc' => 'Returns embeddable html that can be used on an admin page (or any page) ->explorertree + cofiguration area + placeholder for hierarchy area.', 107 'parameters' => array( 108 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 109 'folder' => "string the folder opened by default. You should use ':' (colon) to separate namespaces.", 110 ), 111 'return' => 'string html (echo-able or sendable via ajax)', 112 ); 113 $result[] = array( 114 'name' => 'showHtml', 115 'desc' => 'Returns embeddable html of the configuration area only.', 116 'parameters' => array( 117 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 118 'folder' => "string the folder opened by default. You should use ':' (colon) to separate namespaces.", 119 ), 120 'return' => 'string html (echo-able or sendable via ajax)', 121 ); 122 $result[] = array( 123 'name' => 'showExportHtml', 124 'desc' => 'Returns embeddable html of the configuration area only (export-config version).', 125 'parameters' => array( 126 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 127 'folder' => "string the folder opened by default. You should use ':' (colon) to separate namespaces.", 128 'options' => "array the options for exporting (e.g. title of export, onsuccess callback)", 129 ), 130 'return' => 'string html (echo-able or sendable via ajax)', 131 ); 132 $result[] = array( 133 'name' => 'showHierarchy', 134 'desc' => 'Returns embeddable html of the hierarchy area only.', 135 'parameters' => array( 136 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 137 'key' => "string the name of the setting e.g. 'pagesize'.", 138 ), 139 'return' => 'string html (echo-able or sendable via ajax)', 140 ); 141 $result[] = array( 142 'name' => 'saveLevel', 143 'desc' => 'Saves/validates changes and returns the updated embeddable html of the configuration area only.', 144 'parameters' => array( 145 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 146 'folder' => "string the folder (level) which's values are going to be saved. You should use ':' (colon) to separate namespaces.", 147 'data' => "array the data to be saved. required structure: array('settingsname'=>array('protect'=>1/0, 'config'=>'newvalue')). Requires only the parameters that are changed!", 148 'results' => "OUT array the results of save: array('success' => true/false, 'error' => 'true/false', 'msg' => 'Changes are saved/Changes are not saved (by lang)')", 149 ), 150 'return' => 'string html (echo-able or sendable via ajax)', 151 ); 152 $result[] = array( 153 'name' => 'exportLevel', 154 'desc' => 'Validates changes and returns nothing on success or the updated embeddable html of the configuration area only with all changes/errors on error.', 155 'parameters' => array( 156 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 157 'folder' => "string the folder (level) which's values are going to be saved. You should use ':' (colon) to separate namespaces.", 158 'data' => "array the data to be saved. required structure: array('settingsname'=>array('protect'=>1/0, 'config'=>'newvalue')). Requires only the parameters that are changed!", 159 'results' => "OUT array the results of save: array('success' => true/false, 'error' => 'true/false', 'msg' => 'Changes are saved/Changes are not saved (by lang)')", 160 'options' => "array the options for exporting (e.g. title of export, onsuccess callback)", 161 ), 162 'return' => 'mixed null on success, string html (echo-able or sendable via ajax) on error', 163 ); 164 $result[] = array( 165 'name' => 'getConf', 166 'desc' => 'Gets the effective values for the current namespace/page. Only values that are changeable by settingstree are returned (i.e. no ignored settings).', 167 'parameters' => array( 168 'pluginname' => "string plugin's name which's settings are displayed e.g. 'dw2pdf'.", 169 'folder' => "string the folder (level) which's values are returned. You should use ':' (colon) to separate namespaces.", 170 ), 171 'return' => "array effective values for each key e.g. array('settingsname'=>1, 'settingsname2'=>'1x3')", 172 ); 173 174 return $result; 175 } 176 177 function checkSettingsVersion($pluginname,$version){ 178 if ($this->cache() && $cache_ver = $this->cache()->get("plugin_settringstree_settingsversion_{$pluginname}")){ 179 return ((int)$cache_ver) < $version; 180 } 181 return @filemtime(DOKU_SETTINGS_DIR."/{$pluginname}.meta.json") < $version; 182 } 183 184 185 function registerSettings($pluginname,$version,$meta,$defaults){ 186 if (!file_put_contents($file = DOKU_SETTINGS_DIR."/{$pluginname}.meta.json",json_encode($meta)) 187 || 188 !file_put_contents($file = DOKU_SETTINGS_DIR."/{$pluginname}.defaults.json",json_encode($defaults)) 189 ){ 190 trigger_error("Can not store settings for {$pluginname} to {$file}!",E_USER_ERROR); 191 } 192 if ($c = $this->cache()){ 193 $TTL = 0; // DECIDE: push this to config? 194 $c->set("plugin_settringstree_settingsversion_{$pluginname}",$version,$TTL); 195 $c->set("plugin_settringstree_settingsmeta_{$pluginname}",$meta,$TTL); 196 $c->set("plugin_settringstree_settingsdefaults_{$pluginname}",$defaults,$TTL); 197 } 198 } 199 200 private function _loadSettings($pluginname){ 201 if (!$this->_settingsHierarchy[$pluginname]){ 202 $c = $this->cache(); 203 if (!$c || !($meta = $c->get("plugin_settringstree_settingsmeta_{$pluginname}"))){ 204 $meta = json_decode(@file_get_contents($file = DOKU_SETTINGS_DIR."/{$pluginname}.meta.json"),true); 205 } 206 if (!is_array($meta)){ 207 trigger_error("Could not load file: {$file}",E_USER_ERROR); 208 } 209 if (!$c || !($defaults = $c->get("plugin_settringstree_settingsdefaults_{$pluginname}"))){ 210 $defaults = json_decode(@file_get_contents($file = DOKU_SETTINGS_DIR."/{$pluginname}.defaults.json"),true); 211 } 212 if (!is_array($defaults)){ 213 trigger_error("Could not load file: {$file}",E_USER_ERROR); 214 } 215 if (!$c || !($values = $c->get("plugin_settringstree_settingsvalues_{$pluginname}"))){ 216 $values = json_decode(@file_get_contents(DOKU_SETTINGS_DIR."/{$pluginname}.json"),true); 217 } 218 if (!is_array($values)){ $values = array(); } 219 $this->_settingsHierarchy[$pluginname] = new settingshierarchy($pluginname,$meta,$defaults,$values); 220 } 221 return $this->_settingsHierarchy[$pluginname]; 222 } 223 private function _storeValues($pluginname,settingshierarchy $set){ 224 $c = $this->cache(); 225 $values = $set->getValueTree(); 226 if ($ret = file_put_contents(DOKU_SETTINGS_DIR."/{$pluginname}.json",json_encode($values)) !== false){ 227 if ($c){ // we don't update cache, if we can't save the values to the filesystem. It would be bad to have correct data until cache is flushed then suddenly something corrupt... 228 $TTL = 0; // TODO: push this to config? 229 $c->set("plugin_settringstree_settingsvalues_{$pluginname}",$values,$TTL); 230 } 231 } 232 return $ret; 233 } 234 function getConf($pluginname,$folder){ 235 $set = $this->_loadSettings($pluginname); 236 $level = $set->getLevel($folder); 237 return $level->getAllValues(); 238 } 239 240 241 function showAdmin($pluginname,$folder){ 242 $set = $this->_loadSettings($pluginname); 243 $e = $this->init_explorertree(); 244 $ret = ""; 245 $ret .= "<div class='settingstree_left'>"; 246 $ret .= $e->htmlExplorer('settingstree',':',$folder); 247 $ret .= "<div class='settingstree_left_column'></div></div>"; 248 $ret .= "<div class='settingstree_right'><form id='settingstree_area' class='settingstree_area' method='GET' onsubmit='return false;'>"; 249 $level = $set->getLevel($folder); 250 $ret .= $level->showHtml(); 251 $ret .="</form></div>"; 252 $ret .= "<script type='text/javascript'> jQuery('#settingstree_area').settingsTree({$this->_treeOpts($pluginname)});</script>"; 253 return $ret; 254 } 255 function saveLevel($pluginname,$folder,$data,&$results){ 256 $set = $this->_loadSettings($pluginname); 257 $level = $set->getLevel($folder); 258 if ($level->checkValues($data) && $this->_storeValues($pluginname,$set)){ // the values are okay, and it managed to save to file/cache 259 $results['error'] = false; 260 $results['msg'] = $this->getLang('changes_saved'); 261 $results['success'] = true; 262 }else{ 263 $results['error'] = true; 264 $results['msg'] = $this->getLang('invalid_values')."<br/>".$this->getLang('changes_not_saved'); 265 $results['success'] = false; 266 } 267 return $level->showHtml(); 268 } 269 function exportLevel($pluginname,$folder,$data,&$results,$options){ 270 $set = $this->_loadSettings($pluginname); 271 $level = $set->getLevel($folder)->getExport($options); 272 if ($level->checkValues($data)){ // the values are okay, and it managed to save to file/cache 273 $results['error'] = false; 274 $results['values'] = $level->getAllValues(); 275// $results['msg'] = $this->getLang('changes_saved'); 276 $results['success'] = true; 277 return null; 278 }else{ 279 $results['error'] = true; 280 $results['msg'] = $this->getLang('invalid_values'); 281 $results['success'] = false; 282 return $level->showHtml(); 283 } 284 } 285 function showHtml($pluginname,$folder){ 286 $set = $this->_loadSettings($pluginname); 287 $level = $set->getLevel($folder); 288 return $level->showHtml(); 289 } 290 function showExportHtml($pluginname,$folder,array $options = array()){ 291 $set = $this->_loadSettings($pluginname); 292 $level = $set->getLevel($folder)->getExport($options); 293 return $level->showHtml(); 294 } 295 296 function showHierarchy($pluginname,$key){ 297 $set = $this->_loadSettings($pluginname); 298 return $set->showHierarchy($key); 299 } 300 301 private function _treeOpts($pluginname){ 302 return json_encode(array( 303 'token'=> getSecurityToken(), 304 'pluginname'=> $pluginname, 305 'explorertree_id' => 'settingstree_expolrertree', 306 307 )); 308 } 309 310 311} 312// vim:ts=4:sw=4:et: 313