PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/plugins/config/settings/config.class.php

http://github.com/splitbrain/dokuwiki
PHP | 1414 lines | 777 code | 222 blank | 415 comment | 207 complexity | c247e1414492d78bdfcadd00520aa981 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Configuration Class and generic setting classes
  4. *
  5. * @author Chris Smith <chris@jalakai.co.uk>
  6. * @author Ben Coburn <btcoburn@silicodon.net>
  7. */
  8. if(!defined('CM_KEYMARKER')) define('CM_KEYMARKER','____');
  9. if (!class_exists('configuration')) {
  10. /**
  11. * Class configuration
  12. */
  13. class configuration {
  14. var $_name = 'conf'; // name of the config variable found in the files (overridden by $config['varname'])
  15. var $_format = 'php'; // format of the config file, supported formats - php (overridden by $config['format'])
  16. var $_heading = ''; // heading string written at top of config file - don't include comment indicators
  17. var $_loaded = false; // set to true after configuration files are loaded
  18. var $_metadata = array(); // holds metadata describing the settings
  19. /** @var setting[] */
  20. var $setting = array(); // array of setting objects
  21. var $locked = false; // configuration is considered locked if it can't be updated
  22. var $show_disabled_plugins = false;
  23. // configuration filenames
  24. var $_default_files = array();
  25. var $_local_files = array(); // updated configuration is written to the first file
  26. var $_protected_files = array();
  27. var $_plugin_list = null;
  28. /**
  29. * constructor
  30. *
  31. * @param string $datafile path to config metadata file
  32. */
  33. public function __construct($datafile) {
  34. global $conf, $config_cascade;
  35. if (!file_exists($datafile)) {
  36. msg('No configuration metadata found at - '.htmlspecialchars($datafile),-1);
  37. return;
  38. }
  39. $meta = array();
  40. include($datafile);
  41. if (isset($config['varname'])) $this->_name = $config['varname'];
  42. if (isset($config['format'])) $this->_format = $config['format'];
  43. if (isset($config['heading'])) $this->_heading = $config['heading'];
  44. $this->_default_files = $config_cascade['main']['default'];
  45. $this->_local_files = $config_cascade['main']['local'];
  46. $this->_protected_files = $config_cascade['main']['protected'];
  47. $this->locked = $this->_is_locked();
  48. $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template']));
  49. $this->retrieve_settings();
  50. }
  51. /**
  52. * Retrieve and stores settings in setting[] attribute
  53. */
  54. public function retrieve_settings() {
  55. global $conf;
  56. $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class');
  57. if (!$this->_loaded) {
  58. $default = array_merge($this->get_plugintpl_default($conf['template']), $this->_read_config_group($this->_default_files));
  59. $local = $this->_read_config_group($this->_local_files);
  60. $protected = $this->_read_config_group($this->_protected_files);
  61. $keys = array_merge(array_keys($this->_metadata),array_keys($default), array_keys($local), array_keys($protected));
  62. $keys = array_unique($keys);
  63. $param = null;
  64. foreach ($keys as $key) {
  65. if (isset($this->_metadata[$key])) {
  66. $class = $this->_metadata[$key][0];
  67. if($class && class_exists('setting_'.$class)){
  68. $class = 'setting_'.$class;
  69. } else {
  70. if($class != '') {
  71. $this->setting[] = new setting_no_class($key,$param);
  72. }
  73. $class = 'setting';
  74. }
  75. $param = $this->_metadata[$key];
  76. array_shift($param);
  77. } else {
  78. $class = 'setting_undefined';
  79. $param = null;
  80. }
  81. if (!in_array($class, $no_default_check) && !isset($default[$key])) {
  82. $this->setting[] = new setting_no_default($key,$param);
  83. }
  84. $this->setting[$key] = new $class($key,$param);
  85. $d = array_key_exists($key, $default) ? $default[$key] : null;
  86. $l = array_key_exists($key, $local) ? $local[$key] : null;
  87. $p = array_key_exists($key, $protected) ? $protected[$key] : null;
  88. $this->setting[$key]->initialize($d,$l,$p);
  89. }
  90. $this->_loaded = true;
  91. }
  92. }
  93. /**
  94. * Stores setting[] array to file
  95. *
  96. * @param string $id Name of plugin, which saves the settings
  97. * @param string $header Text at the top of the rewritten settings file
  98. * @param bool $backup backup current file? (remove any existing backup)
  99. * @return bool succesful?
  100. */
  101. public function save_settings($id, $header='', $backup=true) {
  102. global $conf;
  103. if ($this->locked) return false;
  104. // write back to the last file in the local config cascade
  105. $file = end($this->_local_files);
  106. // backup current file (remove any existing backup)
  107. if (file_exists($file) && $backup) {
  108. if (file_exists($file.'.bak')) @unlink($file.'.bak');
  109. if (!io_rename($file, $file.'.bak')) return false;
  110. }
  111. if (!$fh = @fopen($file, 'wb')) {
  112. io_rename($file.'.bak', $file); // problem opening, restore the backup
  113. return false;
  114. }
  115. if (empty($header)) $header = $this->_heading;
  116. $out = $this->_out_header($id,$header);
  117. foreach ($this->setting as $setting) {
  118. $out .= $setting->out($this->_name, $this->_format);
  119. }
  120. $out .= $this->_out_footer();
  121. @fwrite($fh, $out);
  122. fclose($fh);
  123. if($conf['fperm']) chmod($file, $conf['fperm']);
  124. return true;
  125. }
  126. /**
  127. * Update last modified time stamp of the config file
  128. *
  129. * @return bool
  130. */
  131. public function touch_settings(){
  132. if ($this->locked) return false;
  133. $file = end($this->_local_files);
  134. return @touch($file);
  135. }
  136. /**
  137. * Read and merge given config files
  138. *
  139. * @param array $files file paths
  140. * @return array config settings
  141. */
  142. protected function _read_config_group($files) {
  143. $config = array();
  144. foreach ($files as $file) {
  145. $config = array_merge($config, $this->_read_config($file));
  146. }
  147. return $config;
  148. }
  149. /**
  150. * Return an array of config settings
  151. *
  152. * @param string $file file path
  153. * @return array config settings
  154. */
  155. function _read_config($file) {
  156. if (!$file) return array();
  157. $config = array();
  158. if ($this->_format == 'php') {
  159. if(file_exists($file)){
  160. $contents = @php_strip_whitespace($file);
  161. }else{
  162. $contents = '';
  163. }
  164. $pattern = '/\$'.$this->_name.'\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$'.$this->_name.'|$))/s';
  165. $matches=array();
  166. preg_match_all($pattern,$contents,$matches,PREG_SET_ORDER);
  167. for ($i=0; $i<count($matches); $i++) {
  168. $value = $matches[$i][2];
  169. // correct issues with the incoming data
  170. // FIXME ... for now merge multi-dimensional array indices using ____
  171. $key = preg_replace('/.\]\[./',CM_KEYMARKER,$matches[$i][1]);
  172. // handle arrays
  173. if(preg_match('/^array ?\((.*)\)/', $value, $match)){
  174. $arr = explode(',', $match[1]);
  175. // remove quotes from quoted strings & unescape escaped data
  176. $len = count($arr);
  177. for($j=0; $j<$len; $j++){
  178. $arr[$j] = trim($arr[$j]);
  179. $arr[$j] = $this->_readValue($arr[$j]);
  180. }
  181. $value = $arr;
  182. }else{
  183. $value = $this->_readValue($value);
  184. }
  185. $config[$key] = $value;
  186. }
  187. }
  188. return $config;
  189. }
  190. /**
  191. * Convert php string into value
  192. *
  193. * @param string $value
  194. * @return bool|string
  195. */
  196. protected function _readValue($value) {
  197. $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s';
  198. $unescape_pairs = array(
  199. '\\\\' => '\\',
  200. '\\\'' => '\'',
  201. '\\"' => '"'
  202. );
  203. if($value == 'true') {
  204. $value = true;
  205. } elseif($value == 'false') {
  206. $value = false;
  207. } else {
  208. // remove quotes from quoted strings & unescape escaped data
  209. $value = preg_replace($removequotes_pattern,'$2',$value);
  210. $value = strtr($value, $unescape_pairs);
  211. }
  212. return $value;
  213. }
  214. /**
  215. * Returns header of rewritten settings file
  216. *
  217. * @param string $id plugin name of which generated this output
  218. * @param string $header additional text for at top of the file
  219. * @return string text of header
  220. */
  221. protected function _out_header($id, $header) {
  222. $out = '';
  223. if ($this->_format == 'php') {
  224. $out .= '<'.'?php'."\n".
  225. "/*\n".
  226. " * ".$header."\n".
  227. " * Auto-generated by ".$id." plugin\n".
  228. " * Run for user: ".$_SERVER['REMOTE_USER']."\n".
  229. " * Date: ".date('r')."\n".
  230. " */\n\n";
  231. }
  232. return $out;
  233. }
  234. /**
  235. * Returns footer of rewritten settings file
  236. *
  237. * @return string text of footer
  238. */
  239. protected function _out_footer() {
  240. $out = '';
  241. if ($this->_format == 'php') {
  242. $out .= "\n// end auto-generated content\n";
  243. }
  244. return $out;
  245. }
  246. /**
  247. * Configuration is considered locked if there is no local settings filename
  248. * or the directory its in is not writable or the file exists and is not writable
  249. *
  250. * @return bool true: locked, false: writable
  251. */
  252. protected function _is_locked() {
  253. if (!$this->_local_files) return true;
  254. $local = $this->_local_files[0];
  255. if (!is_writable(dirname($local))) return true;
  256. if (file_exists($local) && !is_writable($local)) return true;
  257. return false;
  258. }
  259. /**
  260. * not used ... conf's contents are an array!
  261. * reduce any multidimensional settings to one dimension using CM_KEYMARKER
  262. *
  263. * @param $conf
  264. * @param string $prefix
  265. * @return array
  266. */
  267. protected function _flatten($conf,$prefix='') {
  268. $out = array();
  269. foreach($conf as $key => $value) {
  270. if (!is_array($value)) {
  271. $out[$prefix.$key] = $value;
  272. continue;
  273. }
  274. $tmp = $this->_flatten($value,$prefix.$key.CM_KEYMARKER);
  275. $out = array_merge($out,$tmp);
  276. }
  277. return $out;
  278. }
  279. /**
  280. * Returns array of plugin names
  281. *
  282. * @return array plugin names
  283. * @triggers PLUGIN_CONFIG_PLUGINLIST event
  284. */
  285. function get_plugin_list() {
  286. if (is_null($this->_plugin_list)) {
  287. $list = plugin_list('',$this->show_disabled_plugins);
  288. // remove this plugin from the list
  289. $idx = array_search('config',$list);
  290. unset($list[$idx]);
  291. trigger_event('PLUGIN_CONFIG_PLUGINLIST',$list);
  292. $this->_plugin_list = $list;
  293. }
  294. return $this->_plugin_list;
  295. }
  296. /**
  297. * load metadata for plugin and template settings
  298. *
  299. * @param string $tpl name of active template
  300. * @return array metadata of settings
  301. */
  302. function get_plugintpl_metadata($tpl){
  303. $file = '/conf/metadata.php';
  304. $class = '/conf/settings.class.php';
  305. $metadata = array();
  306. foreach ($this->get_plugin_list() as $plugin) {
  307. $plugin_dir = plugin_directory($plugin);
  308. if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
  309. $meta = array();
  310. @include(DOKU_PLUGIN.$plugin_dir.$file);
  311. @include(DOKU_PLUGIN.$plugin_dir.$class);
  312. if (!empty($meta)) {
  313. $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = array('fieldset');
  314. }
  315. foreach ($meta as $key => $value){
  316. if ($value[0]=='fieldset') { continue; } //plugins only get one fieldset
  317. $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
  318. }
  319. }
  320. }
  321. // the same for the active template
  322. if (file_exists(tpl_incdir().$file)){
  323. $meta = array();
  324. @include(tpl_incdir().$file);
  325. @include(tpl_incdir().$class);
  326. if (!empty($meta)) {
  327. $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.'template_settings_name'] = array('fieldset');
  328. }
  329. foreach ($meta as $key => $value){
  330. if ($value[0]=='fieldset') { continue; } //template only gets one fieldset
  331. $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value;
  332. }
  333. }
  334. return $metadata;
  335. }
  336. /**
  337. * Load default settings for plugins and templates
  338. *
  339. * @param string $tpl name of active template
  340. * @return array default settings
  341. */
  342. function get_plugintpl_default($tpl){
  343. $file = '/conf/default.php';
  344. $default = array();
  345. foreach ($this->get_plugin_list() as $plugin) {
  346. $plugin_dir = plugin_directory($plugin);
  347. if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
  348. $conf = $this->_read_config(DOKU_PLUGIN.$plugin_dir.$file);
  349. foreach ($conf as $key => $value){
  350. $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
  351. }
  352. }
  353. }
  354. // the same for the active template
  355. if (file_exists(tpl_incdir().$file)){
  356. $conf = $this->_read_config(tpl_incdir().$file);
  357. foreach ($conf as $key => $value){
  358. $default['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value;
  359. }
  360. }
  361. return $default;
  362. }
  363. }
  364. }
  365. if (!class_exists('setting')) {
  366. /**
  367. * Class setting
  368. */
  369. class setting {
  370. var $_key = '';
  371. var $_default = null;
  372. var $_local = null;
  373. var $_protected = null;
  374. var $_pattern = '';
  375. var $_error = false; // only used by those classes which error check
  376. var $_input = null; // only used by those classes which error check
  377. var $_caution = null; // used by any setting to provide an alert along with the setting
  378. // valid alerts, 'warning', 'danger', 'security'
  379. // images matching the alerts are in the plugin's images directory
  380. static protected $_validCautions = array('warning','danger','security');
  381. /**
  382. * @param string $key
  383. * @param array|null $params array with metadata of setting
  384. */
  385. public function __construct($key, $params=null) {
  386. $this->_key = $key;
  387. if (is_array($params)) {
  388. foreach($params as $property => $value) {
  389. $this->$property = $value;
  390. }
  391. }
  392. }
  393. /**
  394. * Receives current values for the setting $key
  395. *
  396. * @param mixed $default default setting value
  397. * @param mixed $local local setting value
  398. * @param mixed $protected protected setting value
  399. */
  400. public function initialize($default, $local, $protected) {
  401. if (isset($default)) $this->_default = $default;
  402. if (isset($local)) $this->_local = $local;
  403. if (isset($protected)) $this->_protected = $protected;
  404. }
  405. /**
  406. * update changed setting with user provided value $input
  407. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  408. * - if changed value passes error check, set $this->_local to the new value
  409. *
  410. * @param mixed $input the new value
  411. * @return boolean true if changed, false otherwise (also on error)
  412. */
  413. public function update($input) {
  414. if (is_null($input)) return false;
  415. if ($this->is_protected()) return false;
  416. $value = is_null($this->_local) ? $this->_default : $this->_local;
  417. if ($value == $input) return false;
  418. if ($this->_pattern && !preg_match($this->_pattern,$input)) {
  419. $this->_error = true;
  420. $this->_input = $input;
  421. return false;
  422. }
  423. $this->_local = $input;
  424. return true;
  425. }
  426. /**
  427. * Build html for label and input of setting
  428. *
  429. * @param DokuWiki_Plugin $plugin object of config plugin
  430. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  431. * @return string[] with content array(string $label_html, string $input_html)
  432. */
  433. public function html(&$plugin, $echo=false) {
  434. $disable = '';
  435. if ($this->is_protected()) {
  436. $value = $this->_protected;
  437. $disable = 'disabled="disabled"';
  438. } else {
  439. if ($echo && $this->_error) {
  440. $value = $this->_input;
  441. } else {
  442. $value = is_null($this->_local) ? $this->_default : $this->_local;
  443. }
  444. }
  445. $key = htmlspecialchars($this->_key);
  446. $value = formText($value);
  447. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  448. $input = '<textarea rows="3" cols="40" id="config___'.$key.'" name="config['.$key.']" class="edit" '.$disable.'>'.$value.'</textarea>';
  449. return array($label,$input);
  450. }
  451. /**
  452. * Generate string to save setting value to file according to $fmt
  453. *
  454. * @param string $var name of variable
  455. * @param string $fmt save format
  456. * @return string
  457. */
  458. public function out($var, $fmt='php') {
  459. if ($this->is_protected()) return '';
  460. if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
  461. $out = '';
  462. if ($fmt=='php') {
  463. $tr = array("\\" => '\\\\', "'" => '\\\'');
  464. $out = '$'.$var."['".$this->_out_key()."'] = '".strtr( cleanText($this->_local), $tr)."';\n";
  465. }
  466. return $out;
  467. }
  468. /**
  469. * Returns the localized prompt
  470. *
  471. * @param DokuWiki_Plugin $plugin object of config plugin
  472. * @return string text
  473. */
  474. public function prompt(&$plugin) {
  475. $prompt = $plugin->getLang($this->_key);
  476. if (!$prompt) $prompt = htmlspecialchars(str_replace(array('____','_'),' ',$this->_key));
  477. return $prompt;
  478. }
  479. /**
  480. * Is setting protected
  481. *
  482. * @return bool
  483. */
  484. public function is_protected() { return !is_null($this->_protected); }
  485. /**
  486. * Is setting the default?
  487. *
  488. * @return bool
  489. */
  490. public function is_default() { return !$this->is_protected() && is_null($this->_local); }
  491. /**
  492. * Has an error?
  493. *
  494. * @return bool
  495. */
  496. public function error() { return $this->_error; }
  497. /**
  498. * Returns caution
  499. *
  500. * @return false|string caution string, otherwise false for invalid caution
  501. */
  502. public function caution() {
  503. if (!empty($this->_caution)) {
  504. if (!in_array($this->_caution, setting::$_validCautions)) {
  505. trigger_error('Invalid caution string ('.$this->_caution.') in metadata for setting "'.$this->_key.'"', E_USER_WARNING);
  506. return false;
  507. }
  508. return $this->_caution;
  509. }
  510. // compatibility with previous cautionList
  511. // TODO: check if any plugins use; remove
  512. if (!empty($this->_cautionList[$this->_key])) {
  513. $this->_caution = $this->_cautionList[$this->_key];
  514. unset($this->_cautionList);
  515. return $this->caution();
  516. }
  517. return false;
  518. }
  519. /**
  520. * Returns setting key, eventually with referer to config: namespace at dokuwiki.org
  521. *
  522. * @param bool $pretty create nice key
  523. * @param bool $url provide url to config: namespace
  524. * @return string key
  525. */
  526. public function _out_key($pretty=false,$url=false) {
  527. if($pretty){
  528. $out = str_replace(CM_KEYMARKER,"Âť",$this->_key);
  529. if ($url && !strstr($out,'Âť')) {//provide no urls for plugins, etc.
  530. if ($out == 'start') //one exception
  531. return '<a href="http://www.dokuwiki.org/config:startpage">'.$out.'</a>';
  532. else
  533. return '<a href="http://www.dokuwiki.org/config:'.$out.'">'.$out.'</a>';
  534. }
  535. return $out;
  536. }else{
  537. return str_replace(CM_KEYMARKER,"']['",$this->_key);
  538. }
  539. }
  540. }
  541. }
  542. if (!class_exists('setting_array')) {
  543. /**
  544. * Class setting_array
  545. */
  546. class setting_array extends setting {
  547. /**
  548. * Create an array from a string
  549. *
  550. * @param string $string
  551. * @return array
  552. */
  553. protected function _from_string($string){
  554. $array = explode(',', $string);
  555. $array = array_map('trim', $array);
  556. $array = array_filter($array);
  557. $array = array_unique($array);
  558. return $array;
  559. }
  560. /**
  561. * Create a string from an array
  562. *
  563. * @param array $array
  564. * @return string
  565. */
  566. protected function _from_array($array){
  567. return join(', ', (array) $array);
  568. }
  569. /**
  570. * update setting with user provided value $input
  571. * if value fails error check, save it
  572. *
  573. * @param string $input
  574. * @return bool true if changed, false otherwise (incl. on error)
  575. */
  576. function update($input) {
  577. if (is_null($input)) return false;
  578. if ($this->is_protected()) return false;
  579. $input = $this->_from_string($input);
  580. $value = is_null($this->_local) ? $this->_default : $this->_local;
  581. if ($value == $input) return false;
  582. foreach($input as $item){
  583. if ($this->_pattern && !preg_match($this->_pattern,$item)) {
  584. $this->_error = true;
  585. $this->_input = $input;
  586. return false;
  587. }
  588. }
  589. $this->_local = $input;
  590. return true;
  591. }
  592. /**
  593. * Escaping
  594. *
  595. * @param string $string
  596. * @return string
  597. */
  598. protected function _escape($string) {
  599. $tr = array("\\" => '\\\\', "'" => '\\\'');
  600. return "'".strtr( cleanText($string), $tr)."'";
  601. }
  602. /**
  603. * Generate string to save setting value to file according to $fmt
  604. *
  605. * @param string $var name of variable
  606. * @param string $fmt save format
  607. * @return string
  608. */
  609. function out($var, $fmt='php') {
  610. if ($this->is_protected()) return '';
  611. if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
  612. $out = '';
  613. if ($fmt=='php') {
  614. $vals = array_map(array($this, '_escape'), $this->_local);
  615. $out = '$'.$var."['".$this->_out_key()."'] = array(".join(', ',$vals).");\n";
  616. }
  617. return $out;
  618. }
  619. /**
  620. * Build html for label and input of setting
  621. *
  622. * @param DokuWiki_Plugin $plugin object of config plugin
  623. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  624. * @return string[] with content array(string $label_html, string $input_html)
  625. */
  626. function html(&$plugin, $echo=false) {
  627. $disable = '';
  628. if ($this->is_protected()) {
  629. $value = $this->_protected;
  630. $disable = 'disabled="disabled"';
  631. } else {
  632. if ($echo && $this->_error) {
  633. $value = $this->_input;
  634. } else {
  635. $value = is_null($this->_local) ? $this->_default : $this->_local;
  636. }
  637. }
  638. $key = htmlspecialchars($this->_key);
  639. $value = htmlspecialchars($this->_from_array($value));
  640. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  641. $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>';
  642. return array($label,$input);
  643. }
  644. }
  645. }
  646. if (!class_exists('setting_string')) {
  647. /**
  648. * Class setting_string
  649. */
  650. class setting_string extends setting {
  651. /**
  652. * Build html for label and input of setting
  653. *
  654. * @param DokuWiki_Plugin $plugin object of config plugin
  655. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  656. * @return string[] with content array(string $label_html, string $input_html)
  657. */
  658. function html(&$plugin, $echo=false) {
  659. $disable = '';
  660. if ($this->is_protected()) {
  661. $value = $this->_protected;
  662. $disable = 'disabled="disabled"';
  663. } else {
  664. if ($echo && $this->_error) {
  665. $value = $this->_input;
  666. } else {
  667. $value = is_null($this->_local) ? $this->_default : $this->_local;
  668. }
  669. }
  670. $key = htmlspecialchars($this->_key);
  671. $value = htmlspecialchars($value);
  672. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  673. $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>';
  674. return array($label,$input);
  675. }
  676. }
  677. }
  678. if (!class_exists('setting_password')) {
  679. /**
  680. * Class setting_password
  681. */
  682. class setting_password extends setting_string {
  683. var $_code = 'plain'; // mechanism to be used to obscure passwords
  684. /**
  685. * update changed setting with user provided value $input
  686. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  687. * - if changed value passes error check, set $this->_local to the new value
  688. *
  689. * @param mixed $input the new value
  690. * @return boolean true if changed, false otherwise (also on error)
  691. */
  692. function update($input) {
  693. if ($this->is_protected()) return false;
  694. if (!$input) return false;
  695. if ($this->_pattern && !preg_match($this->_pattern,$input)) {
  696. $this->_error = true;
  697. $this->_input = $input;
  698. return false;
  699. }
  700. $this->_local = conf_encodeString($input,$this->_code);
  701. return true;
  702. }
  703. /**
  704. * Build html for label and input of setting
  705. *
  706. * @param DokuWiki_Plugin $plugin object of config plugin
  707. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  708. * @return string[] with content array(string $label_html, string $input_html)
  709. */
  710. function html(&$plugin, $echo=false) {
  711. $disable = $this->is_protected() ? 'disabled="disabled"' : '';
  712. $key = htmlspecialchars($this->_key);
  713. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  714. $input = '<input id="config___'.$key.'" name="config['.$key.']" autocomplete="off" type="password" class="edit" value="" '.$disable.' />';
  715. return array($label,$input);
  716. }
  717. }
  718. }
  719. if (!class_exists('setting_email')) {
  720. /**
  721. * Class setting_email
  722. */
  723. class setting_email extends setting_string {
  724. var $_multiple = false;
  725. var $_placeholders = false;
  726. /**
  727. * update setting with user provided value $input
  728. * if value fails error check, save it
  729. *
  730. * @param mixed $input
  731. * @return boolean true if changed, false otherwise (incl. on error)
  732. */
  733. function update($input) {
  734. if (is_null($input)) return false;
  735. if ($this->is_protected()) return false;
  736. $value = is_null($this->_local) ? $this->_default : $this->_local;
  737. if ($value == $input) return false;
  738. if($input === ''){
  739. $this->_local = $input;
  740. return true;
  741. }
  742. $mail = $input;
  743. if($this->_placeholders){
  744. // replace variables with pseudo values
  745. $mail = str_replace('@USER@','joe',$mail);
  746. $mail = str_replace('@NAME@','Joe Schmoe',$mail);
  747. $mail = str_replace('@MAIL@','joe@example.com',$mail);
  748. }
  749. // multiple mail addresses?
  750. if ($this->_multiple) {
  751. $mails = array_filter(array_map('trim', explode(',', $mail)));
  752. } else {
  753. $mails = array($mail);
  754. }
  755. // check them all
  756. foreach ($mails as $mail) {
  757. // only check the address part
  758. if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)){
  759. $addr = $matches[2];
  760. }else{
  761. $addr = $mail;
  762. }
  763. if (!mail_isvalid($addr)) {
  764. $this->_error = true;
  765. $this->_input = $input;
  766. return false;
  767. }
  768. }
  769. $this->_local = $input;
  770. return true;
  771. }
  772. }
  773. }
  774. if (!class_exists('setting_numeric')) {
  775. /**
  776. * Class setting_numeric
  777. */
  778. class setting_numeric extends setting_string {
  779. // This allows for many PHP syntax errors...
  780. // var $_pattern = '/^[-+\/*0-9 ]*$/';
  781. // much more restrictive, but should eliminate syntax errors.
  782. var $_pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/';
  783. var $_min = null;
  784. var $_max = null;
  785. /**
  786. * update changed setting with user provided value $input
  787. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  788. * - if changed value passes error check, set $this->_local to the new value
  789. *
  790. * @param mixed $input the new value
  791. * @return boolean true if changed, false otherwise (also on error)
  792. */
  793. function update($input) {
  794. $local = $this->_local;
  795. $valid = parent::update($input);
  796. if ($valid && !(is_null($this->_min) && is_null($this->_max))) {
  797. $numeric_local = (int) eval('return '.$this->_local.';');
  798. if ((!is_null($this->_min) && $numeric_local < $this->_min) ||
  799. (!is_null($this->_max) && $numeric_local > $this->_max)) {
  800. $this->_error = true;
  801. $this->_input = $input;
  802. $this->_local = $local;
  803. $valid = false;
  804. }
  805. }
  806. return $valid;
  807. }
  808. /**
  809. * Generate string to save setting value to file according to $fmt
  810. *
  811. * @param string $var name of variable
  812. * @param string $fmt save format
  813. * @return string
  814. */
  815. function out($var, $fmt='php') {
  816. if ($this->is_protected()) return '';
  817. if (is_null($this->_local) || ($this->_default == $this->_local)) return '';
  818. $out = '';
  819. if ($fmt=='php') {
  820. $local = $this->_local === '' ? "''" : $this->_local;
  821. $out .= '$'.$var."['".$this->_out_key()."'] = ".$local.";\n";
  822. }
  823. return $out;
  824. }
  825. }
  826. }
  827. if (!class_exists('setting_numericopt')) {
  828. /**
  829. * Class setting_numericopt
  830. */
  831. class setting_numericopt extends setting_numeric {
  832. // just allow an empty config
  833. var $_pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/';
  834. }
  835. }
  836. if (!class_exists('setting_onoff')) {
  837. /**
  838. * Class setting_onoff
  839. */
  840. class setting_onoff extends setting_numeric {
  841. /**
  842. * Build html for label and input of setting
  843. *
  844. * @param DokuWiki_Plugin $plugin object of config plugin
  845. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  846. * @return string[] with content array(string $label_html, string $input_html)
  847. */
  848. function html(&$plugin, $echo = false) {
  849. $disable = '';
  850. if ($this->is_protected()) {
  851. $value = $this->_protected;
  852. $disable = ' disabled="disabled"';
  853. } else {
  854. $value = is_null($this->_local) ? $this->_default : $this->_local;
  855. }
  856. $key = htmlspecialchars($this->_key);
  857. $checked = ($value) ? ' checked="checked"' : '';
  858. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  859. $input = '<div class="input"><input id="config___'.$key.'" name="config['.$key.']" type="checkbox" class="checkbox" value="1"'.$checked.$disable.'/></div>';
  860. return array($label,$input);
  861. }
  862. /**
  863. * update changed setting with user provided value $input
  864. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  865. * - if changed value passes error check, set $this->_local to the new value
  866. *
  867. * @param mixed $input the new value
  868. * @return boolean true if changed, false otherwise (also on error)
  869. */
  870. function update($input) {
  871. if ($this->is_protected()) return false;
  872. $input = ($input) ? 1 : 0;
  873. $value = is_null($this->_local) ? $this->_default : $this->_local;
  874. if ($value == $input) return false;
  875. $this->_local = $input;
  876. return true;
  877. }
  878. }
  879. }
  880. if (!class_exists('setting_multichoice')) {
  881. /**
  882. * Class setting_multichoice
  883. */
  884. class setting_multichoice extends setting_string {
  885. var $_choices = array();
  886. var $lang; //some custom language strings are stored in setting
  887. /**
  888. * Build html for label and input of setting
  889. *
  890. * @param DokuWiki_Plugin $plugin object of config plugin
  891. * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting
  892. * @return string[] with content array(string $label_html, string $input_html)
  893. */
  894. function html(&$plugin, $echo = false) {
  895. $disable = '';
  896. $nochoice = '';
  897. if ($this->is_protected()) {
  898. $value = $this->_protected;
  899. $disable = ' disabled="disabled"';
  900. } else {
  901. $value = is_null($this->_local) ? $this->_default : $this->_local;
  902. }
  903. // ensure current value is included
  904. if (!in_array($value, $this->_choices)) {
  905. $this->_choices[] = $value;
  906. }
  907. // disable if no other choices
  908. if (!$this->is_protected() && count($this->_choices) <= 1) {
  909. $disable = ' disabled="disabled"';
  910. $nochoice = $plugin->getLang('nochoice');
  911. }
  912. $key = htmlspecialchars($this->_key);
  913. $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>';
  914. $input = "<div class=\"input\">\n";
  915. $input .= '<select class="edit" id="config___'.$key.'" name="config['.$key.']"'.$disable.'>'."\n";
  916. foreach ($this->_choices as $choice) {
  917. $selected = ($value == $choice) ? ' selected="selected"' : '';
  918. $option = $plugin->getLang($this->_key.'_o_'.$choice);
  919. if (!$option && isset($this->lang[$this->_key.'_o_'.$choice])) $option = $this->lang[$this->_key.'_o_'.$choice];
  920. if (!$option) $option = $choice;
  921. $choice = htmlspecialchars($choice);
  922. $option = htmlspecialchars($option);
  923. $input .= ' <option value="'.$choice.'"'.$selected.' >'.$option.'</option>'."\n";
  924. }
  925. $input .= "</select> $nochoice \n";
  926. $input .= "</div>\n";
  927. return array($label,$input);
  928. }
  929. /**
  930. * update changed setting with user provided value $input
  931. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  932. * - if changed value passes error check, set $this->_local to the new value
  933. *
  934. * @param mixed $input the new value
  935. * @return boolean true if changed, false otherwise (also on error)
  936. */
  937. function update($input) {
  938. if (is_null($input)) return false;
  939. if ($this->is_protected()) return false;
  940. $value = is_null($this->_local) ? $this->_default : $this->_local;
  941. if ($value == $input) return false;
  942. if (!in_array($input, $this->_choices)) return false;
  943. $this->_local = $input;
  944. return true;
  945. }
  946. }
  947. }
  948. if (!class_exists('setting_dirchoice')) {
  949. /**
  950. * Class setting_dirchoice
  951. */
  952. class setting_dirchoice extends setting_multichoice {
  953. var $_dir = '';
  954. /**
  955. * Receives current values for the setting $key
  956. *
  957. * @param mixed $default default setting value
  958. * @param mixed $local local setting value
  959. * @param mixed $protected protected setting value
  960. */
  961. function initialize($default,$local,$protected) {
  962. // populate $this->_choices with a list of directories
  963. $list = array();
  964. if ($dh = @opendir($this->_dir)) {
  965. while (false !== ($entry = readdir($dh))) {
  966. if ($entry == '.' || $entry == '..') continue;
  967. if ($this->_pattern && !preg_match($this->_pattern,$entry)) continue;
  968. $file = (is_link($this->_dir.$entry)) ? readlink($this->_dir.$entry) : $this->_dir.$entry;
  969. if (is_dir($file)) $list[] = $entry;
  970. }
  971. closedir($dh);
  972. }
  973. sort($list);
  974. $this->_choices = $list;
  975. parent::initialize($default,$local,$protected);
  976. }
  977. }
  978. }
  979. if (!class_exists('setting_hidden')) {
  980. /**
  981. * Class setting_hidden
  982. */
  983. class setting_hidden extends setting {
  984. // Used to explicitly ignore a setting in the configuration manager.
  985. }
  986. }
  987. if (!class_exists('setting_fieldset')) {
  988. /**
  989. * Class setting_fieldset
  990. */
  991. class setting_fieldset extends setting {
  992. // A do-nothing class used to detect the 'fieldset' type.
  993. // Used to start a new settings "display-group".
  994. }
  995. }
  996. if (!class_exists('setting_undefined')) {
  997. /**
  998. * Class setting_undefined
  999. */
  1000. class setting_undefined extends setting_hidden {
  1001. // A do-nothing class used to detect settings with no metadata entry.
  1002. // Used internaly to hide undefined settings, and generate the undefined settings list.
  1003. }
  1004. }
  1005. if (!class_exists('setting_no_class')) {
  1006. /**
  1007. * Class setting_no_class
  1008. */
  1009. class setting_no_class extends setting_undefined {
  1010. // A do-nothing class used to detect settings with a missing setting class.
  1011. // Used internaly to hide undefined settings, and generate the undefined settings list.
  1012. }
  1013. }
  1014. if (!class_exists('setting_no_default')) {
  1015. /**
  1016. * Class setting_no_default
  1017. */
  1018. class setting_no_default extends setting_undefined {
  1019. // A do-nothing class used to detect settings with no default value.
  1020. // Used internaly to hide undefined settings, and generate the undefined settings list.
  1021. }
  1022. }
  1023. if (!class_exists('setting_multicheckbox')) {
  1024. /**
  1025. * Class setting_multicheckbox
  1026. */
  1027. class setting_multicheckbox extends setting_string {
  1028. var $_choices = array();
  1029. var $_combine = array();
  1030. var $_other = 'always';
  1031. /**
  1032. * update changed setting with user provided value $input
  1033. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  1034. * - if changed value passes error check, set $this->_local to the new value
  1035. *
  1036. * @param mixed $input the new value
  1037. * @return boolean true if changed, false otherwise (also on error)
  1038. */
  1039. function update($input) {
  1040. if ($this->is_protected()) return false;
  1041. // split any combined values + convert from array to comma separated string
  1042. $input = ($input) ? $input : array();
  1043. $input = $this->_array2str($input);
  1044. $value = is_null($this->_local) ? $this->_default : $this->_local;
  1045. if ($value == $input) return false;
  1046. if ($this->_pattern && !preg_match($this->_pattern,$input)) {
  1047. $this->_error = true;
  1048. $this->_input = $input;
  1049. return false;
  1050. }
  1051. $this->_local = $input;
  1052. return true;
  1053. }
  1054. /**
  1055. * Build html for label and input of setting
  1056. *
  1057. * @param DokuWiki_Plugin $plugin object of config plugin
  1058. * @param bool $echo true: show input value, when error occurred, otherwise the stored setting
  1059. * @return string[] with content array(string $label_html, string $input_html)
  1060. */
  1061. function html(&$plugin, $echo=false) {
  1062. $disable = '';
  1063. if ($this->is_protected()) {
  1064. $value = $this->_protected;
  1065. $disable = 'disabled="disabled"';
  1066. } else {
  1067. if ($echo && $this->_error) {
  1068. $value = $this->_input;
  1069. } else {
  1070. $value = is_null($this->_local) ? $this->_default : $this->_local;
  1071. }
  1072. }
  1073. $key = htmlspecialchars($this->_key);
  1074. // convert from comma separated list into array + combine complimentary actions
  1075. $value = $this->_str2array($value);
  1076. $default = $this->_str2array($this->_default);
  1077. $input = '';
  1078. foreach ($this->_choices as $choice) {
  1079. $idx = array_search($choice, $value);
  1080. $idx_default = array_search($choice,$default);
  1081. $checked = ($idx !== false) ? 'checked="checked"' : '';
  1082. // @todo ideally this would be handled using a second class of "default"
  1083. $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : "";
  1084. $prompt = ($plugin->getLang($this->_key.'_'.$choice) ?
  1085. $plugin->getLang($this->_key.'_'.$choice) : htmlspecialchars($choice));
  1086. $input .= '<div class="selection'.$class.'">'."\n";
  1087. $input .= '<label for="config___'.$key.'_'.$choice.'">'.$prompt."</label>\n";
  1088. $input .= '<input id="config___'.$key.'_'.$choice.'" name="config['.$key.'][]" type="checkbox" class="checkbox" value="'.$choice.'" '.$disable.' '.$checked."/>\n";
  1089. $input .= "</div>\n";
  1090. // remove this action from the disabledactions array
  1091. if ($idx !== false) unset($value[$idx]);
  1092. if ($idx_default !== false) unset($default[$idx_default]);
  1093. }
  1094. // handle any remaining values
  1095. if ($this->_other != 'never'){
  1096. $other = join(',',$value);
  1097. // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists')
  1098. // use != 'exists' rather than == 'always' to ensure invalid values default to 'always'
  1099. if ($this->_other != 'exists' || $other) {
  1100. $class = ((count($default) == count($value)) && (count($value) == count(array_intersect($value,$default)))) ?
  1101. " selectiondefault" : "";
  1102. $input .= '<div class="other'.$class.'">'."\n";
  1103. $input .= '<label for="config___'.$key.'_other">'.$plugin->getLang($key.'_other')."</label>\n";
  1104. $input .= '<input id="config___'.$key.'_other" name="config['.$key.'][other]" type="text" class="edit" value="'.htmlspecialchars($other).'" '.$disable." />\n";
  1105. $input .= "</div>\n";
  1106. }
  1107. }
  1108. $label = '<label>'.$this->prompt($plugin).'</label>';
  1109. return array($label,$input);
  1110. }
  1111. /**
  1112. * convert comma separated list to an array and combine any complimentary values
  1113. *
  1114. * @param string $str
  1115. * @return array
  1116. */
  1117. function _str2array($str) {
  1118. $array = explode(',',$str);
  1119. if (!empty($this->_combine)) {
  1120. foreach ($this->_combine as $key => $combinators) {
  1121. $idx = array();
  1122. foreach ($combinators as $val) {
  1123. if (($idx[] = array_search($val, $array)) === false) break;
  1124. }
  1125. if (count($idx) && $idx[count($idx)-1] !== false) {
  1126. foreach ($idx as $i) unset($array[$i]);
  1127. $array[] = $key;
  1128. }
  1129. }
  1130. }
  1131. return $array;
  1132. }
  1133. /**
  1134. * convert array of values + other back to a comma separated list, incl. splitting any combined values
  1135. *
  1136. * @param array $input
  1137. * @return string
  1138. */
  1139. function _array2str($input) {
  1140. // handle other
  1141. $other = trim($input['other']);
  1142. $other = !empty($other) ? explode(',',str_replace(' ','',$input['other'])) : array();
  1143. unset($input['other']);
  1144. $array = array_unique(array_merge($input, $other));
  1145. // deconstruct any combinations
  1146. if (!empty($this->_combine)) {
  1147. foreach ($this->_combine as $key => $combinators) {
  1148. $idx = array_search($key,$array);
  1149. if ($idx !== false) {
  1150. unset($array[$idx]);
  1151. $array = array_merge($array, $combinators);
  1152. }
  1153. }
  1154. }
  1155. return join(',',array_unique($array));
  1156. }
  1157. }
  1158. }
  1159. if (!class_exists('setting_regex')){
  1160. /**
  1161. * Class setting_regex
  1162. */
  1163. class setting_regex extends setting_string {
  1164. var $_delimiter = '/'; // regex delimiter to be used in testing input
  1165. var $_pregflags = 'ui'; // regex pattern modifiers to be used in testing input
  1166. /**
  1167. * update changed setting with user provided value $input
  1168. * - if changed value fails error check, save it to $this->_input (to allow echoing later)
  1169. * - if changed value passes error check, set $this->_local to the new value
  1170. *
  1171. * @param mixed $input the new value
  1172. * @return boolean true if changed, false otherwise (incl. on error)
  1173. */
  1174. function update($input) {
  1175. // let parent do basic checks, value, not changed, etc.
  1176. $local = $this->_local;
  1177. if (!parent::update($input)) return false;
  1178. $this->_local = $local;
  1179. // see if the regex compiles and runs (we don't check for effectiveness)
  1180. $regex = $this->_delimiter . $input . $this->_delimiter . $this->_pregflags;
  1181. $lastError = error_get_last();
  1182. @preg_match($regex,'testdata');
  1183. if (preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) {
  1184. $this->_input = $input;
  1185. $this->_error = true;
  1186. return false;
  1187. }
  1188. $this->_local = $input;
  1189. return true;
  1190. }
  1191. }
  1192. }