diff options
author | ArthurHoaro <arthur@hoa.ro> | 2017-05-07 19:17:33 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2017-05-07 19:17:33 +0200 |
commit | 01e942d44c7194607649817216aeb5d65c6acad6 (patch) | |
tree | 15777aa1005251f119e6dd680291147117766b5b /application/LinkDB.php | |
parent | bc22c9a0acb095970e9494cbe8954f0612e05dc0 (diff) | |
parent | 8868f3ca461011a8fb6dd9f90b60ed697ab52fc5 (diff) | |
download | Shaarli-01e942d44c7194607649817216aeb5d65c6acad6.tar.gz Shaarli-01e942d44c7194607649817216aeb5d65c6acad6.tar.zst Shaarli-01e942d44c7194607649817216aeb5d65c6acad6.zip |
Merge tag 'v0.8.4' into stable
Release v0.8.4
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r-- | application/LinkDB.php | 318 |
1 files changed, 211 insertions, 107 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php index 1cb70de0..1e13286a 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -6,14 +6,15 @@ | |||
6 | * | 6 | * |
7 | * Example: | 7 | * Example: |
8 | * $myLinks = new LinkDB(); | 8 | * $myLinks = new LinkDB(); |
9 | * echo $myLinks['20110826_161819']['title']; | 9 | * echo $myLinks[350]['title']; |
10 | * foreach ($myLinks as $link) | 10 | * foreach ($myLinks as $link) |
11 | * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; | 11 | * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; |
12 | * | 12 | * |
13 | * Available keys: | 13 | * Available keys: |
14 | * - id: primary key, incremental integer identifier (persistent) | ||
14 | * - description: description of the entry | 15 | * - description: description of the entry |
15 | * - linkdate: date of the creation of this entry, in the form YYYYMMDD_HHMMSS | 16 | * - created: creation date of this entry, DateTime object. |
16 | * (e.g.'20110914_192317') | 17 | * - updated: last modification date of this entry, DateTime object. |
17 | * - private: Is this link private? 0=no, other value=yes | 18 | * - private: Is this link private? 0=no, other value=yes |
18 | * - tags: tags attached to this entry (separated by spaces) | 19 | * - tags: tags attached to this entry (separated by spaces) |
19 | * - title Title of the link | 20 | * - title Title of the link |
@@ -21,16 +22,30 @@ | |||
21 | * Can be absolute or relative. | 22 | * Can be absolute or relative. |
22 | * Relative URLs are permalinks (e.g.'?m-ukcw') | 23 | * Relative URLs are permalinks (e.g.'?m-ukcw') |
23 | * - real_url Absolute processed URL. | 24 | * - real_url Absolute processed URL. |
25 | * - shorturl Permalink smallhash | ||
24 | * | 26 | * |
25 | * Implements 3 interfaces: | 27 | * Implements 3 interfaces: |
26 | * - ArrayAccess: behaves like an associative array; | 28 | * - ArrayAccess: behaves like an associative array; |
27 | * - Countable: there is a count() method; | 29 | * - Countable: there is a count() method; |
28 | * - Iterator: usable in foreach () loops. | 30 | * - Iterator: usable in foreach () loops. |
31 | * | ||
32 | * ID mechanism: | ||
33 | * ArrayAccess is implemented in a way that will allow to access a link | ||
34 | * with the unique identifier ID directly with $link[ID]. | ||
35 | * Note that it's not the real key of the link array attribute. | ||
36 | * This mechanism is in place to have persistent link IDs, | ||
37 | * even though the internal array is reordered by date. | ||
38 | * Example: | ||
39 | * - DB: link #1 (2010-01-01) link #2 (2016-01-01) | ||
40 | * - Order: #2 #1 | ||
41 | * - Import links containing: link #3 (2013-01-01) | ||
42 | * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01) | ||
43 | * - Real order: #2 #3 #1 | ||
29 | */ | 44 | */ |
30 | class LinkDB implements Iterator, Countable, ArrayAccess | 45 | class LinkDB implements Iterator, Countable, ArrayAccess |
31 | { | 46 | { |
32 | // Links are stored as a PHP serialized string | 47 | // Links are stored as a PHP serialized string |
33 | private $_datastore; | 48 | private $datastore; |
34 | 49 | ||
35 | // Link date storage format | 50 | // Link date storage format |
36 | const LINK_DATE_FORMAT = 'Ymd_His'; | 51 | const LINK_DATE_FORMAT = 'Ymd_His'; |
@@ -44,26 +59,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
44 | // List of links (associative array) | 59 | // List of links (associative array) |
45 | // - key: link date (e.g. "20110823_124546"), | 60 | // - key: link date (e.g. "20110823_124546"), |
46 | // - value: associative array (keys: title, description...) | 61 | // - value: associative array (keys: title, description...) |
47 | private $_links; | 62 | private $links; |
63 | |||
64 | // List of all recorded URLs (key=url, value=link offset) | ||
65 | // for fast reserve search (url-->link offset) | ||
66 | private $urls; | ||
48 | 67 | ||
49 | // List of all recorded URLs (key=url, value=linkdate) | 68 | /** |
50 | // for fast reserve search (url-->linkdate) | 69 | * @var array List of all links IDS mapped with their array offset. |
51 | private $_urls; | 70 | * Map: id->offset. |
71 | */ | ||
72 | protected $ids; | ||
52 | 73 | ||
53 | // List of linkdate keys (for the Iterator interface implementation) | 74 | // List of offset keys (for the Iterator interface implementation) |
54 | private $_keys; | 75 | private $keys; |
55 | 76 | ||
56 | // Position in the $this->_keys array (for the Iterator interface) | 77 | // Position in the $this->keys array (for the Iterator interface) |
57 | private $_position; | 78 | private $position; |
58 | 79 | ||
59 | // Is the user logged in? (used to filter private links) | 80 | // Is the user logged in? (used to filter private links) |
60 | private $_loggedIn; | 81 | private $loggedIn; |
61 | 82 | ||
62 | // Hide public links | 83 | // Hide public links |
63 | private $_hidePublicLinks; | 84 | private $hidePublicLinks; |
64 | 85 | ||
65 | // link redirector set in user settings. | 86 | // link redirector set in user settings. |
66 | private $_redirector; | 87 | private $redirector; |
67 | 88 | ||
68 | /** | 89 | /** |
69 | * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched. | 90 | * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched. |
@@ -86,7 +107,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
86 | * @param string $redirector link redirector set in user settings. | 107 | * @param string $redirector link redirector set in user settings. |
87 | * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true). | 108 | * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true). |
88 | */ | 109 | */ |
89 | function __construct( | 110 | public function __construct( |
90 | $datastore, | 111 | $datastore, |
91 | $isLoggedIn, | 112 | $isLoggedIn, |
92 | $hidePublicLinks, | 113 | $hidePublicLinks, |
@@ -94,13 +115,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
94 | $redirectorEncode = true | 115 | $redirectorEncode = true |
95 | ) | 116 | ) |
96 | { | 117 | { |
97 | $this->_datastore = $datastore; | 118 | $this->datastore = $datastore; |
98 | $this->_loggedIn = $isLoggedIn; | 119 | $this->loggedIn = $isLoggedIn; |
99 | $this->_hidePublicLinks = $hidePublicLinks; | 120 | $this->hidePublicLinks = $hidePublicLinks; |
100 | $this->_redirector = $redirector; | 121 | $this->redirector = $redirector; |
101 | $this->redirectorEncode = $redirectorEncode === true; | 122 | $this->redirectorEncode = $redirectorEncode === true; |
102 | $this->_checkDB(); | 123 | $this->check(); |
103 | $this->_readDB(); | 124 | $this->read(); |
104 | } | 125 | } |
105 | 126 | ||
106 | /** | 127 | /** |
@@ -108,7 +129,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
108 | */ | 129 | */ |
109 | public function count() | 130 | public function count() |
110 | { | 131 | { |
111 | return count($this->_links); | 132 | return count($this->links); |
112 | } | 133 | } |
113 | 134 | ||
114 | /** | 135 | /** |
@@ -117,17 +138,29 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
117 | public function offsetSet($offset, $value) | 138 | public function offsetSet($offset, $value) |
118 | { | 139 | { |
119 | // TODO: use exceptions instead of "die" | 140 | // TODO: use exceptions instead of "die" |
120 | if (!$this->_loggedIn) { | 141 | if (!$this->loggedIn) { |
121 | die('You are not authorized to add a link.'); | 142 | die('You are not authorized to add a link.'); |
122 | } | 143 | } |
123 | if (empty($value['linkdate']) || empty($value['url'])) { | 144 | if (!isset($value['id']) || empty($value['url'])) { |
124 | die('Internal Error: A link should always have a linkdate and URL.'); | 145 | die('Internal Error: A link should always have an id and URL.'); |
146 | } | ||
147 | if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { | ||
148 | die('You must specify an integer as a key.'); | ||
125 | } | 149 | } |
126 | if (empty($offset)) { | 150 | if (! empty($offset) && $offset !== $value['id']) { |
127 | die('You must specify a key.'); | 151 | die('Array offset and link ID must be equal.'); |
128 | } | 152 | } |
129 | $this->_links[$offset] = $value; | 153 | |
130 | $this->_urls[$value['url']]=$offset; | 154 | // If the link exists, we reuse the real offset, otherwise new entry |
155 | $existing = $this->getLinkOffset($offset); | ||
156 | if ($existing !== null) { | ||
157 | $offset = $existing; | ||
158 | } else { | ||
159 | $offset = count($this->links); | ||
160 | } | ||
161 | $this->links[$offset] = $value; | ||
162 | $this->urls[$value['url']] = $offset; | ||
163 | $this->ids[$value['id']] = $offset; | ||
131 | } | 164 | } |
132 | 165 | ||
133 | /** | 166 | /** |
@@ -135,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
135 | */ | 168 | */ |
136 | public function offsetExists($offset) | 169 | public function offsetExists($offset) |
137 | { | 170 | { |
138 | return array_key_exists($offset, $this->_links); | 171 | return array_key_exists($this->getLinkOffset($offset), $this->links); |
139 | } | 172 | } |
140 | 173 | ||
141 | /** | 174 | /** |
@@ -143,13 +176,15 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
143 | */ | 176 | */ |
144 | public function offsetUnset($offset) | 177 | public function offsetUnset($offset) |
145 | { | 178 | { |
146 | if (!$this->_loggedIn) { | 179 | if (!$this->loggedIn) { |
147 | // TODO: raise an exception | 180 | // TODO: raise an exception |
148 | die('You are not authorized to delete a link.'); | 181 | die('You are not authorized to delete a link.'); |
149 | } | 182 | } |
150 | $url = $this->_links[$offset]['url']; | 183 | $realOffset = $this->getLinkOffset($offset); |
151 | unset($this->_urls[$url]); | 184 | $url = $this->links[$realOffset]['url']; |
152 | unset($this->_links[$offset]); | 185 | unset($this->urls[$url]); |
186 | unset($this->ids[$realOffset]); | ||
187 | unset($this->links[$realOffset]); | ||
153 | } | 188 | } |
154 | 189 | ||
155 | /** | 190 | /** |
@@ -157,31 +192,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
157 | */ | 192 | */ |
158 | public function offsetGet($offset) | 193 | public function offsetGet($offset) |
159 | { | 194 | { |
160 | return isset($this->_links[$offset]) ? $this->_links[$offset] : null; | 195 | $realOffset = $this->getLinkOffset($offset); |
196 | return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null; | ||
161 | } | 197 | } |
162 | 198 | ||
163 | /** | 199 | /** |
164 | * Iterator - Returns the current element | 200 | * Iterator - Returns the current element |
165 | */ | 201 | */ |
166 | function current() | 202 | public function current() |
167 | { | 203 | { |
168 | return $this->_links[$this->_keys[$this->_position]]; | 204 | return $this[$this->keys[$this->position]]; |
169 | } | 205 | } |
170 | 206 | ||
171 | /** | 207 | /** |
172 | * Iterator - Returns the key of the current element | 208 | * Iterator - Returns the key of the current element |
173 | */ | 209 | */ |
174 | function key() | 210 | public function key() |
175 | { | 211 | { |
176 | return $this->_keys[$this->_position]; | 212 | return $this->keys[$this->position]; |
177 | } | 213 | } |
178 | 214 | ||
179 | /** | 215 | /** |
180 | * Iterator - Moves forward to next element | 216 | * Iterator - Moves forward to next element |
181 | */ | 217 | */ |
182 | function next() | 218 | public function next() |
183 | { | 219 | { |
184 | ++$this->_position; | 220 | ++$this->position; |
185 | } | 221 | } |
186 | 222 | ||
187 | /** | 223 | /** |
@@ -189,19 +225,18 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
189 | * | 225 | * |
190 | * Entries are sorted by date (latest first) | 226 | * Entries are sorted by date (latest first) |
191 | */ | 227 | */ |
192 | function rewind() | 228 | public function rewind() |
193 | { | 229 | { |
194 | $this->_keys = array_keys($this->_links); | 230 | $this->keys = array_keys($this->ids); |
195 | rsort($this->_keys); | 231 | $this->position = 0; |
196 | $this->_position = 0; | ||
197 | } | 232 | } |
198 | 233 | ||
199 | /** | 234 | /** |
200 | * Iterator - Checks if current position is valid | 235 | * Iterator - Checks if current position is valid |
201 | */ | 236 | */ |
202 | function valid() | 237 | public function valid() |
203 | { | 238 | { |
204 | return isset($this->_keys[$this->_position]); | 239 | return isset($this->keys[$this->position]); |
205 | } | 240 | } |
206 | 241 | ||
207 | /** | 242 | /** |
@@ -209,15 +244,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
209 | * | 244 | * |
210 | * If no DB file is found, creates a dummy DB. | 245 | * If no DB file is found, creates a dummy DB. |
211 | */ | 246 | */ |
212 | private function _checkDB() | 247 | private function check() |
213 | { | 248 | { |
214 | if (file_exists($this->_datastore)) { | 249 | if (file_exists($this->datastore)) { |
215 | return; | 250 | return; |
216 | } | 251 | } |
217 | 252 | ||
218 | // Create a dummy database for example | 253 | // Create a dummy database for example |
219 | $this->_links = array(); | 254 | $this->links = array(); |
220 | $link = array( | 255 | $link = array( |
256 | 'id' => 1, | ||
221 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', | 257 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', |
222 | 'url'=>'https://github.com/shaarli/Shaarli/wiki', | 258 | 'url'=>'https://github.com/shaarli/Shaarli/wiki', |
223 | 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. | 259 | 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. |
@@ -226,77 +262,69 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom | |||
226 | 262 | ||
227 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', | 263 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', |
228 | 'private'=>0, | 264 | 'private'=>0, |
229 | 'linkdate'=> date('Ymd_His'), | 265 | 'created'=> new DateTime(), |
230 | 'tags'=>'opensource software' | 266 | 'tags'=>'opensource software' |
231 | ); | 267 | ); |
232 | $this->_links[$link['linkdate']] = $link; | 268 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); |
269 | $this->links[1] = $link; | ||
233 | 270 | ||
234 | $link = array( | 271 | $link = array( |
272 | 'id' => 0, | ||
235 | 'title'=>'My secret stuff... - Pastebin.com', | 273 | 'title'=>'My secret stuff... - Pastebin.com', |
236 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', | 274 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', |
237 | 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', | 275 | 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', |
238 | 'private'=>1, | 276 | 'private'=>1, |
239 | 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), | 277 | 'created'=> new DateTime('1 minute ago'), |
240 | 'tags'=>'secretstuff' | 278 | 'tags'=>'secretstuff', |
241 | ); | 279 | ); |
242 | $this->_links[$link['linkdate']] = $link; | 280 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); |
281 | $this->links[0] = $link; | ||
243 | 282 | ||
244 | // Write database to disk | 283 | // Write database to disk |
245 | $this->writeDB(); | 284 | $this->write(); |
246 | } | 285 | } |
247 | 286 | ||
248 | /** | 287 | /** |
249 | * Reads database from disk to memory | 288 | * Reads database from disk to memory |
250 | */ | 289 | */ |
251 | private function _readDB() | 290 | private function read() |
252 | { | 291 | { |
253 | |||
254 | // Public links are hidden and user not logged in => nothing to show | 292 | // Public links are hidden and user not logged in => nothing to show |
255 | if ($this->_hidePublicLinks && !$this->_loggedIn) { | 293 | if ($this->hidePublicLinks && !$this->loggedIn) { |
256 | $this->_links = array(); | 294 | $this->links = array(); |
257 | return; | 295 | return; |
258 | } | 296 | } |
259 | 297 | ||
260 | // Read data | 298 | // Read data |
261 | // Note that gzinflate is faster than gzuncompress. | 299 | // Note that gzinflate is faster than gzuncompress. |
262 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | 300 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 |
263 | $this->_links = array(); | 301 | $this->links = array(); |
264 | 302 | ||
265 | if (file_exists($this->_datastore)) { | 303 | if (file_exists($this->datastore)) { |
266 | $this->_links = unserialize(gzinflate(base64_decode( | 304 | $this->links = unserialize(gzinflate(base64_decode( |
267 | substr(file_get_contents($this->_datastore), | 305 | substr(file_get_contents($this->datastore), |
268 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | 306 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); |
269 | } | 307 | } |
270 | 308 | ||
271 | // If user is not logged in, filter private links. | 309 | $toremove = array(); |
272 | if (!$this->_loggedIn) { | 310 | foreach ($this->links as $key => &$link) { |
273 | $toremove = array(); | 311 | if (! $this->loggedIn && $link['private'] != 0) { |
274 | foreach ($this->_links as $link) { | 312 | // Transition for not upgraded databases. |
275 | if ($link['private'] != 0) { | 313 | $toremove[] = $key; |
276 | $toremove[] = $link['linkdate']; | 314 | continue; |
277 | } | ||
278 | } | ||
279 | foreach ($toremove as $linkdate) { | ||
280 | unset($this->_links[$linkdate]); | ||
281 | } | 315 | } |
282 | } | ||
283 | |||
284 | $this->_urls = array(); | ||
285 | foreach ($this->_links as &$link) { | ||
286 | // Keep the list of the mapping URLs-->linkdate up-to-date. | ||
287 | $this->_urls[$link['url']] = $link['linkdate']; | ||
288 | 316 | ||
289 | // Sanitize data fields. | 317 | // Sanitize data fields. |
290 | sanitizeLink($link); | 318 | sanitizeLink($link); |
291 | 319 | ||
292 | // Remove private tags if the user is not logged in. | 320 | // Remove private tags if the user is not logged in. |
293 | if (! $this->_loggedIn) { | 321 | if (! $this->loggedIn) { |
294 | $link['tags'] = preg_replace('/(^| )\.[^($| )]+/', '', $link['tags']); | 322 | $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']); |
295 | } | 323 | } |
296 | 324 | ||
297 | // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). | 325 | // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). |
298 | if (!empty($this->_redirector) && !startsWith($link['url'], '?')) { | 326 | if (!empty($this->redirector) && !startsWith($link['url'], '?')) { |
299 | $link['real_url'] = $this->_redirector; | 327 | $link['real_url'] = $this->redirector; |
300 | if ($this->redirectorEncode) { | 328 | if ($this->redirectorEncode) { |
301 | $link['real_url'] .= urlencode(unescape($link['url'])); | 329 | $link['real_url'] .= urlencode(unescape($link['url'])); |
302 | } else { | 330 | } else { |
@@ -306,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba | |||
306 | else { | 334 | else { |
307 | $link['real_url'] = $link['url']; | 335 | $link['real_url'] = $link['url']; |
308 | } | 336 | } |
337 | |||
338 | // To be able to load links before running the update, and prepare the update | ||
339 | if (! isset($link['created'])) { | ||
340 | $link['id'] = $link['linkdate']; | ||
341 | $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']); | ||
342 | if (! empty($link['updated'])) { | ||
343 | $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']); | ||
344 | } | ||
345 | $link['shorturl'] = smallHash($link['linkdate']); | ||
346 | } | ||
309 | } | 347 | } |
348 | |||
349 | // If user is not logged in, filter private links. | ||
350 | foreach ($toremove as $offset) { | ||
351 | unset($this->links[$offset]); | ||
352 | } | ||
353 | |||
354 | $this->reorder(); | ||
310 | } | 355 | } |
311 | 356 | ||
312 | /** | 357 | /** |
@@ -314,19 +359,19 @@ You use the community supported version of the original Shaarli project, by Seba | |||
314 | * | 359 | * |
315 | * @throws IOException the datastore is not writable | 360 | * @throws IOException the datastore is not writable |
316 | */ | 361 | */ |
317 | private function writeDB() | 362 | private function write() |
318 | { | 363 | { |
319 | if (is_file($this->_datastore) && !is_writeable($this->_datastore)) { | 364 | if (is_file($this->datastore) && !is_writeable($this->datastore)) { |
320 | // The datastore exists but is not writeable | 365 | // The datastore exists but is not writeable |
321 | throw new IOException($this->_datastore); | 366 | throw new IOException($this->datastore); |
322 | } else if (!is_file($this->_datastore) && !is_writeable(dirname($this->_datastore))) { | 367 | } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { |
323 | // The datastore does not exist and its parent directory is not writeable | 368 | // The datastore does not exist and its parent directory is not writeable |
324 | throw new IOException(dirname($this->_datastore)); | 369 | throw new IOException(dirname($this->datastore)); |
325 | } | 370 | } |
326 | 371 | ||
327 | file_put_contents( | 372 | file_put_contents( |
328 | $this->_datastore, | 373 | $this->datastore, |
329 | self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix | 374 | self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix |
330 | ); | 375 | ); |
331 | 376 | ||
332 | } | 377 | } |
@@ -336,14 +381,14 @@ You use the community supported version of the original Shaarli project, by Seba | |||
336 | * | 381 | * |
337 | * @param string $pageCacheDir page cache directory | 382 | * @param string $pageCacheDir page cache directory |
338 | */ | 383 | */ |
339 | public function savedb($pageCacheDir) | 384 | public function save($pageCacheDir) |
340 | { | 385 | { |
341 | if (!$this->_loggedIn) { | 386 | if (!$this->loggedIn) { |
342 | // TODO: raise an Exception instead | 387 | // TODO: raise an Exception instead |
343 | die('You are not authorized to change the database.'); | 388 | die('You are not authorized to change the database.'); |
344 | } | 389 | } |
345 | 390 | ||
346 | $this->writeDB(); | 391 | $this->write(); |
347 | 392 | ||
348 | invalidateCaches($pageCacheDir); | 393 | invalidateCaches($pageCacheDir); |
349 | } | 394 | } |
@@ -357,8 +402,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
357 | */ | 402 | */ |
358 | public function getLinkFromUrl($url) | 403 | public function getLinkFromUrl($url) |
359 | { | 404 | { |
360 | if (isset($this->_urls[$url])) { | 405 | if (isset($this->urls[$url])) { |
361 | return $this->_links[$this->_urls[$url]]; | 406 | return $this->links[$this->urls[$url]]; |
362 | } | 407 | } |
363 | return false; | 408 | return false; |
364 | } | 409 | } |
@@ -375,7 +420,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
375 | public function filterHash($request) | 420 | public function filterHash($request) |
376 | { | 421 | { |
377 | $request = substr($request, 0, 6); | 422 | $request = substr($request, 0, 6); |
378 | $linkFilter = new LinkFilter($this->_links); | 423 | $linkFilter = new LinkFilter($this->links); |
379 | return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); | 424 | return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); |
380 | } | 425 | } |
381 | 426 | ||
@@ -387,7 +432,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
387 | * @return array list of shaare found. | 432 | * @return array list of shaare found. |
388 | */ | 433 | */ |
389 | public function filterDay($request) { | 434 | public function filterDay($request) { |
390 | $linkFilter = new LinkFilter($this->_links); | 435 | $linkFilter = new LinkFilter($this->links); |
391 | return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); | 436 | return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); |
392 | } | 437 | } |
393 | 438 | ||
@@ -409,7 +454,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
409 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; | 454 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; |
410 | 455 | ||
411 | // Search tags + fullsearch. | 456 | // Search tags + fullsearch. |
412 | if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { | 457 | if (! empty($searchtags) && ! empty($searchterm)) { |
413 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; | 458 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; |
414 | $request = array($searchtags, $searchterm); | 459 | $request = array($searchtags, $searchterm); |
415 | } | 460 | } |
@@ -429,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
429 | $request = ''; | 474 | $request = ''; |
430 | } | 475 | } |
431 | 476 | ||
432 | $linkFilter = new LinkFilter($this->_links); | 477 | $linkFilter = new LinkFilter($this); |
433 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); | 478 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); |
434 | } | 479 | } |
435 | 480 | ||
@@ -440,11 +485,18 @@ You use the community supported version of the original Shaarli project, by Seba | |||
440 | public function allTags() | 485 | public function allTags() |
441 | { | 486 | { |
442 | $tags = array(); | 487 | $tags = array(); |
443 | foreach ($this->_links as $link) { | 488 | $caseMapping = array(); |
444 | foreach (explode(' ', $link['tags']) as $tag) { | 489 | foreach ($this->links as $link) { |
445 | if (!empty($tag)) { | 490 | foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { |
446 | $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); | 491 | if (empty($tag)) { |
492 | continue; | ||
447 | } | 493 | } |
494 | // The first case found will be displayed. | ||
495 | if (!isset($caseMapping[strtolower($tag)])) { | ||
496 | $caseMapping[strtolower($tag)] = $tag; | ||
497 | $tags[$caseMapping[strtolower($tag)]] = 0; | ||
498 | } | ||
499 | $tags[$caseMapping[strtolower($tag)]]++; | ||
448 | } | 500 | } |
449 | } | 501 | } |
450 | // Sort tags by usage (most used tag first) | 502 | // Sort tags by usage (most used tag first) |
@@ -459,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba | |||
459 | public function days() | 511 | public function days() |
460 | { | 512 | { |
461 | $linkDays = array(); | 513 | $linkDays = array(); |
462 | foreach (array_keys($this->_links) as $day) { | 514 | foreach ($this->links as $link) { |
463 | $linkDays[substr($day, 0, 8)] = 0; | 515 | $linkDays[$link['created']->format('Ymd')] = 0; |
464 | } | 516 | } |
465 | $linkDays = array_keys($linkDays); | 517 | $linkDays = array_keys($linkDays); |
466 | sort($linkDays); | 518 | sort($linkDays); |
467 | 519 | ||
468 | return $linkDays; | 520 | return $linkDays; |
469 | } | 521 | } |
522 | |||
523 | /** | ||
524 | * Reorder links by creation date (newest first). | ||
525 | * | ||
526 | * Also update the urls and ids mapping arrays. | ||
527 | * | ||
528 | * @param string $order ASC|DESC | ||
529 | */ | ||
530 | public function reorder($order = 'DESC') | ||
531 | { | ||
532 | $order = $order === 'ASC' ? -1 : 1; | ||
533 | // Reorder array by dates. | ||
534 | usort($this->links, function($a, $b) use ($order) { | ||
535 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | ||
536 | }); | ||
537 | |||
538 | $this->urls = array(); | ||
539 | $this->ids = array(); | ||
540 | foreach ($this->links as $key => $link) { | ||
541 | $this->urls[$link['url']] = $key; | ||
542 | $this->ids[$link['id']] = $key; | ||
543 | } | ||
544 | } | ||
545 | |||
546 | /** | ||
547 | * Return the next key for link creation. | ||
548 | * E.g. If the last ID is 597, the next will be 598. | ||
549 | * | ||
550 | * @return int next ID. | ||
551 | */ | ||
552 | public function getNextId() | ||
553 | { | ||
554 | if (!empty($this->ids)) { | ||
555 | return max(array_keys($this->ids)) + 1; | ||
556 | } | ||
557 | return 0; | ||
558 | } | ||
559 | |||
560 | /** | ||
561 | * Returns a link offset in links array from its unique ID. | ||
562 | * | ||
563 | * @param int $id Persistent ID of a link. | ||
564 | * | ||
565 | * @return int Real offset in local array, or null if doesn't exist. | ||
566 | */ | ||
567 | protected function getLinkOffset($id) | ||
568 | { | ||
569 | if (isset($this->ids[$id])) { | ||
570 | return $this->ids[$id]; | ||
571 | } | ||
572 | return null; | ||
573 | } | ||
470 | } | 574 | } |