aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
Diffstat (limited to 'application')
-rw-r--r--application/Config.php27
-rw-r--r--application/LinkDB.php17
-rw-r--r--application/LinkFilter.php31
-rw-r--r--application/Updater.php243
4 files changed, 276 insertions, 42 deletions
diff --git a/application/Config.php b/application/Config.php
index 9af5a535..05a59452 100644
--- a/application/Config.php
+++ b/application/Config.php
@@ -174,33 +174,6 @@ function load_plugin_parameter_values($plugins, $config)
174} 174}
175 175
176/** 176/**
177 * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
178 * ==> if user is loggedIn, merge its content with config.php, then delete options.php.
179 *
180 * @param array $config contains all configuration fields.
181 * @param bool $isLoggedIn true if user is logged in.
182 *
183 * @return void
184 */
185function mergeDeprecatedConfig($config, $isLoggedIn)
186{
187 $config_file = $config['config']['CONFIG_FILE'];
188
189 if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {
190 include $config['config']['DATADIR'].'/options.php';
191
192 // Load GLOBALS into config
193 foreach ($GLOBALS as $key => $value) {
194 $config[$key] = $value;
195 }
196 $config['config']['CONFIG_FILE'] = $config_file;
197 writeConfig($config, $isLoggedIn);
198
199 unlink($config['config']['DATADIR'].'/options.php');
200 }
201}
202
203/**
204 * Exception used if a mandatory field is missing in given configuration. 177 * Exception used if a mandatory field is missing in given configuration.
205 */ 178 */
206class MissingFieldConfigException extends Exception 179class MissingFieldConfigException extends Exception
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 19ca6435..9f4d3e3c 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -260,15 +260,19 @@ You use the community supported version of the original Shaarli project, by Seba
260 } 260 }
261 } 261 }
262 262
263 // Keep the list of the mapping URLs-->linkdate up-to-date.
264 $this->_urls = array(); 263 $this->_urls = array();
265 foreach ($this->_links as $link) { 264 foreach ($this->_links as &$link) {
265 // Keep the list of the mapping URLs-->linkdate up-to-date.
266 $this->_urls[$link['url']] = $link['linkdate']; 266 $this->_urls[$link['url']] = $link['linkdate'];
267 }
268 267
269 // Escape links data 268 // Sanitize data fields.
270 foreach($this->_links as &$link) {
271 sanitizeLink($link); 269 sanitizeLink($link);
270
271 // Remove private tags if the user is not logged in.
272 if (! $this->_loggedIn) {
273 $link['tags'] = preg_replace('/(^| )\.[^($| )]+/', '', $link['tags']);
274 }
275
272 // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). 276 // Do not use the redirector for internal links (Shaarli note URL starting with a '?').
273 if (!empty($this->_redirector) && !startsWith($link['url'], '?')) { 277 if (!empty($this->_redirector) && !startsWith($link['url'], '?')) {
274 $link['real_url'] = $this->_redirector . urlencode($link['url']); 278 $link['real_url'] = $this->_redirector . urlencode($link['url']);
@@ -343,7 +347,7 @@ You use the community supported version of the original Shaarli project, by Seba
343 * 347 *
344 * @return array filtered links 348 * @return array filtered links
345 */ 349 */
346 public function filter($type, $request, $casesensitive = false, $privateonly = false) 350 public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
347 { 351 {
348 $linkFilter = new LinkFilter($this->_links); 352 $linkFilter = new LinkFilter($this->_links);
349 $requestFilter = is_array($request) ? implode(' ', $request) : $request; 353 $requestFilter = is_array($request) ? implode(' ', $request) : $request;
@@ -381,6 +385,7 @@ You use the community supported version of the original Shaarli project, by Seba
381 } 385 }
382 $linkDays = array_keys($linkDays); 386 $linkDays = array_keys($linkDays);
383 sort($linkDays); 387 sort($linkDays);
388
384 return $linkDays; 389 return $linkDays;
385 } 390 }
386} 391}
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index b2e6530f..ceb47d16 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -209,19 +209,33 @@ class LinkFilter
209 */ 209 */
210 public function filterTags($tags, $casesensitive = false, $privateonly = false) 210 public function filterTags($tags, $casesensitive = false, $privateonly = false)
211 { 211 {
212 $searchtags = $this->tagsStrToArray($tags, $casesensitive); 212 $searchtags = self::tagsStrToArray($tags, $casesensitive);
213 $filtered = array(); 213 $filtered = array();
214 if (empty($searchtags)) {
215 return $filtered;
216 }
214 217
215 foreach ($this->links as $l) { 218 foreach ($this->links as $link) {
216 // ignore non private links when 'privatonly' is on. 219 // ignore non private links when 'privatonly' is on.
217 if (! $l['private'] && $privateonly === true) { 220 if (! $link['private'] && $privateonly === true) {
218 continue; 221 continue;
219 } 222 }
220 223
221 $linktags = $this->tagsStrToArray($l['tags'], $casesensitive); 224 $linktags = self::tagsStrToArray($link['tags'], $casesensitive);
222 225
223 if (count(array_intersect($linktags, $searchtags)) == count($searchtags)) { 226 $found = true;
224 $filtered[$l['linkdate']] = $l; 227 for ($i = 0 ; $i < count($searchtags) && $found; $i++) {
228 // Exclusive search, quit if tag found.
229 // Or, tag not found in the link, quit.
230 if (($searchtags[$i][0] == '-' && in_array(substr($searchtags[$i], 1), $linktags))
231 || ($searchtags[$i][0] != '-') && ! in_array($searchtags[$i], $linktags)
232 ) {
233 $found = false;
234 }
235 }
236
237 if ($found) {
238 $filtered[$link['linkdate']] = $link;
225 } 239 }
226 } 240 }
227 krsort($filtered); 241 krsort($filtered);
@@ -260,19 +274,18 @@ class LinkFilter
260 * Convert a list of tags (str) to an array. Also 274 * Convert a list of tags (str) to an array. Also
261 * - handle case sensitivity. 275 * - handle case sensitivity.
262 * - accepts spaces commas as separator. 276 * - accepts spaces commas as separator.
263 * - remove private tags for loggedout users.
264 * 277 *
265 * @param string $tags string containing a list of tags. 278 * @param string $tags string containing a list of tags.
266 * @param bool $casesensitive will convert everything to lowercase if false. 279 * @param bool $casesensitive will convert everything to lowercase if false.
267 * 280 *
268 * @return array filtered tags string. 281 * @return array filtered tags string.
269 */ 282 */
270 public function tagsStrToArray($tags, $casesensitive) 283 public static function tagsStrToArray($tags, $casesensitive)
271 { 284 {
272 // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek) 285 // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
273 $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'); 286 $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
274 $tagsOut = str_replace(',', ' ', $tagsOut); 287 $tagsOut = str_replace(',', ' ', $tagsOut);
275 288
276 return explode(' ', trim($tagsOut)); 289 return array_filter(explode(' ', trim($tagsOut)), 'strlen');
277 } 290 }
278} 291}
diff --git a/application/Updater.php b/application/Updater.php
new file mode 100644
index 00000000..773a1ffa
--- /dev/null
+++ b/application/Updater.php
@@ -0,0 +1,243 @@
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 */
8class Updater
9{
10 /**
11 * @var array Updates which are already done.
12 */
13 protected $doneUpdates;
14
15 /**
16 * @var array Shaarli's configuration array.
17 */
18 protected $config;
19
20 /**
21 * @var LinkDB instance.
22 */
23 protected $linkDB;
24
25 /**
26 * @var bool True if the user is logged in, false otherwise.
27 */
28 protected $isLoggedIn;
29
30 /**
31 * @var ReflectionMethod[] List of current class methods.
32 */
33 protected $methods;
34
35 /**
36 * Object constructor.
37 *
38 * @param array $doneUpdates Updates which are already done.
39 * @param array $config Shaarli's configuration array.
40 * @param LinkDB $linkDB LinkDB instance.
41 * @param boolean $isLoggedIn True if the user is logged in.
42 */
43 public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn)
44 {
45 $this->doneUpdates = $doneUpdates;
46 $this->config = $config;
47 $this->linkDB = $linkDB;
48 $this->isLoggedIn = $isLoggedIn;
49
50 // Retrieve all update methods.
51 $class = new ReflectionClass($this);
52 $this->methods = $class->getMethods();
53 }
54
55 /**
56 * Run all new updates.
57 * Update methods have to start with 'updateMethod' and return true (on success).
58 *
59 * @return array An array containing ran updates.
60 *
61 * @throws UpdaterException If something went wrong.
62 */
63 public function update()
64 {
65 $updatesRan = array();
66
67 // If the user isn't logged in, exit without updating.
68 if ($this->isLoggedIn !== true) {
69 return $updatesRan;
70 }
71
72 if ($this->methods == null) {
73 throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
74 }
75
76 foreach ($this->methods as $method) {
77 // Not an update method or already done, pass.
78 if (! startsWith($method->getName(), 'updateMethod')
79 || in_array($method->getName(), $this->doneUpdates)
80 ) {
81 continue;
82 }
83
84 try {
85 $method->setAccessible(true);
86 $res = $method->invoke($this);
87 // Update method must return true to be considered processed.
88 if ($res === true) {
89 $updatesRan[] = $method->getName();
90 }
91 } catch (Exception $e) {
92 throw new UpdaterException($method, $e);
93 }
94 }
95
96 $this->doneUpdates = array_merge($this->doneUpdates, $updatesRan);
97
98 return $updatesRan;
99 }
100
101 /**
102 * @return array Updates methods already processed.
103 */
104 public function getDoneUpdates()
105 {
106 return $this->doneUpdates;
107 }
108
109 /**
110 * Move deprecated options.php to config.php.
111 *
112 * Milestone 0.9 (old versioning) - shaarli/Shaarli#41:
113 * options.php is not supported anymore.
114 */
115 public function updateMethodMergeDeprecatedConfigFile()
116 {
117 $config_file = $this->config['config']['CONFIG_FILE'];
118
119 if (is_file($this->config['config']['DATADIR'].'/options.php')) {
120 include $this->config['config']['DATADIR'].'/options.php';
121
122 // Load GLOBALS into config
123 foreach ($GLOBALS as $key => $value) {
124 $this->config[$key] = $value;
125 }
126 $this->config['config']['CONFIG_FILE'] = $config_file;
127 writeConfig($this->config, $this->isLoggedIn);
128
129 unlink($this->config['config']['DATADIR'].'/options.php');
130 }
131
132 return true;
133 }
134
135 /**
136 * Rename tags starting with a '-' to work with tag exclusion search.
137 */
138 public function updateMethodRenameDashTags()
139 {
140 $linklist = $this->linkDB->filter();
141 foreach ($linklist as $link) {
142 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
144 $this->linkDB[$link['linkdate']] = $link;
145 }
146 $this->linkDB->savedb($this->config['config']['PAGECACHE']);
147 return true;
148 }
149}
150
151/**
152 * Class UpdaterException.
153 */
154class UpdaterException extends Exception
155{
156 /**
157 * @var string Method where the error occurred.
158 */
159 protected $method;
160
161 /**
162 * @var Exception The parent exception.
163 */
164 protected $previous;
165
166 /**
167 * Constructor.
168 *
169 * @param string $message Force the error message if set.
170 * @param string $method Method where the error occurred.
171 * @param Exception|bool $previous Parent exception.
172 */
173 public function __construct($message = '', $method = '', $previous = false)
174 {
175 $this->method = $method;
176 $this->previous = $previous;
177 $this->message = $this->buildMessage($message);
178 }
179
180 /**
181 * Build the exception error message.
182 *
183 * @param string $message Optional given error message.
184 *
185 * @return string The built error message.
186 */
187 private function buildMessage($message)
188 {
189 $out = '';
190 if (! empty($message)) {
191 $out .= $message . PHP_EOL;
192 }
193
194 if (! empty($this->method)) {
195 $out .= 'An error occurred while running the update '. $this->method . PHP_EOL;
196 }
197
198 if (! empty($this->previous)) {
199 $out .= ' '. $this->previous->getMessage();
200 }
201
202 return $out;
203 }
204}
205
206
207/**
208 * Read the updates file, and return already done updates.
209 *
210 * @param string $updatesFilepath Updates file path.
211 *
212 * @return array Already done update methods.
213 */
214function read_updates_file($updatesFilepath)
215{
216 if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
217 $content = file_get_contents($updatesFilepath);
218 if (! empty($content)) {
219 return explode(';', $content);
220 }
221 }
222 return array();
223}
224
225/**
226 * Write updates file.
227 *
228 * @param string $updatesFilepath Updates file path.
229 * @param array $updates Updates array to write.
230 *
231 * @throws Exception Couldn't write version number.
232 */
233function write_updates_file($updatesFilepath, $updates)
234{
235 if (empty($updatesFilepath)) {
236 throw new Exception('Updates file path is not set, can\'t write updates.');
237 }
238
239 $res = file_put_contents($updatesFilepath, implode(';', $updates));
240 if ($res === false) {
241 throw new Exception('Unable to write updates in '. $updatesFilepath . '.');
242 }
243}