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 | |
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')
-rw-r--r-- | application/FeedBuilder.php | 6 | ||||
-rw-r--r-- | application/LinkDB.php | 184 | ||||
-rw-r--r-- | application/LinkFilter.php | 37 | ||||
-rw-r--r-- | application/LinkUtils.php | 13 | ||||
-rw-r--r-- | application/NetscapeBookmarkUtils.php | 26 | ||||
-rw-r--r-- | application/Updater.php | 45 | ||||
-rw-r--r-- | application/Utils.php | 6 |
7 files changed, 232 insertions, 85 deletions
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php index 4036a7cc..fedd90e6 100644 --- a/application/FeedBuilder.php +++ b/application/FeedBuilder.php | |||
@@ -143,7 +143,7 @@ class FeedBuilder | |||
143 | */ | 143 | */ |
144 | protected function buildItem($link, $pageaddr) | 144 | protected function buildItem($link, $pageaddr) |
145 | { | 145 | { |
146 | $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']); | 146 | $link['guid'] = $pageaddr .'?'. $link['shorturl']; |
147 | // Check for both signs of a note: starting with ? and 7 chars long. | 147 | // Check for both signs of a note: starting with ? and 7 chars long. |
148 | if ($link['url'][0] === '?' && strlen($link['url']) === 7) { | 148 | if ($link['url'][0] === '?' && strlen($link['url']) === 7) { |
149 | $link['url'] = $pageaddr . $link['url']; | 149 | $link['url'] = $pageaddr . $link['url']; |
@@ -156,12 +156,12 @@ class FeedBuilder | |||
156 | $link['description'] = format_description($link['description'], '', $pageaddr); | 156 | $link['description'] = format_description($link['description'], '', $pageaddr); |
157 | $link['description'] .= PHP_EOL .'<br>— '. $permalink; | 157 | $link['description'] .= PHP_EOL .'<br>— '. $permalink; |
158 | 158 | ||
159 | $pubDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | 159 | $pubDate = $link['created']; |
160 | $link['pub_iso_date'] = $this->getIsoDate($pubDate); | 160 | $link['pub_iso_date'] = $this->getIsoDate($pubDate); |
161 | 161 | ||
162 | // atom:entry elements MUST contain exactly one atom:updated element. | 162 | // atom:entry elements MUST contain exactly one atom:updated element. |
163 | if (!empty($link['updated'])) { | 163 | if (!empty($link['updated'])) { |
164 | $upDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']); | 164 | $upDate = $link['updated']; |
165 | $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); | 165 | $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); |
166 | } else { | 166 | } else { |
167 | $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; | 167 | $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; |
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 | } |
diff --git a/application/LinkFilter.php b/application/LinkFilter.php index d4fe28df..daa6d9cc 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php | |||
@@ -33,12 +33,12 @@ class LinkFilter | |||
33 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; | 33 | public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; |
34 | 34 | ||
35 | /** | 35 | /** |
36 | * @var array all available links. | 36 | * @var LinkDB all available links. |
37 | */ | 37 | */ |
38 | private $links; | 38 | private $links; |
39 | 39 | ||
40 | /** | 40 | /** |
41 | * @param array $links initialization. | 41 | * @param LinkDB $links initialization. |
42 | */ | 42 | */ |
43 | public function __construct($links) | 43 | public function __construct($links) |
44 | { | 44 | { |
@@ -94,18 +94,16 @@ class LinkFilter | |||
94 | private function noFilter($privateonly = false) | 94 | private function noFilter($privateonly = false) |
95 | { | 95 | { |
96 | if (! $privateonly) { | 96 | if (! $privateonly) { |
97 | krsort($this->links); | ||
98 | return $this->links; | 97 | return $this->links; |
99 | } | 98 | } |
100 | 99 | ||
101 | $out = array(); | 100 | $out = array(); |
102 | foreach ($this->links as $value) { | 101 | foreach ($this->links as $key => $value) { |
103 | if ($value['private']) { | 102 | if ($value['private']) { |
104 | $out[$value['linkdate']] = $value; | 103 | $out[$key] = $value; |
105 | } | 104 | } |
106 | } | 105 | } |
107 | 106 | ||
108 | krsort($out); | ||
109 | return $out; | 107 | return $out; |
110 | } | 108 | } |
111 | 109 | ||
@@ -121,10 +119,10 @@ class LinkFilter | |||
121 | private function filterSmallHash($smallHash) | 119 | private function filterSmallHash($smallHash) |
122 | { | 120 | { |
123 | $filtered = array(); | 121 | $filtered = array(); |
124 | foreach ($this->links as $l) { | 122 | foreach ($this->links as $key => $l) { |
125 | if ($smallHash == smallHash($l['linkdate'])) { | 123 | if ($smallHash == $l['shorturl']) { |
126 | // Yes, this is ugly and slow | 124 | // Yes, this is ugly and slow |
127 | $filtered[$l['linkdate']] = $l; | 125 | $filtered[$key] = $l; |
128 | return $filtered; | 126 | return $filtered; |
129 | } | 127 | } |
130 | } | 128 | } |
@@ -188,7 +186,7 @@ class LinkFilter | |||
188 | $keys = array('title', 'description', 'url', 'tags'); | 186 | $keys = array('title', 'description', 'url', 'tags'); |
189 | 187 | ||
190 | // Iterate over every stored link. | 188 | // Iterate over every stored link. |
191 | foreach ($this->links as $link) { | 189 | foreach ($this->links as $id => $link) { |
192 | 190 | ||
193 | // ignore non private links when 'privatonly' is on. | 191 | // ignore non private links when 'privatonly' is on. |
194 | if (! $link['private'] && $privateonly === true) { | 192 | if (! $link['private'] && $privateonly === true) { |
@@ -222,11 +220,10 @@ class LinkFilter | |||
222 | } | 220 | } |
223 | 221 | ||
224 | if ($found) { | 222 | if ($found) { |
225 | $filtered[$link['linkdate']] = $link; | 223 | $filtered[$id] = $link; |
226 | } | 224 | } |
227 | } | 225 | } |
228 | 226 | ||
229 | krsort($filtered); | ||
230 | return $filtered; | 227 | return $filtered; |
231 | } | 228 | } |
232 | 229 | ||
@@ -256,7 +253,7 @@ class LinkFilter | |||
256 | return $filtered; | 253 | return $filtered; |
257 | } | 254 | } |
258 | 255 | ||
259 | foreach ($this->links as $link) { | 256 | foreach ($this->links as $key => $link) { |
260 | // ignore non private links when 'privatonly' is on. | 257 | // ignore non private links when 'privatonly' is on. |
261 | if (! $link['private'] && $privateonly === true) { | 258 | if (! $link['private'] && $privateonly === true) { |
262 | continue; | 259 | continue; |
@@ -278,10 +275,9 @@ class LinkFilter | |||
278 | } | 275 | } |
279 | 276 | ||
280 | if ($found) { | 277 | if ($found) { |
281 | $filtered[$link['linkdate']] = $link; | 278 | $filtered[$key] = $link; |
282 | } | 279 | } |
283 | } | 280 | } |
284 | krsort($filtered); | ||
285 | return $filtered; | 281 | return $filtered; |
286 | } | 282 | } |
287 | 283 | ||
@@ -304,13 +300,14 @@ class LinkFilter | |||
304 | } | 300 | } |
305 | 301 | ||
306 | $filtered = array(); | 302 | $filtered = array(); |
307 | foreach ($this->links as $l) { | 303 | foreach ($this->links as $key => $l) { |
308 | if (startsWith($l['linkdate'], $day)) { | 304 | if ($l['created']->format('Ymd') == $day) { |
309 | $filtered[$l['linkdate']] = $l; | 305 | $filtered[$key] = $l; |
310 | } | 306 | } |
311 | } | 307 | } |
312 | ksort($filtered); | 308 | |
313 | return $filtered; | 309 | // sort by date ASC |
310 | return array_reverse($filtered, true); | ||
314 | } | 311 | } |
315 | 312 | ||
316 | /** | 313 | /** |
diff --git a/application/LinkUtils.php b/application/LinkUtils.php index 9d9ae3cb..cf58f808 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php | |||
@@ -169,3 +169,16 @@ function space2nbsp($text) | |||
169 | function format_description($description, $redirector = '', $indexUrl = '') { | 169 | function format_description($description, $redirector = '', $indexUrl = '') { |
170 | return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl))); | 170 | return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl))); |
171 | } | 171 | } |
172 | |||
173 | /** | ||
174 | * Generate a small hash for a link. | ||
175 | * | ||
176 | * @param DateTime $date Link creation date. | ||
177 | * @param int $id Link ID. | ||
178 | * | ||
179 | * @return string the small hash generated from link data. | ||
180 | */ | ||
181 | function link_small_hash($date, $id) | ||
182 | { | ||
183 | return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id); | ||
184 | } | ||
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php index dd21f05b..e7148d00 100644 --- a/application/NetscapeBookmarkUtils.php +++ b/application/NetscapeBookmarkUtils.php | |||
@@ -38,7 +38,7 @@ class NetscapeBookmarkUtils | |||
38 | if ($link['private'] == 0 && $selection == 'private') { | 38 | if ($link['private'] == 0 && $selection == 'private') { |
39 | continue; | 39 | continue; |
40 | } | 40 | } |
41 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | 41 | $date = $link['created']; |
42 | $link['timestamp'] = $date->getTimestamp(); | 42 | $link['timestamp'] = $date->getTimestamp(); |
43 | $link['taglist'] = str_replace(' ', ',', $link['tags']); | 43 | $link['taglist'] = str_replace(' ', ',', $link['tags']); |
44 | 44 | ||
@@ -147,7 +147,6 @@ class NetscapeBookmarkUtils | |||
147 | 'url' => $bkm['uri'], | 147 | 'url' => $bkm['uri'], |
148 | 'description' => $bkm['note'], | 148 | 'description' => $bkm['note'], |
149 | 'private' => $private, | 149 | 'private' => $private, |
150 | 'linkdate'=> '', | ||
151 | 'tags' => $bkm['tags'] | 150 | 'tags' => $bkm['tags'] |
152 | ); | 151 | ); |
153 | 152 | ||
@@ -161,25 +160,22 @@ class NetscapeBookmarkUtils | |||
161 | } | 160 | } |
162 | 161 | ||
163 | // Overwrite an existing link, keep its date | 162 | // Overwrite an existing link, keep its date |
164 | $newLink['linkdate'] = $existingLink['linkdate']; | 163 | $newLink['id'] = $existingLink['id']; |
165 | $linkDb[$existingLink['linkdate']] = $newLink; | 164 | $newLink['created'] = $existingLink['created']; |
165 | $newLink['updated'] = new DateTime(); | ||
166 | $linkDb[$existingLink['id']] = $newLink; | ||
166 | $importCount++; | 167 | $importCount++; |
167 | $overwriteCount++; | 168 | $overwriteCount++; |
168 | continue; | 169 | continue; |
169 | } | 170 | } |
170 | 171 | ||
171 | // Add a new link | 172 | // Add a new link - @ used for UNIX timestamps |
172 | $newLinkDate = new DateTime('@'.strval($bkm['time'])); | 173 | $newLinkDate = new DateTime('@'.strval($bkm['time'])); |
173 | while (!empty($linkDb[$newLinkDate->format(LinkDB::LINK_DATE_FORMAT)])) { | 174 | $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); |
174 | // Ensure the date/time is not already used | 175 | $newLink['created'] = $newLinkDate; |
175 | // - this hack is necessary as the date/time acts as a primary key | 176 | $newLink['id'] = $linkDb->getNextId(); |
176 | // - apply 1 second increments until an unused index is found | 177 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); |
177 | // See https://github.com/shaarli/Shaarli/issues/351 | 178 | $linkDb[$newLink['id']] = $newLink; |
178 | $newLinkDate->add(new DateInterval('PT1S')); | ||
179 | } | ||
180 | $linkDbDate = $newLinkDate->format(LinkDB::LINK_DATE_FORMAT); | ||
181 | $newLink['linkdate'] = $linkDbDate; | ||
182 | $linkDb[$linkDbDate] = $newLink; | ||
183 | $importCount++; | 179 | $importCount++; |
184 | } | 180 | } |
185 | 181 | ||
diff --git a/application/Updater.php b/application/Updater.php index 36eddd4f..f0d02814 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -138,10 +138,10 @@ class Updater | |||
138 | public function updateMethodRenameDashTags() | 138 | public function updateMethodRenameDashTags() |
139 | { | 139 | { |
140 | $linklist = $this->linkDB->filterSearch(); | 140 | $linklist = $this->linkDB->filterSearch(); |
141 | foreach ($linklist as $link) { | 141 | foreach ($linklist as $key => $link) { |
142 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); | 142 | $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); |
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[$key] = $link; |
145 | } | 145 | } |
146 | $this->linkDB->save($this->conf->get('resource.page_cache')); | 146 | $this->linkDB->save($this->conf->get('resource.page_cache')); |
147 | return true; | 147 | return true; |
@@ -215,6 +215,47 @@ class Updater | |||
215 | } | 215 | } |
216 | return true; | 216 | return true; |
217 | } | 217 | } |
218 | |||
219 | /** | ||
220 | * Update the database to use the new ID system, which replaces linkdate primary keys. | ||
221 | * Also, creation and update dates are now DateTime objects (done by LinkDB). | ||
222 | * | ||
223 | * Since this update is very sensitve (changing the whole database), the datastore will be | ||
224 | * automatically backed up into the file datastore.<datetime>.php. | ||
225 | * | ||
226 | * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), | ||
227 | * which will be saved by this method. | ||
228 | * | ||
229 | * @return bool true if the update is successful, false otherwise. | ||
230 | */ | ||
231 | public function updateMethodDatastoreIds() | ||
232 | { | ||
233 | // up to date database | ||
234 | if (isset($this->linkDB[0])) { | ||
235 | return true; | ||
236 | } | ||
237 | |||
238 | $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php'; | ||
239 | copy($this->conf->get('resource.datastore'), $save); | ||
240 | |||
241 | $links = array(); | ||
242 | foreach ($this->linkDB as $offset => $value) { | ||
243 | $links[] = $value; | ||
244 | unset($this->linkDB[$offset]); | ||
245 | } | ||
246 | $links = array_reverse($links); | ||
247 | $cpt = 0; | ||
248 | foreach ($links as $l) { | ||
249 | unset($l['linkdate']); | ||
250 | $l['id'] = $cpt; | ||
251 | $this->linkDB[$cpt++] = $l; | ||
252 | } | ||
253 | |||
254 | $this->linkDB->save($this->conf->get('resource.page_cache')); | ||
255 | $this->linkDB->reorder(); | ||
256 | |||
257 | return true; | ||
258 | } | ||
218 | } | 259 | } |
219 | 260 | ||
220 | /** | 261 | /** |
diff --git a/application/Utils.php b/application/Utils.php index 0166ee2a..0a5b476e 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message) | |||
31 | * - are NOT cryptographically secure (they CAN be forged) | 31 | * - are NOT cryptographically secure (they CAN be forged) |
32 | * | 32 | * |
33 | * In Shaarli, they are used as a tinyurl-like link to individual entries, | 33 | * In Shaarli, they are used as a tinyurl-like link to individual entries, |
34 | * e.g. smallHash('20111006_131924') --> yZH23w | 34 | * built once with the combination of the date and item ID. |
35 | * e.g. smallHash('20111006_131924' . 142) --> eaWxtQ | ||
36 | * | ||
37 | * @warning before v0.8.1, smallhashes were built only with the date, | ||
38 | * and their value has been preserved. | ||
35 | * | 39 | * |
36 | * @param string $text Create a hash from this text. | 40 | * @param string $text Create a hash from this text. |
37 | * | 41 | * |