]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/Updater.php
Remove remaining settings initialization in index.php
[github/shaarli/Shaarli.git] / application / Updater.php
1 <?php
2
3 /**
4 * Class Updater.
5 * Used to update stuff when a new Shaarli's version is reached.
6 * Update methods are ran only once, and the stored in a JSON file.
7 */
8 class Updater
9 {
10 /**
11 * @var array Updates which are already done.
12 */
13 protected $doneUpdates;
14
15 /**
16 * @var LinkDB instance.
17 */
18 protected $linkDB;
19
20 /**
21 * @var bool True if the user is logged in, false otherwise.
22 */
23 protected $isLoggedIn;
24
25 /**
26 * @var ReflectionMethod[] List of current class methods.
27 */
28 protected $methods;
29
30 /**
31 * Object constructor.
32 *
33 * @param array $doneUpdates Updates which are already done.
34 * @param LinkDB $linkDB LinkDB instance.
35 * @param boolean $isLoggedIn True if the user is logged in.
36 */
37 public function __construct($doneUpdates, $linkDB, $isLoggedIn)
38 {
39 $this->doneUpdates = $doneUpdates;
40 $this->linkDB = $linkDB;
41 $this->isLoggedIn = $isLoggedIn;
42
43 // Retrieve all update methods.
44 $class = new ReflectionClass($this);
45 $this->methods = $class->getMethods();
46 }
47
48 /**
49 * Run all new updates.
50 * Update methods have to start with 'updateMethod' and return true (on success).
51 *
52 * @return array An array containing ran updates.
53 *
54 * @throws UpdaterException If something went wrong.
55 */
56 public function update()
57 {
58 $updatesRan = array();
59
60 // If the user isn't logged in, exit without updating.
61 if ($this->isLoggedIn !== true) {
62 return $updatesRan;
63 }
64
65 if ($this->methods == null) {
66 throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
67 }
68
69 foreach ($this->methods as $method) {
70 // Not an update method or already done, pass.
71 if (! startsWith($method->getName(), 'updateMethod')
72 || in_array($method->getName(), $this->doneUpdates)
73 ) {
74 continue;
75 }
76
77 try {
78 $method->setAccessible(true);
79 $res = $method->invoke($this);
80 // Update method must return true to be considered processed.
81 if ($res === true) {
82 $updatesRan[] = $method->getName();
83 }
84 } catch (Exception $e) {
85 throw new UpdaterException($method, $e);
86 }
87 }
88
89 $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan);
90
91 return $updatesRan;
92 }
93
94 /**
95 * @return array Updates methods already processed.
96 */
97 public function getDoneUpdates()
98 {
99 return $this->doneUpdates;
100 }
101
102 /**
103 * Move deprecated options.php to config.php.
104 *
105 * Milestone 0.9 (old versioning) - shaarli/Shaarli#41:
106 * options.php is not supported anymore.
107 */
108 public function updateMethodMergeDeprecatedConfigFile()
109 {
110 $conf = ConfigManager::getInstance();
111
112 if (is_file($conf->get('path.data_dir') . '/options.php')) {
113 include $conf->get('path.data_dir') . '/options.php';
114
115 // Load GLOBALS into config
116 $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
117 $allowedKeys[] = 'config';
118 foreach ($GLOBALS as $key => $value) {
119 if (in_array($key, $allowedKeys)) {
120 $conf->set($key, $value);
121 }
122 }
123 $conf->write($this->isLoggedIn);
124 unlink($conf->get('path.data_dir').'/options.php');
125 }
126
127 return true;
128 }
129
130 /**
131 * Rename tags starting with a '-' to work with tag exclusion search.
132 */
133 public function updateMethodRenameDashTags()
134 {
135 $conf = ConfigManager::getInstance();
136 $linklist = $this->linkDB->filterSearch();
137 foreach ($linklist as $link) {
138 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
139 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
140 $this->linkDB[$link['linkdate']] = $link;
141 }
142 $this->linkDB->savedb($conf->get('path.page_cache'));
143 return true;
144 }
145
146 /**
147 * Move old configuration in PHP to the new config system in JSON format.
148 *
149 * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
150 * It will also convert legacy setting keys to the new ones.
151 */
152 public function updateMethodConfigToJson()
153 {
154 $conf = ConfigManager::getInstance();
155
156 // JSON config already exists, nothing to do.
157 if ($conf->getConfigIO() instanceof ConfigJson) {
158 return true;
159 }
160
161 $configPhp = new ConfigPhp();
162 $configJson = new ConfigJson();
163 $oldConfig = $configPhp->read($conf::$CONFIG_FILE . '.php');
164 rename($conf->getConfigFile(), $conf::$CONFIG_FILE . '.save.php');
165 $conf->setConfigIO($configJson);
166 $conf->reload();
167
168 $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
169 foreach (ConfigPhp::$ROOT_KEYS as $key) {
170 $conf->set($legacyMap[$key], $oldConfig[$key]);
171 }
172
173 // Set sub config keys (config and plugins)
174 $subConfig = array('config', 'plugins');
175 foreach ($subConfig as $sub) {
176 foreach ($oldConfig[$sub] as $key => $value) {
177 if (isset($legacyMap[$sub .'.'. $key])) {
178 $configKey = $legacyMap[$sub .'.'. $key];
179 } else {
180 $configKey = $sub .'.'. $key;
181 }
182 $conf->set($configKey, $value);
183 }
184 }
185
186 try{
187 $conf->write($this->isLoggedIn);
188 return true;
189 } catch (IOException $e) {
190 error_log($e->getMessage());
191 return false;
192 }
193 }
194
195 /**
196 * Escape settings which have been manually escaped in every request in previous versions:
197 * - general.title
198 * - general.header_link
199 * - extras.redirector
200 *
201 * @return bool true if the update is successful, false otherwise.
202 */
203 public function escapeUnescapedConfig()
204 {
205 $conf = ConfigManager::getInstance();
206 try {
207 $conf->set('general.title', escape($conf->get('general.title')));
208 $conf->set('general.header_link', escape($conf->get('general.header_link')));
209 $conf->set('extras.redirector', escape($conf->get('extras.redirector')));
210 $conf->write($this->isLoggedIn);
211 } catch (Exception $e) {
212 error_log($e->getMessage());
213 return false;
214 }
215 return true;
216 }
217 }
218
219 /**
220 * Class UpdaterException.
221 */
222 class UpdaterException extends Exception
223 {
224 /**
225 * @var string Method where the error occurred.
226 */
227 protected $method;
228
229 /**
230 * @var Exception The parent exception.
231 */
232 protected $previous;
233
234 /**
235 * Constructor.
236 *
237 * @param string $message Force the error message if set.
238 * @param string $method Method where the error occurred.
239 * @param Exception|bool $previous Parent exception.
240 */
241 public function __construct($message = '', $method = '', $previous = false)
242 {
243 $this->method = $method;
244 $this->previous = $previous;
245 $this->message = $this->buildMessage($message);
246 }
247
248 /**
249 * Build the exception error message.
250 *
251 * @param string $message Optional given error message.
252 *
253 * @return string The built error message.
254 */
255 private function buildMessage($message)
256 {
257 $out = '';
258 if (! empty($message)) {
259 $out .= $message . PHP_EOL;
260 }
261
262 if (! empty($this->method)) {
263 $out .= 'An error occurred while running the update '. $this->method . PHP_EOL;
264 }
265
266 if (! empty($this->previous)) {
267 $out .= ' '. $this->previous->getMessage();
268 }
269
270 return $out;
271 }
272 }
273
274 /**
275 * Read the updates file, and return already done updates.
276 *
277 * @param string $updatesFilepath Updates file path.
278 *
279 * @return array Already done update methods.
280 */
281 function read_updates_file($updatesFilepath)
282 {
283 if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
284 $content = file_get_contents($updatesFilepath);
285 if (! empty($content)) {
286 return explode(';', $content);
287 }
288 }
289 return array();
290 }
291
292 /**
293 * Write updates file.
294 *
295 * @param string $updatesFilepath Updates file path.
296 * @param array $updates Updates array to write.
297 *
298 * @throws Exception Couldn't write version number.
299 */
300 function write_updates_file($updatesFilepath, $updates)
301 {
302 if (empty($updatesFilepath)) {
303 throw new Exception('Updates file path is not set, can\'t write updates.');
304 }
305
306 $res = file_put_contents($updatesFilepath, implode(';', $updates));
307 if ($res === false) {
308 throw new Exception('Unable to write updates in '. $updatesFilepath . '.');
309 }
310 }