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