diff options
author | Arthur <arthur@hoa.ro> | 2016-12-12 03:15:32 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-12 03:15:32 +0100 |
commit | 9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a (patch) | |
tree | 505cd68f1d07e0b4d6aedcd49c31368760798c62 /application/LinkDB.php | |
parent | a0d079141eb155c263ebfaa1aad2629382223e31 (diff) | |
parent | d592daea8343bb4dfecff5d97e93699581ccc58c (diff) | |
download | Shaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.tar.gz Shaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.tar.zst Shaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.zip |
Merge pull request #697 from ArthurHoaro/feature/ids-bis
Link ID refactoring
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r-- | application/LinkDB.php | 184 |
1 files changed, 140 insertions, 44 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php index c8b162b6..1e13286a 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -6,15 +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: creation date of this entry, format: 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 | * - updated: last modification date of this entry, format: YYYYMMDD_HHMMSS | ||
18 | * - private: Is this link private? 0=no, other value=yes | 18 | * - private: Is this link private? 0=no, other value=yes |
19 | * - tags: tags attached to this entry (separated by spaces) | 19 | * - tags: tags attached to this entry (separated by spaces) |
20 | * - title Title of the link | 20 | * - title Title of the link |
@@ -22,11 +22,25 @@ | |||
22 | * Can be absolute or relative. | 22 | * Can be absolute or relative. |
23 | * Relative URLs are permalinks (e.g.'?m-ukcw') | 23 | * Relative URLs are permalinks (e.g.'?m-ukcw') |
24 | * - real_url Absolute processed URL. | 24 | * - real_url Absolute processed URL. |
25 | * - shorturl Permalink smallhash | ||
25 | * | 26 | * |
26 | * Implements 3 interfaces: | 27 | * Implements 3 interfaces: |
27 | * - ArrayAccess: behaves like an associative array; | 28 | * - ArrayAccess: behaves like an associative array; |
28 | * - Countable: there is a count() method; | 29 | * - Countable: there is a count() method; |
29 | * - 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 | ||
30 | */ | 44 | */ |
31 | class LinkDB implements Iterator, Countable, ArrayAccess | 45 | class LinkDB implements Iterator, Countable, ArrayAccess |
32 | { | 46 | { |
@@ -47,11 +61,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
47 | // - value: associative array (keys: title, description...) | 61 | // - value: associative array (keys: title, description...) |
48 | private $links; | 62 | private $links; |
49 | 63 | ||
50 | // List of all recorded URLs (key=url, value=linkdate) | 64 | // List of all recorded URLs (key=url, value=link offset) |
51 | // for fast reserve search (url-->linkdate) | 65 | // for fast reserve search (url-->link offset) |
52 | private $urls; | 66 | private $urls; |
53 | 67 | ||
54 | // List of linkdate keys (for the Iterator interface implementation) | 68 | /** |
69 | * @var array List of all links IDS mapped with their array offset. | ||
70 | * Map: id->offset. | ||
71 | */ | ||
72 | protected $ids; | ||
73 | |||
74 | // List of offset keys (for the Iterator interface implementation) | ||
55 | private $keys; | 75 | private $keys; |
56 | 76 | ||
57 | // Position in the $this->keys array (for the Iterator interface) | 77 | // Position in the $this->keys array (for the Iterator interface) |
@@ -121,14 +141,26 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
121 | if (!$this->loggedIn) { | 141 | if (!$this->loggedIn) { |
122 | die('You are not authorized to add a link.'); | 142 | die('You are not authorized to add a link.'); |
123 | } | 143 | } |
124 | if (empty($value['linkdate']) || empty($value['url'])) { | 144 | if (!isset($value['id']) || empty($value['url'])) { |
125 | 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.'); |
126 | } | 146 | } |
127 | if (empty($offset)) { | 147 | if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { |
128 | die('You must specify a key.'); | 148 | die('You must specify an integer as a key.'); |
149 | } | ||
150 | if (! empty($offset) && $offset !== $value['id']) { | ||
151 | die('Array offset and link ID must be equal.'); | ||
152 | } | ||
153 | |||
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); | ||
129 | } | 160 | } |
130 | $this->links[$offset] = $value; | 161 | $this->links[$offset] = $value; |
131 | $this->urls[$value['url']]=$offset; | 162 | $this->urls[$value['url']] = $offset; |
163 | $this->ids[$value['id']] = $offset; | ||
132 | } | 164 | } |
133 | 165 | ||
134 | /** | 166 | /** |
@@ -136,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
136 | */ | 168 | */ |
137 | public function offsetExists($offset) | 169 | public function offsetExists($offset) |
138 | { | 170 | { |
139 | return array_key_exists($offset, $this->links); | 171 | return array_key_exists($this->getLinkOffset($offset), $this->links); |
140 | } | 172 | } |
141 | 173 | ||
142 | /** | 174 | /** |
@@ -148,9 +180,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
148 | // TODO: raise an exception | 180 | // TODO: raise an exception |
149 | die('You are not authorized to delete a link.'); | 181 | die('You are not authorized to delete a link.'); |
150 | } | 182 | } |
151 | $url = $this->links[$offset]['url']; | 183 | $realOffset = $this->getLinkOffset($offset); |
184 | $url = $this->links[$realOffset]['url']; | ||
152 | unset($this->urls[$url]); | 185 | unset($this->urls[$url]); |
153 | unset($this->links[$offset]); | 186 | unset($this->ids[$realOffset]); |
187 | unset($this->links[$realOffset]); | ||
154 | } | 188 | } |
155 | 189 | ||
156 | /** | 190 | /** |
@@ -158,7 +192,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
158 | */ | 192 | */ |
159 | public function offsetGet($offset) | 193 | public function offsetGet($offset) |
160 | { | 194 | { |
161 | return isset($this->links[$offset]) ? $this->links[$offset] : null; | 195 | $realOffset = $this->getLinkOffset($offset); |
196 | return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null; | ||
162 | } | 197 | } |
163 | 198 | ||
164 | /** | 199 | /** |
@@ -166,7 +201,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
166 | */ | 201 | */ |
167 | public function current() | 202 | public function current() |
168 | { | 203 | { |
169 | return $this->links[$this->keys[$this->position]]; | 204 | return $this[$this->keys[$this->position]]; |
170 | } | 205 | } |
171 | 206 | ||
172 | /** | 207 | /** |
@@ -192,8 +227,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
192 | */ | 227 | */ |
193 | public function rewind() | 228 | public function rewind() |
194 | { | 229 | { |
195 | $this->keys = array_keys($this->links); | 230 | $this->keys = array_keys($this->ids); |
196 | rsort($this->keys); | ||
197 | $this->position = 0; | 231 | $this->position = 0; |
198 | } | 232 | } |
199 | 233 | ||
@@ -219,6 +253,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
219 | // Create a dummy database for example | 253 | // Create a dummy database for example |
220 | $this->links = array(); | 254 | $this->links = array(); |
221 | $link = array( | 255 | $link = array( |
256 | 'id' => 1, | ||
222 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', | 257 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', |
223 | 'url'=>'https://github.com/shaarli/Shaarli/wiki', | 258 | 'url'=>'https://github.com/shaarli/Shaarli/wiki', |
224 | '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. |
@@ -227,20 +262,23 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom | |||
227 | 262 | ||
228 | 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.', |
229 | 'private'=>0, | 264 | 'private'=>0, |
230 | 'linkdate'=> date('Ymd_His'), | 265 | 'created'=> new DateTime(), |
231 | 'tags'=>'opensource software' | 266 | 'tags'=>'opensource software' |
232 | ); | 267 | ); |
233 | $this->links[$link['linkdate']] = $link; | 268 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); |
269 | $this->links[1] = $link; | ||
234 | 270 | ||
235 | $link = array( | 271 | $link = array( |
272 | 'id' => 0, | ||
236 | 'title'=>'My secret stuff... - Pastebin.com', | 273 | 'title'=>'My secret stuff... - Pastebin.com', |
237 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', | 274 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', |
238 | '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.', |
239 | 'private'=>1, | 276 | 'private'=>1, |
240 | 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), | 277 | 'created'=> new DateTime('1 minute ago'), |
241 | 'tags'=>'secretstuff' | 278 | 'tags'=>'secretstuff', |
242 | ); | 279 | ); |
243 | $this->links[$link['linkdate']] = $link; | 280 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); |
281 | $this->links[0] = $link; | ||
244 | 282 | ||
245 | // Write database to disk | 283 | // Write database to disk |
246 | $this->write(); | 284 | $this->write(); |
@@ -251,7 +289,6 @@ You use the community supported version of the original Shaarli project, by Seba | |||
251 | */ | 289 | */ |
252 | private function read() | 290 | private function read() |
253 | { | 291 | { |
254 | |||
255 | // 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 |
256 | if ($this->hidePublicLinks && !$this->loggedIn) { | 293 | if ($this->hidePublicLinks && !$this->loggedIn) { |
257 | $this->links = array(); | 294 | $this->links = array(); |
@@ -269,23 +306,13 @@ You use the community supported version of the original Shaarli project, by Seba | |||
269 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | 306 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); |
270 | } | 307 | } |
271 | 308 | ||
272 | // If user is not logged in, filter private links. | 309 | $toremove = array(); |
273 | if (!$this->loggedIn) { | 310 | foreach ($this->links as $key => &$link) { |
274 | $toremove = array(); | 311 | if (! $this->loggedIn && $link['private'] != 0) { |
275 | foreach ($this->links as $link) { | 312 | // Transition for not upgraded databases. |
276 | if ($link['private'] != 0) { | 313 | $toremove[] = $key; |
277 | $toremove[] = $link['linkdate']; | 314 | continue; |
278 | } | ||
279 | } | ||
280 | foreach ($toremove as $linkdate) { | ||
281 | unset($this->links[$linkdate]); | ||
282 | } | 315 | } |
283 | } | ||
284 | |||
285 | $this->urls = array(); | ||
286 | foreach ($this->links as &$link) { | ||
287 | // Keep the list of the mapping URLs-->linkdate up-to-date. | ||
288 | $this->urls[$link['url']] = $link['linkdate']; | ||
289 | 316 | ||
290 | // Sanitize data fields. | 317 | // Sanitize data fields. |
291 | sanitizeLink($link); | 318 | sanitizeLink($link); |
@@ -307,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba | |||
307 | else { | 334 | else { |
308 | $link['real_url'] = $link['url']; | 335 | $link['real_url'] = $link['url']; |
309 | } | 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 | } | ||
347 | } | ||
348 | |||
349 | // If user is not logged in, filter private links. | ||
350 | foreach ($toremove as $offset) { | ||
351 | unset($this->links[$offset]); | ||
310 | } | 352 | } |
353 | |||
354 | $this->reorder(); | ||
311 | } | 355 | } |
312 | 356 | ||
313 | /** | 357 | /** |
@@ -430,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
430 | $request = ''; | 474 | $request = ''; |
431 | } | 475 | } |
432 | 476 | ||
433 | $linkFilter = new LinkFilter($this->links); | 477 | $linkFilter = new LinkFilter($this); |
434 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); | 478 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); |
435 | } | 479 | } |
436 | 480 | ||
@@ -467,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba | |||
467 | public function days() | 511 | public function days() |
468 | { | 512 | { |
469 | $linkDays = array(); | 513 | $linkDays = array(); |
470 | foreach (array_keys($this->links) as $day) { | 514 | foreach ($this->links as $link) { |
471 | $linkDays[substr($day, 0, 8)] = 0; | 515 | $linkDays[$link['created']->format('Ymd')] = 0; |
472 | } | 516 | } |
473 | $linkDays = array_keys($linkDays); | 517 | $linkDays = array_keys($linkDays); |
474 | sort($linkDays); | 518 | sort($linkDays); |
475 | 519 | ||
476 | return $linkDays; | 520 | return $linkDays; |
477 | } | 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 | } | ||
478 | } | 574 | } |