diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/ApplicationUtils.php | 24 | ||||
-rw-r--r-- | application/Config.php | 221 | ||||
-rw-r--r-- | application/FileUtils.php | 8 | ||||
-rw-r--r-- | application/PageBuilder.php | 46 | ||||
-rw-r--r-- | application/PluginManager.php | 50 | ||||
-rw-r--r-- | application/Updater.php | 110 | ||||
-rw-r--r-- | application/config/ConfigIO.php | 33 | ||||
-rw-r--r-- | application/config/ConfigJson.php | 78 | ||||
-rw-r--r-- | application/config/ConfigManager.php | 392 | ||||
-rw-r--r-- | application/config/ConfigPhp.php | 132 | ||||
-rw-r--r-- | application/config/ConfigPlugin.php | 120 |
11 files changed, 904 insertions, 310 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 978fc9da..e67b2902 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php | |||
@@ -132,11 +132,11 @@ class ApplicationUtils | |||
132 | /** | 132 | /** |
133 | * Checks Shaarli has the proper access permissions to its resources | 133 | * Checks Shaarli has the proper access permissions to its resources |
134 | * | 134 | * |
135 | * @param array $globalConfig The $GLOBALS['config'] array | 135 | * @param ConfigManager $conf Configuration Manager instance. |
136 | * | 136 | * |
137 | * @return array A list of the detected configuration issues | 137 | * @return array A list of the detected configuration issues |
138 | */ | 138 | */ |
139 | public static function checkResourcePermissions($globalConfig) | 139 | public static function checkResourcePermissions($conf) |
140 | { | 140 | { |
141 | $errors = array(); | 141 | $errors = array(); |
142 | 142 | ||
@@ -145,7 +145,7 @@ class ApplicationUtils | |||
145 | 'application', | 145 | 'application', |
146 | 'inc', | 146 | 'inc', |
147 | 'plugins', | 147 | 'plugins', |
148 | $globalConfig['RAINTPL_TPL'] | 148 | $conf->get('resource.raintpl_tpl'), |
149 | ) as $path) { | 149 | ) as $path) { |
150 | if (! is_readable(realpath($path))) { | 150 | if (! is_readable(realpath($path))) { |
151 | $errors[] = '"'.$path.'" directory is not readable'; | 151 | $errors[] = '"'.$path.'" directory is not readable'; |
@@ -154,10 +154,10 @@ class ApplicationUtils | |||
154 | 154 | ||
155 | // Check cache and data directories are readable and writeable | 155 | // Check cache and data directories are readable and writeable |
156 | foreach (array( | 156 | foreach (array( |
157 | $globalConfig['CACHEDIR'], | 157 | $conf->get('resource.thumbnails_cache'), |
158 | $globalConfig['DATADIR'], | 158 | $conf->get('resource.data_dir'), |
159 | $globalConfig['PAGECACHE'], | 159 | $conf->get('resource.page_cache'), |
160 | $globalConfig['RAINTPL_TMP'] | 160 | $conf->get('resource.raintpl_tmp'), |
161 | ) as $path) { | 161 | ) as $path) { |
162 | if (! is_readable(realpath($path))) { | 162 | if (! is_readable(realpath($path))) { |
163 | $errors[] = '"'.$path.'" directory is not readable'; | 163 | $errors[] = '"'.$path.'" directory is not readable'; |
@@ -169,11 +169,11 @@ class ApplicationUtils | |||
169 | 169 | ||
170 | // Check configuration files are readable and writeable | 170 | // Check configuration files are readable and writeable |
171 | foreach (array( | 171 | foreach (array( |
172 | $globalConfig['CONFIG_FILE'], | 172 | $conf->getConfigFileExt(), |
173 | $globalConfig['DATASTORE'], | 173 | $conf->get('resource.datastore'), |
174 | $globalConfig['IPBANS_FILENAME'], | 174 | $conf->get('resource.ban_file'), |
175 | $globalConfig['LOG_FILE'], | 175 | $conf->get('resource.log'), |
176 | $globalConfig['UPDATECHECK_FILENAME'] | 176 | $conf->get('resource.update_check'), |
177 | ) as $path) { | 177 | ) as $path) { |
178 | if (! is_file(realpath($path))) { | 178 | if (! is_file(realpath($path))) { |
179 | # the file may not exist yet | 179 | # the file may not exist yet |
diff --git a/application/Config.php b/application/Config.php deleted file mode 100644 index 05a59452..00000000 --- a/application/Config.php +++ /dev/null | |||
@@ -1,221 +0,0 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Functions related to configuration management. | ||
4 | */ | ||
5 | |||
6 | /** | ||
7 | * Re-write configuration file according to given array. | ||
8 | * Requires mandatory fields listed in $MANDATORY_FIELDS. | ||
9 | * | ||
10 | * @param array $config contains all configuration fields. | ||
11 | * @param bool $isLoggedIn true if user is logged in. | ||
12 | * | ||
13 | * @return void | ||
14 | * | ||
15 | * @throws MissingFieldConfigException: a mandatory field has not been provided in $config. | ||
16 | * @throws UnauthorizedConfigException: user is not authorize to change configuration. | ||
17 | * @throws Exception: an error occured while writing the new config file. | ||
18 | */ | ||
19 | function writeConfig($config, $isLoggedIn) | ||
20 | { | ||
21 | // These fields are required in configuration. | ||
22 | $MANDATORY_FIELDS = array( | ||
23 | 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink', | ||
24 | 'redirector', 'disablesessionprotection', 'privateLinkByDefault' | ||
25 | ); | ||
26 | |||
27 | if (!isset($config['config']['CONFIG_FILE'])) { | ||
28 | throw new MissingFieldConfigException('CONFIG_FILE'); | ||
29 | } | ||
30 | |||
31 | // Only logged in user can alter config. | ||
32 | if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) { | ||
33 | throw new UnauthorizedConfigException(); | ||
34 | } | ||
35 | |||
36 | // Check that all mandatory fields are provided in $config. | ||
37 | foreach ($MANDATORY_FIELDS as $field) { | ||
38 | if (!isset($config[$field])) { | ||
39 | throw new MissingFieldConfigException($field); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | $configStr = '<?php '. PHP_EOL; | ||
44 | $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL; | ||
45 | $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL; | ||
46 | $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL; | ||
47 | $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL; | ||
48 | $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL; | ||
49 | $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL; | ||
50 | $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL; | ||
51 | $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL; | ||
52 | $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL; | ||
53 | $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL; | ||
54 | |||
55 | // Store all $config['config'] | ||
56 | foreach ($config['config'] as $key => $value) { | ||
57 | $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL; | ||
58 | } | ||
59 | |||
60 | if (isset($config['plugins'])) { | ||
61 | foreach ($config['plugins'] as $key => $value) { | ||
62 | $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr) | ||
67 | || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0 | ||
68 | ) { | ||
69 | throw new Exception( | ||
70 | 'Shaarli could not create the config file. | ||
71 | Please make sure Shaarli has the right to write in the folder is it installed in.' | ||
72 | ); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Process plugin administration form data and save it in an array. | ||
78 | * | ||
79 | * @param array $formData Data sent by the plugin admin form. | ||
80 | * | ||
81 | * @return array New list of enabled plugin, ordered. | ||
82 | * | ||
83 | * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid. | ||
84 | */ | ||
85 | function save_plugin_config($formData) | ||
86 | { | ||
87 | // Make sure there are no duplicates in orders. | ||
88 | if (!validate_plugin_order($formData)) { | ||
89 | throw new PluginConfigOrderException(); | ||
90 | } | ||
91 | |||
92 | $plugins = array(); | ||
93 | $newEnabledPlugins = array(); | ||
94 | foreach ($formData as $key => $data) { | ||
95 | if (startsWith($key, 'order')) { | ||
96 | continue; | ||
97 | } | ||
98 | |||
99 | // If there is no order, it means a disabled plugin has been enabled. | ||
100 | if (isset($formData['order_' . $key])) { | ||
101 | $plugins[(int) $formData['order_' . $key]] = $key; | ||
102 | } | ||
103 | else { | ||
104 | $newEnabledPlugins[] = $key; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | // New enabled plugins will be added at the end of order. | ||
109 | $plugins = array_merge($plugins, $newEnabledPlugins); | ||
110 | |||
111 | // Sort plugins by order. | ||
112 | if (!ksort($plugins)) { | ||
113 | throw new PluginConfigOrderException(); | ||
114 | } | ||
115 | |||
116 | $finalPlugins = array(); | ||
117 | // Make plugins order continuous. | ||
118 | foreach ($plugins as $plugin) { | ||
119 | $finalPlugins[] = $plugin; | ||
120 | } | ||
121 | |||
122 | return $finalPlugins; | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Validate plugin array submitted. | ||
127 | * Will fail if there is duplicate orders value. | ||
128 | * | ||
129 | * @param array $formData Data from submitted form. | ||
130 | * | ||
131 | * @return bool true if ok, false otherwise. | ||
132 | */ | ||
133 | function validate_plugin_order($formData) | ||
134 | { | ||
135 | $orders = array(); | ||
136 | foreach ($formData as $key => $value) { | ||
137 | // No duplicate order allowed. | ||
138 | if (in_array($value, $orders)) { | ||
139 | return false; | ||
140 | } | ||
141 | |||
142 | if (startsWith($key, 'order')) { | ||
143 | $orders[] = $value; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | return true; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Affect plugin parameters values into plugins array. | ||
152 | * | ||
153 | * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>. | ||
154 | * @param mixed $config Plugins configuration. | ||
155 | * | ||
156 | * @return mixed Updated $plugins array. | ||
157 | */ | ||
158 | function load_plugin_parameter_values($plugins, $config) | ||
159 | { | ||
160 | $out = $plugins; | ||
161 | foreach ($plugins as $name => $plugin) { | ||
162 | if (empty($plugin['parameters'])) { | ||
163 | continue; | ||
164 | } | ||
165 | |||
166 | foreach ($plugin['parameters'] as $key => $param) { | ||
167 | if (!empty($config[$key])) { | ||
168 | $out[$name]['parameters'][$key] = $config[$key]; | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | return $out; | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * Exception used if a mandatory field is missing in given configuration. | ||
178 | */ | ||
179 | class MissingFieldConfigException extends Exception | ||
180 | { | ||
181 | public $field; | ||
182 | |||
183 | /** | ||
184 | * Construct exception. | ||
185 | * | ||
186 | * @param string $field field name missing. | ||
187 | */ | ||
188 | public function __construct($field) | ||
189 | { | ||
190 | $this->field = $field; | ||
191 | $this->message = 'Configuration value is required for '. $this->field; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * Exception used if an unauthorized attempt to edit configuration has been made. | ||
197 | */ | ||
198 | class UnauthorizedConfigException extends Exception | ||
199 | { | ||
200 | /** | ||
201 | * Construct exception. | ||
202 | */ | ||
203 | public function __construct() | ||
204 | { | ||
205 | $this->message = 'You are not authorized to alter config.'; | ||
206 | } | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * Exception used if an error occur while saving plugin configuration. | ||
211 | */ | ||
212 | class PluginConfigOrderException extends Exception | ||
213 | { | ||
214 | /** | ||
215 | * Construct exception. | ||
216 | */ | ||
217 | public function __construct() | ||
218 | { | ||
219 | $this->message = 'An error occurred while trying to save plugins loading order.'; | ||
220 | } | ||
221 | } | ||
diff --git a/application/FileUtils.php b/application/FileUtils.php index 6a12ef0e..6cac9825 100644 --- a/application/FileUtils.php +++ b/application/FileUtils.php | |||
@@ -9,11 +9,13 @@ class IOException extends Exception | |||
9 | /** | 9 | /** |
10 | * Construct a new IOException | 10 | * Construct a new IOException |
11 | * | 11 | * |
12 | * @param string $path path to the ressource that cannot be accessed | 12 | * @param string $path path to the resource that cannot be accessed |
13 | * @param string $message Custom exception message. | ||
13 | */ | 14 | */ |
14 | public function __construct($path) | 15 | public function __construct($path, $message = '') |
15 | { | 16 | { |
16 | $this->path = $path; | 17 | $this->path = $path; |
17 | $this->message = 'Error accessing '.$this->path; | 18 | $this->message = empty($message) ? 'Error accessing' : $message; |
19 | $this->message .= PHP_EOL . $this->path; | ||
18 | } | 20 | } |
19 | } | 21 | } |
diff --git a/application/PageBuilder.php b/application/PageBuilder.php index 82580787..7cd88370 100644 --- a/application/PageBuilder.php +++ b/application/PageBuilder.php | |||
@@ -15,12 +15,20 @@ class PageBuilder | |||
15 | private $tpl; | 15 | private $tpl; |
16 | 16 | ||
17 | /** | 17 | /** |
18 | * @var ConfigManager $conf Configuration Manager instance. | ||
19 | */ | ||
20 | protected $conf; | ||
21 | |||
22 | /** | ||
18 | * PageBuilder constructor. | 23 | * PageBuilder constructor. |
19 | * $tpl is initialized at false for lazy loading. | 24 | * $tpl is initialized at false for lazy loading. |
25 | * | ||
26 | * @param ConfigManager $conf Configuration Manager instance (reference). | ||
20 | */ | 27 | */ |
21 | function __construct() | 28 | function __construct(&$conf) |
22 | { | 29 | { |
23 | $this->tpl = false; | 30 | $this->tpl = false; |
31 | $this->conf = $conf; | ||
24 | } | 32 | } |
25 | 33 | ||
26 | /** | 34 | /** |
@@ -33,17 +41,17 @@ class PageBuilder | |||
33 | try { | 41 | try { |
34 | $version = ApplicationUtils::checkUpdate( | 42 | $version = ApplicationUtils::checkUpdate( |
35 | shaarli_version, | 43 | shaarli_version, |
36 | $GLOBALS['config']['UPDATECHECK_FILENAME'], | 44 | $this->conf->get('resource.update_check'), |
37 | $GLOBALS['config']['UPDATECHECK_INTERVAL'], | 45 | $this->conf->get('updates.check_updates_interval'), |
38 | $GLOBALS['config']['ENABLE_UPDATECHECK'], | 46 | $this->conf->get('updates.check_updates'), |
39 | isLoggedIn(), | 47 | isLoggedIn(), |
40 | $GLOBALS['config']['UPDATECHECK_BRANCH'] | 48 | $this->conf->get('updates.check_updates_branch') |
41 | ); | 49 | ); |
42 | $this->tpl->assign('newVersion', escape($version)); | 50 | $this->tpl->assign('newVersion', escape($version)); |
43 | $this->tpl->assign('versionError', ''); | 51 | $this->tpl->assign('versionError', ''); |
44 | 52 | ||
45 | } catch (Exception $exc) { | 53 | } catch (Exception $exc) { |
46 | logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage()); | 54 | logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage()); |
47 | $this->tpl->assign('newVersion', ''); | 55 | $this->tpl->assign('newVersion', ''); |
48 | $this->tpl->assign('versionError', escape($exc->getMessage())); | 56 | $this->tpl->assign('versionError', escape($exc->getMessage())); |
49 | } | 57 | } |
@@ -62,19 +70,24 @@ class PageBuilder | |||
62 | $this->tpl->assign('scripturl', index_url($_SERVER)); | 70 | $this->tpl->assign('scripturl', index_url($_SERVER)); |
63 | $this->tpl->assign('pagetitle', 'Shaarli'); | 71 | $this->tpl->assign('pagetitle', 'Shaarli'); |
64 | $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? | 72 | $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? |
65 | if (!empty($GLOBALS['title'])) { | 73 | if ($this->conf->exists('general.title')) { |
66 | $this->tpl->assign('pagetitle', $GLOBALS['title']); | 74 | $this->tpl->assign('pagetitle', $this->conf->get('general.title')); |
67 | } | 75 | } |
68 | if (!empty($GLOBALS['titleLink'])) { | 76 | if ($this->conf->exists('general.header_link')) { |
69 | $this->tpl->assign('titleLink', $GLOBALS['titleLink']); | 77 | $this->tpl->assign('titleLink', $this->conf->get('general.header_link')); |
70 | } | 78 | } |
71 | if (!empty($GLOBALS['pagetitle'])) { | 79 | if ($this->conf->exists('pagetitle')) { |
72 | $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']); | 80 | $this->tpl->assign('pagetitle', $this->conf->get('pagetitle')); |
73 | } | 81 | } |
74 | $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']); | 82 | $this->tpl->assign('shaarlititle', $this->conf->get('title', 'Shaarli')); |
83 | $this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false)); | ||
84 | $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', false)); | ||
85 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); | ||
75 | if (!empty($GLOBALS['plugin_errors'])) { | 86 | if (!empty($GLOBALS['plugin_errors'])) { |
76 | $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']); | 87 | $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']); |
77 | } | 88 | } |
89 | // To be removed with a proper theme configuration. | ||
90 | $this->tpl->assign('conf', $this->conf); | ||
78 | } | 91 | } |
79 | 92 | ||
80 | /** | 93 | /** |
@@ -85,7 +98,6 @@ class PageBuilder | |||
85 | */ | 98 | */ |
86 | public function assign($placeholder, $value) | 99 | public function assign($placeholder, $value) |
87 | { | 100 | { |
88 | // Lazy initialization | ||
89 | if ($this->tpl === false) { | 101 | if ($this->tpl === false) { |
90 | $this->initialize(); | 102 | $this->initialize(); |
91 | } | 103 | } |
@@ -101,7 +113,6 @@ class PageBuilder | |||
101 | */ | 113 | */ |
102 | public function assignAll($data) | 114 | public function assignAll($data) |
103 | { | 115 | { |
104 | // Lazy initialization | ||
105 | if ($this->tpl === false) { | 116 | if ($this->tpl === false) { |
106 | $this->initialize(); | 117 | $this->initialize(); |
107 | } | 118 | } |
@@ -113,6 +124,7 @@ class PageBuilder | |||
113 | foreach ($data as $key => $value) { | 124 | foreach ($data as $key => $value) { |
114 | $this->assign($key, $value); | 125 | $this->assign($key, $value); |
115 | } | 126 | } |
127 | return true; | ||
116 | } | 128 | } |
117 | 129 | ||
118 | /** | 130 | /** |
@@ -123,10 +135,10 @@ class PageBuilder | |||
123 | */ | 135 | */ |
124 | public function renderPage($page) | 136 | public function renderPage($page) |
125 | { | 137 | { |
126 | // Lazy initialization | 138 | if ($this->tpl === false) { |
127 | if ($this->tpl===false) { | ||
128 | $this->initialize(); | 139 | $this->initialize(); |
129 | } | 140 | } |
141 | |||
130 | $this->tpl->draw($page); | 142 | $this->tpl->draw($page); |
131 | } | 143 | } |
132 | 144 | ||
diff --git a/application/PluginManager.php b/application/PluginManager.php index 787ac6a9..dca7e63e 100644 --- a/application/PluginManager.php +++ b/application/PluginManager.php | |||
@@ -4,18 +4,10 @@ | |||
4 | * Class PluginManager | 4 | * Class PluginManager |
5 | * | 5 | * |
6 | * Use to manage, load and execute plugins. | 6 | * Use to manage, load and execute plugins. |
7 | * | ||
8 | * Using Singleton design pattern. | ||
9 | */ | 7 | */ |
10 | class PluginManager | 8 | class PluginManager |
11 | { | 9 | { |
12 | /** | 10 | /** |
13 | * PluginManager singleton instance. | ||
14 | * @var PluginManager $instance | ||
15 | */ | ||
16 | private static $instance; | ||
17 | |||
18 | /** | ||
19 | * List of authorized plugins from configuration file. | 11 | * List of authorized plugins from configuration file. |
20 | * @var array $authorizedPlugins | 12 | * @var array $authorizedPlugins |
21 | */ | 13 | */ |
@@ -28,6 +20,11 @@ class PluginManager | |||
28 | private $loadedPlugins = array(); | 20 | private $loadedPlugins = array(); |
29 | 21 | ||
30 | /** | 22 | /** |
23 | * @var ConfigManager Configuration Manager instance. | ||
24 | */ | ||
25 | protected $conf; | ||
26 | |||
27 | /** | ||
31 | * Plugins subdirectory. | 28 | * Plugins subdirectory. |
32 | * @var string $PLUGINS_PATH | 29 | * @var string $PLUGINS_PATH |
33 | */ | 30 | */ |
@@ -40,33 +37,13 @@ class PluginManager | |||
40 | public static $META_EXT = 'meta'; | 37 | public static $META_EXT = 'meta'; |
41 | 38 | ||
42 | /** | 39 | /** |
43 | * Private constructor: new instances not allowed. | 40 | * Constructor. |
44 | */ | ||
45 | private function __construct() | ||
46 | { | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * Cloning isn't allowed either. | ||
51 | * | ||
52 | * @return void | ||
53 | */ | ||
54 | private function __clone() | ||
55 | { | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Return existing instance of PluginManager, or create it. | ||
60 | * | 41 | * |
61 | * @return PluginManager instance. | 42 | * @param ConfigManager $conf Configuration Manager instance. |
62 | */ | 43 | */ |
63 | public static function getInstance() | 44 | public function __construct(&$conf) |
64 | { | 45 | { |
65 | if (!(self::$instance instanceof self)) { | 46 | $this->conf = $conf; |
66 | self::$instance = new self(); | ||
67 | } | ||
68 | |||
69 | return self::$instance; | ||
70 | } | 47 | } |
71 | 48 | ||
72 | /** | 49 | /** |
@@ -102,9 +79,9 @@ class PluginManager | |||
102 | /** | 79 | /** |
103 | * Execute all plugins registered hook. | 80 | * Execute all plugins registered hook. |
104 | * | 81 | * |
105 | * @param string $hook name of the hook to trigger. | 82 | * @param string $hook name of the hook to trigger. |
106 | * @param array $data list of data to manipulate passed by reference. | 83 | * @param array $data list of data to manipulate passed by reference. |
107 | * @param array $params additional parameters such as page target. | 84 | * @param array $params additional parameters such as page target. |
108 | * | 85 | * |
109 | * @return void | 86 | * @return void |
110 | */ | 87 | */ |
@@ -122,7 +99,7 @@ class PluginManager | |||
122 | $hookFunction = $this->buildHookName($hook, $plugin); | 99 | $hookFunction = $this->buildHookName($hook, $plugin); |
123 | 100 | ||
124 | if (function_exists($hookFunction)) { | 101 | if (function_exists($hookFunction)) { |
125 | $data = call_user_func($hookFunction, $data); | 102 | $data = call_user_func($hookFunction, $data, $this->conf); |
126 | } | 103 | } |
127 | } | 104 | } |
128 | } | 105 | } |
@@ -148,6 +125,7 @@ class PluginManager | |||
148 | throw new PluginFileNotFoundException($pluginName); | 125 | throw new PluginFileNotFoundException($pluginName); |
149 | } | 126 | } |
150 | 127 | ||
128 | $conf = $this->conf; | ||
151 | include_once $pluginFilePath; | 129 | include_once $pluginFilePath; |
152 | 130 | ||
153 | $this->loadedPlugins[] = $pluginName; | 131 | $this->loadedPlugins[] = $pluginName; |
diff --git a/application/Updater.php b/application/Updater.php index 58c13c07..fd45d17f 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -13,14 +13,14 @@ class Updater | |||
13 | protected $doneUpdates; | 13 | protected $doneUpdates; |
14 | 14 | ||
15 | /** | 15 | /** |
16 | * @var array Shaarli's configuration array. | 16 | * @var LinkDB instance. |
17 | */ | 17 | */ |
18 | protected $config; | 18 | protected $linkDB; |
19 | 19 | ||
20 | /** | 20 | /** |
21 | * @var LinkDB instance. | 21 | * @var ConfigManager $conf Configuration Manager instance. |
22 | */ | 22 | */ |
23 | protected $linkDB; | 23 | protected $conf; |
24 | 24 | ||
25 | /** | 25 | /** |
26 | * @var bool True if the user is logged in, false otherwise. | 26 | * @var bool True if the user is logged in, false otherwise. |
@@ -35,16 +35,16 @@ class Updater | |||
35 | /** | 35 | /** |
36 | * Object constructor. | 36 | * Object constructor. |
37 | * | 37 | * |
38 | * @param array $doneUpdates Updates which are already done. | 38 | * @param array $doneUpdates Updates which are already done. |
39 | * @param array $config Shaarli's configuration array. | 39 | * @param LinkDB $linkDB LinkDB instance. |
40 | * @param LinkDB $linkDB LinkDB instance. | 40 | * @oaram ConfigManager $conf Configuration Manager instance. |
41 | * @param boolean $isLoggedIn True if the user is logged in. | 41 | * @param boolean $isLoggedIn True if the user is logged in. |
42 | */ | 42 | */ |
43 | public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn) | 43 | public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn) |
44 | { | 44 | { |
45 | $this->doneUpdates = $doneUpdates; | 45 | $this->doneUpdates = $doneUpdates; |
46 | $this->config = $config; | ||
47 | $this->linkDB = $linkDB; | 46 | $this->linkDB = $linkDB; |
47 | $this->conf = $conf; | ||
48 | $this->isLoggedIn = $isLoggedIn; | 48 | $this->isLoggedIn = $isLoggedIn; |
49 | 49 | ||
50 | // Retrieve all update methods. | 50 | // Retrieve all update methods. |
@@ -114,19 +114,19 @@ class Updater | |||
114 | */ | 114 | */ |
115 | public function updateMethodMergeDeprecatedConfigFile() | 115 | public function updateMethodMergeDeprecatedConfigFile() |
116 | { | 116 | { |
117 | $config_file = $this->config['config']['CONFIG_FILE']; | 117 | if (is_file($this->conf->get('resource.data_dir') . '/options.php')) { |
118 | 118 | include $this->conf->get('resource.data_dir') . '/options.php'; | |
119 | if (is_file($this->config['config']['DATADIR'].'/options.php')) { | ||
120 | include $this->config['config']['DATADIR'].'/options.php'; | ||
121 | 119 | ||
122 | // Load GLOBALS into config | 120 | // Load GLOBALS into config |
121 | $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS); | ||
122 | $allowedKeys[] = 'config'; | ||
123 | foreach ($GLOBALS as $key => $value) { | 123 | foreach ($GLOBALS as $key => $value) { |
124 | $this->config[$key] = $value; | 124 | if (in_array($key, $allowedKeys)) { |
125 | $this->conf->set($key, $value); | ||
126 | } | ||
125 | } | 127 | } |
126 | $this->config['config']['CONFIG_FILE'] = $config_file; | 128 | $this->conf->write($this->isLoggedIn); |
127 | writeConfig($this->config, $this->isLoggedIn); | 129 | unlink($this->conf->get('resource.data_dir').'/options.php'); |
128 | |||
129 | unlink($this->config['config']['DATADIR'].'/options.php'); | ||
130 | } | 130 | } |
131 | 131 | ||
132 | return true; | 132 | return true; |
@@ -143,7 +143,76 @@ class Updater | |||
143 | $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); | 143 | $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); |
144 | $this->linkDB[$link['linkdate']] = $link; | 144 | $this->linkDB[$link['linkdate']] = $link; |
145 | } | 145 | } |
146 | $this->linkDB->savedb($this->config['config']['PAGECACHE']); | 146 | $this->linkDB->savedb($this->conf->get('resource.page_cache')); |
147 | return true; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Move old configuration in PHP to the new config system in JSON format. | ||
152 | * | ||
153 | * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'. | ||
154 | * It will also convert legacy setting keys to the new ones. | ||
155 | */ | ||
156 | public function updateMethodConfigToJson() | ||
157 | { | ||
158 | // JSON config already exists, nothing to do. | ||
159 | if ($this->conf->getConfigIO() instanceof ConfigJson) { | ||
160 | return true; | ||
161 | } | ||
162 | |||
163 | $configPhp = new ConfigPhp(); | ||
164 | $configJson = new ConfigJson(); | ||
165 | $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php'); | ||
166 | rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php'); | ||
167 | $this->conf->setConfigIO($configJson); | ||
168 | $this->conf->reload(); | ||
169 | |||
170 | $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING); | ||
171 | foreach (ConfigPhp::$ROOT_KEYS as $key) { | ||
172 | $this->conf->set($legacyMap[$key], $oldConfig[$key]); | ||
173 | } | ||
174 | |||
175 | // Set sub config keys (config and plugins) | ||
176 | $subConfig = array('config', 'plugins'); | ||
177 | foreach ($subConfig as $sub) { | ||
178 | foreach ($oldConfig[$sub] as $key => $value) { | ||
179 | if (isset($legacyMap[$sub .'.'. $key])) { | ||
180 | $configKey = $legacyMap[$sub .'.'. $key]; | ||
181 | } else { | ||
182 | $configKey = $sub .'.'. $key; | ||
183 | } | ||
184 | $this->conf->set($configKey, $value); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | try{ | ||
189 | $this->conf->write($this->isLoggedIn); | ||
190 | return true; | ||
191 | } catch (IOException $e) { | ||
192 | error_log($e->getMessage()); | ||
193 | return false; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | * Escape settings which have been manually escaped in every request in previous versions: | ||
199 | * - general.title | ||
200 | * - general.header_link | ||
201 | * - extras.redirector | ||
202 | * | ||
203 | * @return bool true if the update is successful, false otherwise. | ||
204 | */ | ||
205 | public function escapeUnescapedConfig() | ||
206 | { | ||
207 | try { | ||
208 | $this->conf->set('general.title', escape($this->conf->get('general.title'))); | ||
209 | $this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); | ||
210 | $this->conf->set('redirector.url', escape($this->conf->get('redirector.url'))); | ||
211 | $this->conf->write($this->isLoggedIn); | ||
212 | } catch (Exception $e) { | ||
213 | error_log($e->getMessage()); | ||
214 | return false; | ||
215 | } | ||
147 | return true; | 216 | return true; |
148 | } | 217 | } |
149 | } | 218 | } |
@@ -203,7 +272,6 @@ class UpdaterException extends Exception | |||
203 | } | 272 | } |
204 | } | 273 | } |
205 | 274 | ||
206 | |||
207 | /** | 275 | /** |
208 | * Read the updates file, and return already done updates. | 276 | * Read the updates file, and return already done updates. |
209 | * | 277 | * |
diff --git a/application/config/ConfigIO.php b/application/config/ConfigIO.php new file mode 100644 index 00000000..2b68fe6a --- /dev/null +++ b/application/config/ConfigIO.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Interface ConfigIO | ||
5 | * | ||
6 | * This describes how Config types should store their configuration. | ||
7 | */ | ||
8 | interface ConfigIO | ||
9 | { | ||
10 | /** | ||
11 | * Read configuration. | ||
12 | * | ||
13 | * @param string $filepath Config file absolute path. | ||
14 | * | ||
15 | * @return array All configuration in an array. | ||
16 | */ | ||
17 | function read($filepath); | ||
18 | |||
19 | /** | ||
20 | * Write configuration. | ||
21 | * | ||
22 | * @param string $filepath Config file absolute path. | ||
23 | * @param array $conf All configuration in an array. | ||
24 | */ | ||
25 | function write($filepath, $conf); | ||
26 | |||
27 | /** | ||
28 | * Get config file extension according to config type. | ||
29 | * | ||
30 | * @return string Config file extension. | ||
31 | */ | ||
32 | function getExtension(); | ||
33 | } | ||
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php new file mode 100644 index 00000000..d07fefee --- /dev/null +++ b/application/config/ConfigJson.php | |||
@@ -0,0 +1,78 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Class ConfigJson (ConfigIO implementation) | ||
5 | * | ||
6 | * Handle Shaarli's JSON configuration file. | ||
7 | */ | ||
8 | class ConfigJson implements ConfigIO | ||
9 | { | ||
10 | /** | ||
11 | * @inheritdoc | ||
12 | */ | ||
13 | function read($filepath) | ||
14 | { | ||
15 | if (! is_readable($filepath)) { | ||
16 | return array(); | ||
17 | } | ||
18 | $data = file_get_contents($filepath); | ||
19 | $data = str_replace(self::getPhpHeaders(), '', $data); | ||
20 | $data = str_replace(self::getPhpSuffix(), '', $data); | ||
21 | $data = json_decode($data, true); | ||
22 | if ($data === null) { | ||
23 | $error = json_last_error(); | ||
24 | throw new Exception('An error occured while parsing JSON file: error code #'. $error); | ||
25 | } | ||
26 | return $data; | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * @inheritdoc | ||
31 | */ | ||
32 | function write($filepath, $conf) | ||
33 | { | ||
34 | // JSON_PRETTY_PRINT is available from PHP 5.4. | ||
35 | $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; | ||
36 | $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix(); | ||
37 | if (!file_put_contents($filepath, $data)) { | ||
38 | throw new IOException( | ||
39 | $filepath, | ||
40 | 'Shaarli could not create the config file. | ||
41 | Please make sure Shaarli has the right to write in the folder is it installed in.' | ||
42 | ); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * @inheritdoc | ||
48 | */ | ||
49 | function getExtension() | ||
50 | { | ||
51 | return '.json.php'; | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * The JSON data is wrapped in a PHP file for security purpose. | ||
56 | * This way, even if the file is accessible, credentials and configuration won't be exposed. | ||
57 | * | ||
58 | * Note: this isn't a static field because concatenation isn't supported in field declaration before PHP 5.6. | ||
59 | * | ||
60 | * @return string PHP start tag and comment tag. | ||
61 | */ | ||
62 | public static function getPhpHeaders() | ||
63 | { | ||
64 | return '<?php /*'. PHP_EOL; | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Get PHP comment closing tags. | ||
69 | * | ||
70 | * Static method for consistency with getPhpHeaders. | ||
71 | * | ||
72 | * @return string PHP comment closing. | ||
73 | */ | ||
74 | public static function getPhpSuffix() | ||
75 | { | ||
76 | return PHP_EOL . '*/ ?>'; | ||
77 | } | ||
78 | } | ||
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php new file mode 100644 index 00000000..ff41772a --- /dev/null +++ b/application/config/ConfigManager.php | |||
@@ -0,0 +1,392 @@ | |||
1 | <?php | ||
2 | |||
3 | // FIXME! Namespaces... | ||
4 | require_once 'ConfigIO.php'; | ||
5 | require_once 'ConfigJson.php'; | ||
6 | require_once 'ConfigPhp.php'; | ||
7 | |||
8 | /** | ||
9 | * Class ConfigManager | ||
10 | * | ||
11 | * Manages all Shaarli's settings. | ||
12 | * See the documentation for more information on settings: | ||
13 | * - doc/Shaarli-configuration.html | ||
14 | * - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration | ||
15 | */ | ||
16 | class ConfigManager | ||
17 | { | ||
18 | /** | ||
19 | * @var string Flag telling a setting is not found. | ||
20 | */ | ||
21 | protected static $NOT_FOUND = 'NOT_FOUND'; | ||
22 | |||
23 | /** | ||
24 | * @var string Config folder. | ||
25 | */ | ||
26 | protected $configFile; | ||
27 | |||
28 | /** | ||
29 | * @var array Loaded config array. | ||
30 | */ | ||
31 | protected $loadedConfig; | ||
32 | |||
33 | /** | ||
34 | * @var ConfigIO implementation instance. | ||
35 | */ | ||
36 | protected $configIO; | ||
37 | |||
38 | /** | ||
39 | * Constructor. | ||
40 | */ | ||
41 | public function __construct($configFile = 'data/config') | ||
42 | { | ||
43 | $this->configFile = $configFile; | ||
44 | $this->initialize(); | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Reset the ConfigManager instance. | ||
49 | */ | ||
50 | public function reset() | ||
51 | { | ||
52 | $this->initialize(); | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Rebuild the loaded config array from config files. | ||
57 | */ | ||
58 | public function reload() | ||
59 | { | ||
60 | $this->load(); | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * Initialize the ConfigIO and loaded the conf. | ||
65 | */ | ||
66 | protected function initialize() | ||
67 | { | ||
68 | if (file_exists($this->configFile . '.php')) { | ||
69 | $this->configIO = new ConfigPhp(); | ||
70 | } else { | ||
71 | $this->configIO = new ConfigJson(); | ||
72 | } | ||
73 | $this->load(); | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Load configuration in the ConfigurationManager. | ||
78 | */ | ||
79 | protected function load() | ||
80 | { | ||
81 | $this->loadedConfig = $this->configIO->read($this->getConfigFileExt()); | ||
82 | $this->setDefaultValues(); | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Get a setting. | ||
87 | * | ||
88 | * Supports nested settings with dot separated keys. | ||
89 | * Eg. 'config.stuff.option' will find $conf[config][stuff][option], | ||
90 | * or in JSON: | ||
91 | * { "config": { "stuff": {"option": "mysetting" } } } } | ||
92 | * | ||
93 | * @param string $setting Asked setting, keys separated with dots. | ||
94 | * @param string $default Default value if not found. | ||
95 | * | ||
96 | * @return mixed Found setting, or the default value. | ||
97 | */ | ||
98 | public function get($setting, $default = '') | ||
99 | { | ||
100 | // During the ConfigIO transition, map legacy settings to the new ones. | ||
101 | if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) { | ||
102 | $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting]; | ||
103 | } | ||
104 | |||
105 | $settings = explode('.', $setting); | ||
106 | $value = self::getConfig($settings, $this->loadedConfig); | ||
107 | if ($value === self::$NOT_FOUND) { | ||
108 | return $default; | ||
109 | } | ||
110 | return $value; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Set a setting, and eventually write it. | ||
115 | * | ||
116 | * Supports nested settings with dot separated keys. | ||
117 | * | ||
118 | * @param string $setting Asked setting, keys separated with dots. | ||
119 | * @param string $value Value to set. | ||
120 | * @param bool $write Write the new setting in the config file, default false. | ||
121 | * @param bool $isLoggedIn User login state, default false. | ||
122 | * | ||
123 | * @throws Exception Invalid | ||
124 | */ | ||
125 | public function set($setting, $value, $write = false, $isLoggedIn = false) | ||
126 | { | ||
127 | if (empty($setting) || ! is_string($setting)) { | ||
128 | throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting)); | ||
129 | } | ||
130 | |||
131 | // During the ConfigIO transition, map legacy settings to the new ones. | ||
132 | if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) { | ||
133 | $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting]; | ||
134 | } | ||
135 | |||
136 | $settings = explode('.', $setting); | ||
137 | self::setConfig($settings, $value, $this->loadedConfig); | ||
138 | if ($write) { | ||
139 | $this->write($isLoggedIn); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Check if a settings exists. | ||
145 | * | ||
146 | * Supports nested settings with dot separated keys. | ||
147 | * | ||
148 | * @param string $setting Asked setting, keys separated with dots. | ||
149 | * | ||
150 | * @return bool true if the setting exists, false otherwise. | ||
151 | */ | ||
152 | public function exists($setting) | ||
153 | { | ||
154 | // During the ConfigIO transition, map legacy settings to the new ones. | ||
155 | if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) { | ||
156 | $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting]; | ||
157 | } | ||
158 | |||
159 | $settings = explode('.', $setting); | ||
160 | $value = self::getConfig($settings, $this->loadedConfig); | ||
161 | if ($value === self::$NOT_FOUND) { | ||
162 | return false; | ||
163 | } | ||
164 | return true; | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Call the config writer. | ||
169 | * | ||
170 | * @param bool $isLoggedIn User login state. | ||
171 | * | ||
172 | * @return bool True if the configuration has been successfully written, false otherwise. | ||
173 | * | ||
174 | * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf. | ||
175 | * @throws UnauthorizedConfigException: user is not authorize to change configuration. | ||
176 | * @throws IOException: an error occurred while writing the new config file. | ||
177 | */ | ||
178 | public function write($isLoggedIn) | ||
179 | { | ||
180 | // These fields are required in configuration. | ||
181 | $mandatoryFields = array( | ||
182 | 'credentials.login', | ||
183 | 'credentials.hash', | ||
184 | 'credentials.salt', | ||
185 | 'security.session_protection_disabled', | ||
186 | 'general.timezone', | ||
187 | 'general.title', | ||
188 | 'general.header_link', | ||
189 | 'privacy.default_private_links', | ||
190 | 'redirector.url', | ||
191 | ); | ||
192 | |||
193 | // Only logged in user can alter config. | ||
194 | if (is_file($this->getConfigFileExt()) && !$isLoggedIn) { | ||
195 | throw new UnauthorizedConfigException(); | ||
196 | } | ||
197 | |||
198 | // Check that all mandatory fields are provided in $conf. | ||
199 | foreach ($mandatoryFields as $field) { | ||
200 | if (! $this->exists($field)) { | ||
201 | throw new MissingFieldConfigException($field); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig); | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * Set the config file path (without extension). | ||
210 | * | ||
211 | * @param string $configFile File path. | ||
212 | */ | ||
213 | public function setConfigFile($configFile) | ||
214 | { | ||
215 | $this->configFile = $configFile; | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * Return the configuration file path (without extension). | ||
220 | * | ||
221 | * @return string Config path. | ||
222 | */ | ||
223 | public function getConfigFile() | ||
224 | { | ||
225 | return $this->configFile; | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * Get the configuration file path with its extension. | ||
230 | * | ||
231 | * @return string Config file path. | ||
232 | */ | ||
233 | public function getConfigFileExt() | ||
234 | { | ||
235 | return $this->configFile . $this->configIO->getExtension(); | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * Recursive function which find asked setting in the loaded config. | ||
240 | * | ||
241 | * @param array $settings Ordered array which contains keys to find. | ||
242 | * @param array $conf Loaded settings, then sub-array. | ||
243 | * | ||
244 | * @return mixed Found setting or NOT_FOUND flag. | ||
245 | */ | ||
246 | protected static function getConfig($settings, $conf) | ||
247 | { | ||
248 | if (!is_array($settings) || count($settings) == 0) { | ||
249 | return self::$NOT_FOUND; | ||
250 | } | ||
251 | |||
252 | $setting = array_shift($settings); | ||
253 | if (!isset($conf[$setting])) { | ||
254 | return self::$NOT_FOUND; | ||
255 | } | ||
256 | |||
257 | if (count($settings) > 0) { | ||
258 | return self::getConfig($settings, $conf[$setting]); | ||
259 | } | ||
260 | return $conf[$setting]; | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Recursive function which find asked setting in the loaded config. | ||
265 | * | ||
266 | * @param array $settings Ordered array which contains keys to find. | ||
267 | * @param mixed $value | ||
268 | * @param array $conf Loaded settings, then sub-array. | ||
269 | * | ||
270 | * @return mixed Found setting or NOT_FOUND flag. | ||
271 | */ | ||
272 | protected static function setConfig($settings, $value, &$conf) | ||
273 | { | ||
274 | if (!is_array($settings) || count($settings) == 0) { | ||
275 | return self::$NOT_FOUND; | ||
276 | } | ||
277 | |||
278 | $setting = array_shift($settings); | ||
279 | if (count($settings) > 0) { | ||
280 | return self::setConfig($settings, $value, $conf[$setting]); | ||
281 | } | ||
282 | $conf[$setting] = $value; | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * Set a bunch of default values allowing Shaarli to start without a config file. | ||
287 | */ | ||
288 | protected function setDefaultValues() | ||
289 | { | ||
290 | $this->setEmpty('resource.data_dir', 'data'); | ||
291 | $this->setEmpty('resource.config', 'data/config.php'); | ||
292 | $this->setEmpty('resource.datastore', 'data/datastore.php'); | ||
293 | $this->setEmpty('resource.ban_file', 'data/ipbans.php'); | ||
294 | $this->setEmpty('resource.updates', 'data/updates.txt'); | ||
295 | $this->setEmpty('resource.log', 'data/log.txt'); | ||
296 | $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); | ||
297 | $this->setEmpty('resource.raintpl_tpl', 'tpl/'); | ||
298 | $this->setEmpty('resource.raintpl_tmp', 'tmp/'); | ||
299 | $this->setEmpty('resource.thumbnails_cache', 'cache'); | ||
300 | $this->setEmpty('resource.page_cache', 'pagecache'); | ||
301 | |||
302 | $this->setEmpty('security.ban_after', 4); | ||
303 | $this->setEmpty('security.ban_duration', 1800); | ||
304 | $this->setEmpty('security.session_protection_disabled', false); | ||
305 | $this->setEmpty('security.open_shaarli', false); | ||
306 | |||
307 | $this->setEmpty('general.header_link', '?'); | ||
308 | $this->setEmpty('general.links_per_page', 20); | ||
309 | $this->setEmpty('general.enabled_plugins', array('qrcode')); | ||
310 | |||
311 | $this->setEmpty('updates.check_updates', false); | ||
312 | $this->setEmpty('updates.check_updates_branch', 'stable'); | ||
313 | $this->setEmpty('updates.check_updates_interval', 86400); | ||
314 | |||
315 | $this->setEmpty('feed.rss_permalinks', true); | ||
316 | $this->setEmpty('feed.show_atom', false); | ||
317 | |||
318 | $this->setEmpty('privacy.default_private_links', false); | ||
319 | $this->setEmpty('privacy.hide_public_links', false); | ||
320 | $this->setEmpty('privacy.hide_timestamps', false); | ||
321 | |||
322 | $this->setEmpty('thumbnail.enable_thumbnails', true); | ||
323 | $this->setEmpty('thumbnail.enable_localcache', true); | ||
324 | |||
325 | $this->setEmpty('redirector.url', ''); | ||
326 | $this->setEmpty('redirector.encode_url', true); | ||
327 | |||
328 | $this->setEmpty('plugins', array()); | ||
329 | } | ||
330 | |||
331 | /** | ||
332 | * Set only if the setting does not exists. | ||
333 | * | ||
334 | * @param string $key Setting key. | ||
335 | * @param mixed $value Setting value. | ||
336 | */ | ||
337 | public function setEmpty($key, $value) | ||
338 | { | ||
339 | if (! $this->exists($key)) { | ||
340 | $this->set($key, $value); | ||
341 | } | ||
342 | } | ||
343 | |||
344 | /** | ||
345 | * @return ConfigIO | ||
346 | */ | ||
347 | public function getConfigIO() | ||
348 | { | ||
349 | return $this->configIO; | ||
350 | } | ||
351 | |||
352 | /** | ||
353 | * @param ConfigIO $configIO | ||
354 | */ | ||
355 | public function setConfigIO($configIO) | ||
356 | { | ||
357 | $this->configIO = $configIO; | ||
358 | } | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * Exception used if a mandatory field is missing in given configuration. | ||
363 | */ | ||
364 | class MissingFieldConfigException extends Exception | ||
365 | { | ||
366 | public $field; | ||
367 | |||
368 | /** | ||
369 | * Construct exception. | ||
370 | * | ||
371 | * @param string $field field name missing. | ||
372 | */ | ||
373 | public function __construct($field) | ||
374 | { | ||
375 | $this->field = $field; | ||
376 | $this->message = 'Configuration value is required for '. $this->field; | ||
377 | } | ||
378 | } | ||
379 | |||
380 | /** | ||
381 | * Exception used if an unauthorized attempt to edit configuration has been made. | ||
382 | */ | ||
383 | class UnauthorizedConfigException extends Exception | ||
384 | { | ||
385 | /** | ||
386 | * Construct exception. | ||
387 | */ | ||
388 | public function __construct() | ||
389 | { | ||
390 | $this->message = 'You are not authorized to alter config.'; | ||
391 | } | ||
392 | } | ||
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php new file mode 100644 index 00000000..27187b66 --- /dev/null +++ b/application/config/ConfigPhp.php | |||
@@ -0,0 +1,132 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Class ConfigPhp (ConfigIO implementation) | ||
5 | * | ||
6 | * Handle Shaarli's legacy PHP configuration file. | ||
7 | * Note: this is only designed to support the transition to JSON configuration. | ||
8 | */ | ||
9 | class ConfigPhp implements ConfigIO | ||
10 | { | ||
11 | /** | ||
12 | * @var array List of config key without group. | ||
13 | */ | ||
14 | public static $ROOT_KEYS = array( | ||
15 | 'login', | ||
16 | 'hash', | ||
17 | 'salt', | ||
18 | 'timezone', | ||
19 | 'title', | ||
20 | 'titleLink', | ||
21 | 'redirector', | ||
22 | 'disablesessionprotection', | ||
23 | 'privateLinkByDefault', | ||
24 | ); | ||
25 | |||
26 | /** | ||
27 | * Map legacy config keys with the new ones. | ||
28 | * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>. | ||
29 | * The Updater will use this array to transform keys when switching to JSON. | ||
30 | * | ||
31 | * @var array current key => legacy key. | ||
32 | */ | ||
33 | public static $LEGACY_KEYS_MAPPING = array( | ||
34 | 'credentials.login' => 'login', | ||
35 | 'credentials.hash' => 'hash', | ||
36 | 'credentials.salt' => 'salt', | ||
37 | 'resource.data_dir' => 'config.DATADIR', | ||
38 | 'resource.config' => 'config.CONFIG_FILE', | ||
39 | 'resource.datastore' => 'config.DATASTORE', | ||
40 | 'resource.updates' => 'config.UPDATES_FILE', | ||
41 | 'resource.log' => 'config.LOG_FILE', | ||
42 | 'resource.update_check' => 'config.UPDATECHECK_FILENAME', | ||
43 | 'resource.raintpl_tpl' => 'config.RAINTPL_TPL', | ||
44 | 'resource.raintpl_tmp' => 'config.RAINTPL_TMP', | ||
45 | 'resource.thumbnails_cache' => 'config.CACHEDIR', | ||
46 | 'resource.page_cache' => 'config.PAGECACHE', | ||
47 | 'resource.ban_file' => 'config.IPBANS_FILENAME', | ||
48 | 'security.session_protection_disabled' => 'disablesessionprotection', | ||
49 | 'security.ban_after' => 'config.BAN_AFTER', | ||
50 | 'security.ban_duration' => 'config.BAN_DURATION', | ||
51 | 'general.title' => 'title', | ||
52 | 'general.timezone' => 'timezone', | ||
53 | 'general.header_link' => 'titleLink', | ||
54 | 'updates.check_updates' => 'config.ENABLE_UPDATECHECK', | ||
55 | 'updates.check_updates_branch' => 'config.UPDATECHECK_BRANCH', | ||
56 | 'updates.check_updates_interval' => 'config.UPDATECHECK_INTERVAL', | ||
57 | 'privacy.default_private_links' => 'privateLinkByDefault', | ||
58 | 'feed.rss_permalinks' => 'config.ENABLE_RSS_PERMALINKS', | ||
59 | 'general.links_per_page' => 'config.LINKS_PER_PAGE', | ||
60 | 'thumbnail.enable_thumbnails' => 'config.ENABLE_THUMBNAILS', | ||
61 | 'thumbnail.enable_localcache' => 'config.ENABLE_LOCALCACHE', | ||
62 | 'general.enabled_plugins' => 'config.ENABLED_PLUGINS', | ||
63 | 'redirector.url' => 'redirector', | ||
64 | 'redirector.encode_url' => 'config.REDIRECTOR_URLENCODE', | ||
65 | 'feed.show_atom' => 'config.SHOW_ATOM', | ||
66 | 'privacy.hide_public_links' => 'config.HIDE_PUBLIC_LINKS', | ||
67 | 'privacy.hide_timestamps' => 'config.HIDE_TIMESTAMPS', | ||
68 | 'security.open_shaarli' => 'config.OPEN_SHAARLI', | ||
69 | ); | ||
70 | |||
71 | /** | ||
72 | * @inheritdoc | ||
73 | */ | ||
74 | function read($filepath) | ||
75 | { | ||
76 | if (! file_exists($filepath) || ! is_readable($filepath)) { | ||
77 | return array(); | ||
78 | } | ||
79 | |||
80 | include $filepath; | ||
81 | |||
82 | $out = array(); | ||
83 | foreach (self::$ROOT_KEYS as $key) { | ||
84 | $out[$key] = $GLOBALS[$key]; | ||
85 | } | ||
86 | $out['config'] = $GLOBALS['config']; | ||
87 | $out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array(); | ||
88 | return $out; | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * @inheritdoc | ||
93 | */ | ||
94 | function write($filepath, $conf) | ||
95 | { | ||
96 | $configStr = '<?php '. PHP_EOL; | ||
97 | foreach (self::$ROOT_KEYS as $key) { | ||
98 | if (isset($conf[$key])) { | ||
99 | $configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | // Store all $conf['config'] | ||
104 | foreach ($conf['config'] as $key => $value) { | ||
105 | $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL; | ||
106 | } | ||
107 | |||
108 | if (isset($conf['plugins'])) { | ||
109 | foreach ($conf['plugins'] as $key => $value) { | ||
110 | $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL; | ||
111 | } | ||
112 | } | ||
113 | |||
114 | if (!file_put_contents($filepath, $configStr) | ||
115 | || strcmp(file_get_contents($filepath), $configStr) != 0 | ||
116 | ) { | ||
117 | throw new IOException( | ||
118 | $filepath, | ||
119 | 'Shaarli could not create the config file. | ||
120 | Please make sure Shaarli has the right to write in the folder is it installed in.' | ||
121 | ); | ||
122 | } | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * @inheritdoc | ||
127 | */ | ||
128 | function getExtension() | ||
129 | { | ||
130 | return '.php'; | ||
131 | } | ||
132 | } | ||
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php new file mode 100644 index 00000000..047d2b03 --- /dev/null +++ b/application/config/ConfigPlugin.php | |||
@@ -0,0 +1,120 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Plugin configuration helper functions. | ||
4 | * | ||
5 | * Note: no access to configuration files here. | ||
6 | */ | ||
7 | |||
8 | /** | ||
9 | * Process plugin administration form data and save it in an array. | ||
10 | * | ||
11 | * @param array $formData Data sent by the plugin admin form. | ||
12 | * | ||
13 | * @return array New list of enabled plugin, ordered. | ||
14 | * | ||
15 | * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid. | ||
16 | */ | ||
17 | function save_plugin_config($formData) | ||
18 | { | ||
19 | // Make sure there are no duplicates in orders. | ||
20 | if (!validate_plugin_order($formData)) { | ||
21 | throw new PluginConfigOrderException(); | ||
22 | } | ||
23 | |||
24 | $plugins = array(); | ||
25 | $newEnabledPlugins = array(); | ||
26 | foreach ($formData as $key => $data) { | ||
27 | if (startsWith($key, 'order')) { | ||
28 | continue; | ||
29 | } | ||
30 | |||
31 | // If there is no order, it means a disabled plugin has been enabled. | ||
32 | if (isset($formData['order_' . $key])) { | ||
33 | $plugins[(int) $formData['order_' . $key]] = $key; | ||
34 | } | ||
35 | else { | ||
36 | $newEnabledPlugins[] = $key; | ||
37 | } | ||
38 | } | ||
39 | |||
40 | // New enabled plugins will be added at the end of order. | ||
41 | $plugins = array_merge($plugins, $newEnabledPlugins); | ||
42 | |||
43 | // Sort plugins by order. | ||
44 | if (!ksort($plugins)) { | ||
45 | throw new PluginConfigOrderException(); | ||
46 | } | ||
47 | |||
48 | $finalPlugins = array(); | ||
49 | // Make plugins order continuous. | ||
50 | foreach ($plugins as $plugin) { | ||
51 | $finalPlugins[] = $plugin; | ||
52 | } | ||
53 | |||
54 | return $finalPlugins; | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * Validate plugin array submitted. | ||
59 | * Will fail if there is duplicate orders value. | ||
60 | * | ||
61 | * @param array $formData Data from submitted form. | ||
62 | * | ||
63 | * @return bool true if ok, false otherwise. | ||
64 | */ | ||
65 | function validate_plugin_order($formData) | ||
66 | { | ||
67 | $orders = array(); | ||
68 | foreach ($formData as $key => $value) { | ||
69 | // No duplicate order allowed. | ||
70 | if (in_array($value, $orders)) { | ||
71 | return false; | ||
72 | } | ||
73 | |||
74 | if (startsWith($key, 'order')) { | ||
75 | $orders[] = $value; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | return true; | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * Affect plugin parameters values into plugins array. | ||
84 | * | ||
85 | * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>. | ||
86 | * @param mixed $conf Plugins configuration. | ||
87 | * | ||
88 | * @return mixed Updated $plugins array. | ||
89 | */ | ||
90 | function load_plugin_parameter_values($plugins, $conf) | ||
91 | { | ||
92 | $out = $plugins; | ||
93 | foreach ($plugins as $name => $plugin) { | ||
94 | if (empty($plugin['parameters'])) { | ||
95 | continue; | ||
96 | } | ||
97 | |||
98 | foreach ($plugin['parameters'] as $key => $param) { | ||
99 | if (!empty($conf[$key])) { | ||
100 | $out[$name]['parameters'][$key] = $conf[$key]; | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | |||
105 | return $out; | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Exception used if an error occur while saving plugin configuration. | ||
110 | */ | ||
111 | class PluginConfigOrderException extends Exception | ||
112 | { | ||
113 | /** | ||
114 | * Construct exception. | ||
115 | */ | ||
116 | public function __construct() | ||
117 | { | ||
118 | $this->message = 'An error occurred while trying to save plugins loading order.'; | ||
119 | } | ||
120 | } | ||