aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthur <arthur@hoa.ro>2016-12-12 03:15:32 +0100
committerGitHub <noreply@github.com>2016-12-12 03:15:32 +0100
commit9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a (patch)
tree505cd68f1d07e0b4d6aedcd49c31368760798c62
parenta0d079141eb155c263ebfaa1aad2629382223e31 (diff)
parentd592daea8343bb4dfecff5d97e93699581ccc58c (diff)
downloadShaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.tar.gz
Shaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.tar.zst
Shaarli-9cf93bcfc53c36e0dd59fcfc717ac483ee74b35a.zip
Merge pull request #697 from ArthurHoaro/feature/ids-bis
Link ID refactoring
-rw-r--r--application/FeedBuilder.php6
-rw-r--r--application/LinkDB.php184
-rw-r--r--application/LinkFilter.php37
-rw-r--r--application/LinkUtils.php13
-rw-r--r--application/NetscapeBookmarkUtils.php26
-rw-r--r--application/Updater.php45
-rw-r--r--application/Utils.php6
-rw-r--r--index.php104
-rw-r--r--plugins/isso/isso.php4
-rw-r--r--tests/FeedBuilderTest.php31
-rw-r--r--tests/LinkDBTest.php39
-rw-r--r--tests/LinkFilterTest.php6
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkExportTest.php6
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkImportTest.php146
-rw-r--r--tests/Updater/UpdaterTest.php98
-rw-r--r--tests/plugins/PluginIssoTest.php25
-rw-r--r--tests/utils/ReferenceLinkDB.php61
-rw-r--r--tpl/daily.html4
-rw-r--r--tpl/editlink.html3
-rw-r--r--tpl/linklist.html6
20 files changed, 617 insertions, 233 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>&#8212; '. $permalink; 157 $link['description'] .= PHP_EOL .'<br>&#8212; '. $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 */
31class LinkDB implements Iterator, Countable, ArrayAccess 45class 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
228You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 263You 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)
169function format_description($description, $redirector = '', $indexUrl = '') { 169function 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 */
181function 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 *
diff --git a/index.php b/index.php
index 5366cb0e..fdbdfaa2 100644
--- a/index.php
+++ b/index.php
@@ -564,24 +564,19 @@ function showDailyRSS($conf) {
564 ); 564 );
565 565
566 /* Some Shaarlies may have very few links, so we need to look 566 /* Some Shaarlies may have very few links, so we need to look
567 back in time (rsort()) until we have enough days ($nb_of_days). 567 back in time until we have enough days ($nb_of_days).
568 */ 568 */
569 $linkdates = array();
570 foreach ($LINKSDB as $linkdate => $value) {
571 $linkdates[] = $linkdate;
572 }
573 rsort($linkdates);
574 $nb_of_days = 7; // We take 7 days. 569 $nb_of_days = 7; // We take 7 days.
575 $today = date('Ymd'); 570 $today = date('Ymd');
576 $days = array(); 571 $days = array();
577 572
578 foreach ($linkdates as $linkdate) { 573 foreach ($LINKSDB as $link) {
579 $day = substr($linkdate, 0, 8); // Extract day (without time) 574 $day = $link['created']->format('Ymd'); // Extract day (without time)
580 if (strcmp($day,$today) < 0) { 575 if (strcmp($day, $today) < 0) {
581 if (empty($days[$day])) { 576 if (empty($days[$day])) {
582 $days[$day] = array(); 577 $days[$day] = array();
583 } 578 }
584 $days[$day][] = $linkdate; 579 $days[$day][] = $link;
585 } 580 }
586 581
587 if (count($days) > $nb_of_days) { 582 if (count($days) > $nb_of_days) {
@@ -601,24 +596,18 @@ function showDailyRSS($conf) {
601 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; 596 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
602 597
603 // For each day. 598 // For each day.
604 foreach ($days as $day => $linkdates) { 599 foreach ($days as $day => $links) {
605 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 600 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
606 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. 601 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
607 602
608 // Build the HTML body of this RSS entry.
609 $links = array();
610
611 // We pre-format some fields for proper output. 603 // We pre-format some fields for proper output.
612 foreach ($linkdates as $linkdate) { 604 foreach ($links as &$link) {
613 $l = $LINKSDB[$linkdate]; 605 $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
614 $l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url')); 606 $link['thumbnail'] = thumbnail($conf, $link['url']);
615 $l['thumbnail'] = thumbnail($conf, $l['url']); 607 $link['timestamp'] = $link['created']->getTimestamp();
616 $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']); 608 if (startsWith($link['url'], '?')) {
617 $l['timestamp'] = $l_date->getTimestamp(); 609 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
618 if (startsWith($l['url'], '?')) {
619 $l['url'] = index_url($_SERVER) . $l['url']; // make permalink URL absolute
620 } 610 }
621 $links[$linkdate] = $l;
622 } 611 }
623 612
624 // Then build the HTML for this day: 613 // Then build the HTML for this day:
@@ -680,8 +669,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
680 $linksToDisplay[$key]['taglist']=$taglist; 669 $linksToDisplay[$key]['taglist']=$taglist;
681 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url')); 670 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
682 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']); 671 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
683 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 672 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
684 $linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
685 } 673 }
686 674
687 /* We need to spread the articles on 3 columns. 675 /* We need to spread the articles on 3 columns.
@@ -831,7 +819,7 @@ function renderPage($conf, $pluginManager)
831 // Get only links which have a thumbnail. 819 // Get only links which have a thumbnail.
832 foreach($links as $link) 820 foreach($links as $link)
833 { 821 {
834 $permalink='?'.escape(smallHash($link['linkdate'])); 822 $permalink='?'.$link['shorturl'];
835 $thumb=lazyThumbnail($conf, $link['url'],$permalink); 823 $thumb=lazyThumbnail($conf, $link['url'],$permalink);
836 if ($thumb!='') // Only output links which have a thumbnail. 824 if ($thumb!='') // Only output links which have a thumbnail.
837 { 825 {
@@ -1245,13 +1233,28 @@ function renderPage($conf, $pluginManager)
1245 // -------- User clicked the "Save" button when editing a link: Save link to database. 1233 // -------- User clicked the "Save" button when editing a link: Save link to database.
1246 if (isset($_POST['save_edit'])) 1234 if (isset($_POST['save_edit']))
1247 { 1235 {
1248 $linkdate = $_POST['lf_linkdate'];
1249 $updated = isset($LINKSDB[$linkdate]) ? strval(date('Ymd_His')) : false;
1250
1251 // Go away! 1236 // Go away!
1252 if (! tokenOk($_POST['token'])) { 1237 if (! tokenOk($_POST['token'])) {
1253 die('Wrong token.'); 1238 die('Wrong token.');
1254 } 1239 }
1240
1241 // lf_id should only be present if the link exists.
1242 $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
1243 // Linkdate is kept here to:
1244 // - use the same permalink for notes as they're displayed when creating them
1245 // - let users hack creation date of their posts
1246 // See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link
1247 $linkdate = escape($_POST['lf_linkdate']);
1248 if (isset($LINKSDB[$id])) {
1249 // Edit
1250 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1251 $updated = new DateTime();
1252 } else {
1253 // New link
1254 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1255 $updated = null;
1256 }
1257
1255 // Remove multiple spaces. 1258 // Remove multiple spaces.
1256 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); 1259 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
1257 // Remove first '-' char in tags. 1260 // Remove first '-' char in tags.
@@ -1268,14 +1271,17 @@ function renderPage($conf, $pluginManager)
1268 } 1271 }
1269 1272
1270 $link = array( 1273 $link = array(
1274 'id' => $id,
1271 'title' => trim($_POST['lf_title']), 1275 'title' => trim($_POST['lf_title']),
1272 'url' => $url, 1276 'url' => $url,
1273 'description' => $_POST['lf_description'], 1277 'description' => $_POST['lf_description'],
1274 'private' => (isset($_POST['lf_private']) ? 1 : 0), 1278 'private' => (isset($_POST['lf_private']) ? 1 : 0),
1275 'linkdate' => $linkdate, 1279 'created' => $created,
1276 'updated' => $updated, 1280 'updated' => $updated,
1277 'tags' => str_replace(',', ' ', $tags) 1281 'tags' => str_replace(',', ' ', $tags),
1282 'shorturl' => link_small_hash($created, $id),
1278 ); 1283 );
1284
1279 // If title is empty, use the URL as title. 1285 // If title is empty, use the URL as title.
1280 if ($link['title'] == '') { 1286 if ($link['title'] == '') {
1281 $link['title'] = $link['url']; 1287 $link['title'] = $link['url'];
@@ -1283,7 +1289,7 @@ function renderPage($conf, $pluginManager)
1283 1289
1284 $pluginManager->executeHooks('save_link', $link); 1290 $pluginManager->executeHooks('save_link', $link);
1285 1291
1286 $LINKSDB[$linkdate] = $link; 1292 $LINKSDB[$id] = $link;
1287 $LINKSDB->save($conf->get('resource.page_cache')); 1293 $LINKSDB->save($conf->get('resource.page_cache'));
1288 pubsubhub($conf); 1294 pubsubhub($conf);
1289 1295
@@ -1296,7 +1302,7 @@ function renderPage($conf, $pluginManager)
1296 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; 1302 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
1297 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1303 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1298 // Scroll to the link which has been edited. 1304 // Scroll to the link which has been edited.
1299 $location .= '#' . smallHash($_POST['lf_linkdate']); 1305 $location .= '#' . $link['shorturl'];
1300 // After saving the link, redirect to the page the user was on. 1306 // After saving the link, redirect to the page the user was on.
1301 header('Location: '. $location); 1307 header('Location: '. $location);
1302 exit; 1308 exit;
@@ -1307,8 +1313,10 @@ function renderPage($conf, $pluginManager)
1307 { 1313 {
1308 // If we are called from the bookmarklet, we must close the popup: 1314 // If we are called from the bookmarklet, we must close the popup:
1309 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1315 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
1316 $link = $LINKSDB[(int) escape($_POST['lf_id'])];
1310 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1317 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1311 $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. 1318 // Scroll to the link which has been edited.
1319 $returnurl .= '#'. $link['shorturl'];
1312 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1320 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1313 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. 1321 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
1314 exit; 1322 exit;
@@ -1318,14 +1326,17 @@ function renderPage($conf, $pluginManager)
1318 if (isset($_POST['delete_link'])) 1326 if (isset($_POST['delete_link']))
1319 { 1327 {
1320 if (!tokenOk($_POST['token'])) die('Wrong token.'); 1328 if (!tokenOk($_POST['token'])) die('Wrong token.');
1329
1321 // We do not need to ask for confirmation: 1330 // We do not need to ask for confirmation:
1322 // - confirmation is handled by JavaScript 1331 // - confirmation is handled by JavaScript
1323 // - we are protected from XSRF by the token. 1332 // - we are protected from XSRF by the token.
1324 $linkdate=$_POST['lf_linkdate'];
1325 1333
1326 $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]); 1334 // FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
1335 $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
1336
1337 $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
1327 1338
1328 unset($LINKSDB[$linkdate]); 1339 unset($LINKSDB[$id]);
1329 $LINKSDB->save('resource.page_cache'); // save to disk 1340 $LINKSDB->save('resource.page_cache'); // save to disk
1330 1341
1331 // If we are called from the bookmarklet, we must close the popup: 1342 // If we are called from the bookmarklet, we must close the popup:
@@ -1364,8 +1375,10 @@ function renderPage($conf, $pluginManager)
1364 // -------- User clicked the "EDIT" button on a link: Display link edit form. 1375 // -------- User clicked the "EDIT" button on a link: Display link edit form.
1365 if (isset($_GET['edit_link'])) 1376 if (isset($_GET['edit_link']))
1366 { 1377 {
1367 $link = $LINKSDB[$_GET['edit_link']]; // Read database 1378 $id = (int) escape($_GET['edit_link']);
1379 $link = $LINKSDB[$id]; // Read database
1368 if (!$link) { header('Location: ?'); exit; } // Link not found in database. 1380 if (!$link) { header('Location: ?'); exit; } // Link not found in database.
1381 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1369 $data = array( 1382 $data = array(
1370 'link' => $link, 1383 'link' => $link,
1371 'link_is_new' => false, 1384 'link_is_new' => false,
@@ -1389,10 +1402,10 @@ function renderPage($conf, $pluginManager)
1389 $link_is_new = false; 1402 $link_is_new = false;
1390 // Check if URL is not already in database (in this case, we will edit the existing link) 1403 // Check if URL is not already in database (in this case, we will edit the existing link)
1391 $link = $LINKSDB->getLinkFromUrl($url); 1404 $link = $LINKSDB->getLinkFromUrl($url);
1392 if (!$link) 1405 if (! $link)
1393 { 1406 {
1394 $link_is_new = true; 1407 $link_is_new = true;
1395 $linkdate = strval(date('Ymd_His')); 1408 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
1396 // Get title if it was provided in URL (by the bookmarklet). 1409 // Get title if it was provided in URL (by the bookmarklet).
1397 $title = empty($_GET['title']) ? '' : escape($_GET['title']); 1410 $title = empty($_GET['title']) ? '' : escape($_GET['title']);
1398 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] 1411 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
@@ -1416,7 +1429,7 @@ function renderPage($conf, $pluginManager)
1416 } 1429 }
1417 1430
1418 if ($url == '') { 1431 if ($url == '') {
1419 $url = '?' . smallHash($linkdate); 1432 $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
1420 $title = 'Note: '; 1433 $title = 'Note: ';
1421 } 1434 }
1422 $url = escape($url); 1435 $url = escape($url);
@@ -1430,6 +1443,8 @@ function renderPage($conf, $pluginManager)
1430 'tags' => $tags, 1443 'tags' => $tags,
1431 'private' => $private 1444 'private' => $private
1432 ); 1445 );
1446 } else {
1447 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1433 } 1448 }
1434 1449
1435 $data = array( 1450 $data = array(
@@ -1635,18 +1650,15 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1635 $link['description'] = format_description($link['description'], $conf->get('redirector.url')); 1650 $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
1636 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; 1651 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
1637 $link['class'] = $link['private'] == 0 ? $classLi : 'private'; 1652 $link['class'] = $link['private'] == 0 ? $classLi : 'private';
1638 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 1653 $link['timestamp'] = $link['created']->getTimestamp();
1639 $link['timestamp'] = $date->getTimestamp();
1640 if (! empty($link['updated'])) { 1654 if (! empty($link['updated'])) {
1641 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']); 1655 $link['updated_timestamp'] = $link['updated']->getTimestamp();
1642 $link['updated_timestamp'] = $date->getTimestamp();
1643 } else { 1656 } else {
1644 $link['updated_timestamp'] = ''; 1657 $link['updated_timestamp'] = '';
1645 } 1658 }
1646 $taglist = explode(' ', $link['tags']); 1659 $taglist = explode(' ', $link['tags']);
1647 uasort($taglist, 'strcasecmp'); 1660 uasort($taglist, 'strcasecmp');
1648 $link['taglist'] = $taglist; 1661 $link['taglist'] = $taglist;
1649 $link['shorturl'] = smallHash($link['linkdate']);
1650 // Check for both signs of a note: starting with ? and 7 chars long. 1662 // Check for both signs of a note: starting with ? and 7 chars long.
1651 if ($link['url'][0] === '?' && 1663 if ($link['url'][0] === '?' &&
1652 strlen($link['url']) === 7) { 1664 strlen($link['url']) === 7) {
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php
index ffb7cfac..ce16645f 100644
--- a/plugins/isso/isso.php
+++ b/plugins/isso/isso.php
@@ -41,9 +41,9 @@ function hook_isso_render_linklist($data, $conf)
41 // Only display comments for permalinks. 41 // Only display comments for permalinks.
42 if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) { 42 if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
43 $link = reset($data['links']); 43 $link = reset($data['links']);
44 $isso_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html'); 44 $issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
45 45
46 $isso = sprintf($isso_html, $issoUrl, $issoUrl, $link['linkdate'], $link['linkdate']); 46 $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
47 $data['plugin_end_zone'][] = $isso; 47 $data['plugin_end_zone'][] = $isso;
48 48
49 // Hackish way to include this CSS file only when necessary. 49 // Hackish way to include this CSS file only when necessary.
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index d7839402..06a44506 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -84,8 +84,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
85 85
86 // Test first link (note link) 86 // Test first link (note link)
87 $link = array_shift($data['links']); 87 $link = reset($data['links']);
88 $this->assertEquals('20150310_114651', $link['linkdate']); 88 $this->assertEquals(41, $link['id']);
89 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 90 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
90 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 91 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
91 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); 92 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
@@ -99,14 +100,14 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
99 $this->assertEquals('sTuff', $link['taglist'][0]); 100 $this->assertEquals('sTuff', $link['taglist'][0]);
100 101
101 // Test URL with external link. 102 // Test URL with external link.
102 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']); 103 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
103 104
104 // Test multitags. 105 // Test multitags.
105 $this->assertEquals(5, count($data['links']['20141125_084734']['taglist'])); 106 $this->assertEquals(5, count($data['links'][6]['taglist']));
106 $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]); 107 $this->assertEquals('css', $data['links'][6]['taglist'][0]);
107 108
108 // Test update date 109 // Test update date
109 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']); 110 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
110 } 111 }
111 112
112 /** 113 /**
@@ -119,9 +120,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
119 $data = $feedBuilder->buildData(); 120 $data = $feedBuilder->buildData();
120 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 121 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
121 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); 122 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
122 $link = array_shift($data['links']); 123 $link = reset($data['links']);
123 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']); 124 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
124 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']); 125 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
125 } 126 }
126 127
127 /** 128 /**
@@ -138,7 +139,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
138 $data = $feedBuilder->buildData(); 139 $data = $feedBuilder->buildData();
139 $this->assertEquals(1, count($data['links'])); 140 $this->assertEquals(1, count($data['links']));
140 $link = array_shift($data['links']); 141 $link = array_shift($data['links']);
141 $this->assertEquals('20150310_114651', $link['linkdate']); 142 $this->assertEquals(41, $link['id']);
143 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
142 } 144 }
143 145
144 /** 146 /**
@@ -154,7 +156,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
154 $data = $feedBuilder->buildData(); 156 $data = $feedBuilder->buildData();
155 $this->assertEquals(1, count($data['links'])); 157 $this->assertEquals(1, count($data['links']));
156 $link = array_shift($data['links']); 158 $link = array_shift($data['links']);
157 $this->assertEquals('20150310_114651', $link['linkdate']); 159 $this->assertEquals(41, $link['id']);
160 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
158 } 161 }
159 162
160 /** 163 /**
@@ -170,15 +173,17 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
170 $this->assertTrue($data['usepermalinks']); 173 $this->assertTrue($data['usepermalinks']);
171 // First link is a permalink 174 // First link is a permalink
172 $link = array_shift($data['links']); 175 $link = array_shift($data['links']);
173 $this->assertEquals('20150310_114651', $link['linkdate']); 176 $this->assertEquals(41, $link['id']);
177 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
174 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 178 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
175 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 179 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
176 $this->assertContains('Direct link', $link['description']); 180 $this->assertContains('Direct link', $link['description']);
177 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 181 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
178 // Second link is a direct link 182 // Second link is a direct link
179 $link = array_shift($data['links']); 183 $link = array_shift($data['links']);
180 $this->assertEquals('20150310_114633', $link['linkdate']); 184 $this->assertEquals(8, $link['id']);
181 $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']); 185 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
186 $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
182 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); 187 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
183 $this->assertContains('Direct link', $link['description']); 188 $this->assertContains('Direct link', $link['description']);
184 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); 189 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 9d79386c..1f62a34a 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -186,14 +186,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
186 $dbSize = sizeof($testDB); 186 $dbSize = sizeof($testDB);
187 187
188 $link = array( 188 $link = array(
189 'id' => 42,
189 'title'=>'an additional link', 190 'title'=>'an additional link',
190 'url'=>'http://dum.my', 191 'url'=>'http://dum.my',
191 'description'=>'One more', 192 'description'=>'One more',
192 'private'=>0, 193 'private'=>0,
193 'linkdate'=>'20150518_190000', 194 'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
194 'tags'=>'unit test' 195 'tags'=>'unit test'
195 ); 196 );
196 $testDB[$link['linkdate']] = $link; 197 $testDB[$link['id']] = $link;
197 $testDB->save('tests'); 198 $testDB->save('tests');
198 199
199 $testDB = new LinkDB(self::$testDatastore, true, false); 200 $testDB = new LinkDB(self::$testDatastore, true, false);
@@ -238,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
238 public function testDays() 239 public function testDays()
239 { 240 {
240 $this->assertEquals( 241 $this->assertEquals(
241 array('20121206', '20130614', '20150310'), 242 array('20100310', '20121206', '20130614', '20150310'),
242 self::$publicLinkDB->days() 243 self::$publicLinkDB->days()
243 ); 244 );
244 245
245 $this->assertEquals( 246 $this->assertEquals(
246 array('20121206', '20130614', '20141125', '20150310'), 247 array('20100310', '20121206', '20130614', '20141125', '20150310'),
247 self::$privateLinkDB->days() 248 self::$privateLinkDB->days()
248 ); 249 );
249 } 250 }
@@ -290,10 +291,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
290 'stallman' => 1, 291 'stallman' => 1,
291 'free' => 1, 292 'free' => 1,
292 '-exclude' => 1, 293 '-exclude' => 1,
294 'hashtag' => 2,
293 // The DB contains a link with `sTuff` and another one with `stuff` tag. 295 // The DB contains a link with `sTuff` and another one with `stuff` tag.
294 // They need to be grouped with the first case found (`sTuff`). 296 // They need to be grouped with the first case found - order by date DESC: `sTuff`.
295 'sTuff' => 2, 297 'sTuff' => 2,
296 'hashtag' => 2, 298 'ut' => 1,
297 ), 299 ),
298 self::$publicLinkDB->allTags() 300 self::$publicLinkDB->allTags()
299 ); 301 );
@@ -321,6 +323,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
321 'tag2' => 1, 323 'tag2' => 1,
322 'tag3' => 1, 324 'tag3' => 1,
323 'tag4' => 1, 325 'tag4' => 1,
326 'ut' => 1,
324 ), 327 ),
325 self::$privateLinkDB->allTags() 328 self::$privateLinkDB->allTags()
326 ); 329 );
@@ -411,6 +414,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
411 1, 414 1,
412 count(self::$publicLinkDB->filterHash($request)) 415 count(self::$publicLinkDB->filterHash($request))
413 ); 416 );
417 $request = smallHash('20150310_114633' . 8);
418 $this->assertEquals(
419 1,
420 count(self::$publicLinkDB->filterHash($request))
421 );
414 } 422 }
415 423
416 /** 424 /**
@@ -433,4 +441,23 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
433 { 441 {
434 self::$publicLinkDB->filterHash(''); 442 self::$publicLinkDB->filterHash('');
435 } 443 }
444
445 /**
446 * Test reorder with asc/desc parameter.
447 */
448 public function testReorderLinksDesc()
449 {
450 self::$privateLinkDB->reorder('ASC');
451 $linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
452 $cpt = 0;
453 foreach (self::$privateLinkDB as $key => $value) {
454 $this->assertEquals($linkIds[$cpt++], $key);
455 }
456 self::$privateLinkDB->reorder('DESC');
457 $linkIds = array_reverse($linkIds);
458 $cpt = 0;
459 foreach (self::$privateLinkDB as $key => $value) {
460 $this->assertEquals($linkIds[$cpt++], $key);
461 }
462 }
436} 463}
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 7d45fc59..21d680a5 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -159,7 +159,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
159 159
160 $this->assertEquals( 160 $this->assertEquals(
161 'MediaGoblin', 161 'MediaGoblin',
162 $links['20130614_184135']['title'] 162 $links[7]['title']
163 ); 163 );
164 } 164 }
165 165
@@ -286,7 +286,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
286 ); 286 );
287 287
288 $this->assertEquals( 288 $this->assertEquals(
289 6, 289 7,
290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution')) 290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
291 ); 291 );
292 } 292 }
@@ -346,7 +346,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
346 ); 346 );
347 347
348 $this->assertEquals( 348 $this->assertEquals(
349 6, 349 7,
350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free')) 350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
351 ); 351 );
352 } 352 }
diff --git a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
index cc54ab9f..6a47bbb9 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
@@ -50,7 +50,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, ''); 50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); 51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links));
52 foreach ($links as $link) { 52 foreach ($links as $link) {
53 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 53 $date = $link['created'];
54 $this->assertEquals( 54 $this->assertEquals(
55 $date->getTimestamp(), 55 $date->getTimestamp(),
56 $link['timestamp'] 56 $link['timestamp']
@@ -70,7 +70,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, ''); 70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); 71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
72 foreach ($links as $link) { 72 foreach ($links as $link) {
73 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 73 $date = $link['created'];
74 $this->assertEquals( 74 $this->assertEquals(
75 $date->getTimestamp(), 75 $date->getTimestamp(),
76 $link['timestamp'] 76 $link['timestamp']
@@ -90,7 +90,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); 90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); 91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
92 foreach ($links as $link) { 92 foreach ($links as $link) {
93 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 93 $date = $link['created'];
94 $this->assertEquals( 94 $this->assertEquals(
95 $date->getTimestamp(), 95 $date->getTimestamp(),
96 $link['timestamp'] 96 $link['timestamp']
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index f0ad500f..0ca07eac 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -43,6 +43,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
43 protected $pagecache = 'tests'; 43 protected $pagecache = 'tests';
44 44
45 /** 45 /**
46 * @var string Save the current timezone.
47 */
48 protected static $defaultTimeZone;
49
50 public static function setUpBeforeClass()
51 {
52 self::$defaultTimeZone = date_default_timezone_get();
53 // Timezone without DST for test consistency
54 date_default_timezone_set('Africa/Nairobi');
55 }
56
57 /**
46 * Resets test data before each test 58 * Resets test data before each test
47 */ 59 */
48 protected function setUp() 60 protected function setUp()
@@ -55,6 +67,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
55 $this->linkDb = new LinkDB(self::$testDatastore, true, false); 67 $this->linkDb = new LinkDB(self::$testDatastore, true, false);
56 } 68 }
57 69
70 public static function tearDownAfterClass()
71 {
72 date_default_timezone_set(self::$defaultTimeZone);
73 }
74
58 /** 75 /**
59 * Attempt to import bookmarks from an empty file 76 * Attempt to import bookmarks from an empty file
60 */ 77 */
@@ -98,18 +115,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
98 115
99 $this->assertEquals( 116 $this->assertEquals(
100 array( 117 array(
101 'linkdate' => '20160618_173944', 118 'id' => 0,
119 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
102 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky', 120 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
103 'url' => 'http://hginit.com/', 121 'url' => 'http://hginit.com/',
104 'description' => '', 122 'description' => '',
105 'private' => 0, 123 'private' => 0,
106 'tags' => '' 124 'tags' => '',
125 'shorturl' => 'La37cg',
107 ), 126 ),
108 $this->linkDb->getLinkFromUrl('http://hginit.com/') 127 $this->linkDb->getLinkFromUrl('http://hginit.com/')
109 ); 128 );
110 } 129 }
111 130
112
113 /** 131 /**
114 * Import bookmarks nested in a folder hierarchy 132 * Import bookmarks nested in a folder hierarchy
115 */ 133 */
@@ -126,89 +144,105 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
126 144
127 $this->assertEquals( 145 $this->assertEquals(
128 array( 146 array(
129 'linkdate' => '20160225_205541', 147 'id' => 0,
148 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
130 'title' => 'Nested 1', 149 'title' => 'Nested 1',
131 'url' => 'http://nest.ed/1', 150 'url' => 'http://nest.ed/1',
132 'description' => '', 151 'description' => '',
133 'private' => 0, 152 'private' => 0,
134 'tags' => 'tag1 tag2' 153 'tags' => 'tag1 tag2',
154 'shorturl' => 'KyDNKA',
135 ), 155 ),
136 $this->linkDb->getLinkFromUrl('http://nest.ed/1') 156 $this->linkDb->getLinkFromUrl('http://nest.ed/1')
137 ); 157 );
138 $this->assertEquals( 158 $this->assertEquals(
139 array( 159 array(
140 'linkdate' => '20160225_205542', 160 'id' => 1,
161 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
141 'title' => 'Nested 1-1', 162 'title' => 'Nested 1-1',
142 'url' => 'http://nest.ed/1-1', 163 'url' => 'http://nest.ed/1-1',
143 'description' => '', 164 'description' => '',
144 'private' => 0, 165 'private' => 0,
145 'tags' => 'folder1 tag1 tag2' 166 'tags' => 'folder1 tag1 tag2',
167 'shorturl' => 'T2LnXg',
146 ), 168 ),
147 $this->linkDb->getLinkFromUrl('http://nest.ed/1-1') 169 $this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
148 ); 170 );
149 $this->assertEquals( 171 $this->assertEquals(
150 array( 172 array(
151 'linkdate' => '20160225_205547', 173 'id' => 2,
174 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
152 'title' => 'Nested 1-2', 175 'title' => 'Nested 1-2',
153 'url' => 'http://nest.ed/1-2', 176 'url' => 'http://nest.ed/1-2',
154 'description' => '', 177 'description' => '',
155 'private' => 0, 178 'private' => 0,
156 'tags' => 'folder1 tag3 tag4' 179 'tags' => 'folder1 tag3 tag4',
180 'shorturl' => '46SZxA',
157 ), 181 ),
158 $this->linkDb->getLinkFromUrl('http://nest.ed/1-2') 182 $this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
159 ); 183 );
160 $this->assertEquals( 184 $this->assertEquals(
161 array( 185 array(
162 'linkdate' => '20160202_172222', 186 'id' => 3,
187 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
163 'title' => 'Nested 2-1', 188 'title' => 'Nested 2-1',
164 'url' => 'http://nest.ed/2-1', 189 'url' => 'http://nest.ed/2-1',
165 'description' => 'First link of the second section', 190 'description' => 'First link of the second section',
166 'private' => 1, 191 'private' => 1,
167 'tags' => 'folder2' 192 'tags' => 'folder2',
193 'shorturl' => '4UHOSw',
168 ), 194 ),
169 $this->linkDb->getLinkFromUrl('http://nest.ed/2-1') 195 $this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
170 ); 196 );
171 $this->assertEquals( 197 $this->assertEquals(
172 array( 198 array(
173 'linkdate' => '20160119_200227', 199 'id' => 4,
200 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
174 'title' => 'Nested 2-2', 201 'title' => 'Nested 2-2',
175 'url' => 'http://nest.ed/2-2', 202 'url' => 'http://nest.ed/2-2',
176 'description' => 'Second link of the second section', 203 'description' => 'Second link of the second section',
177 'private' => 1, 204 'private' => 1,
178 'tags' => 'folder2' 205 'tags' => 'folder2',
206 'shorturl' => 'yfzwbw',
179 ), 207 ),
180 $this->linkDb->getLinkFromUrl('http://nest.ed/2-2') 208 $this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
181 ); 209 );
182 $this->assertEquals( 210 $this->assertEquals(
183 array( 211 array(
184 'linkdate' => '20160202_172223', 212 'id' => 5,
213 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
185 'title' => 'Nested 3-1', 214 'title' => 'Nested 3-1',
186 'url' => 'http://nest.ed/3-1', 215 'url' => 'http://nest.ed/3-1',
187 'description' => '', 216 'description' => '',
188 'private' => 0, 217 'private' => 0,
189 'tags' => 'folder3 folder3-1 tag3' 218 'tags' => 'folder3 folder3-1 tag3',
219 'shorturl' => 'UwxIUQ',
190 ), 220 ),
191 $this->linkDb->getLinkFromUrl('http://nest.ed/3-1') 221 $this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
192 ); 222 );
193 $this->assertEquals( 223 $this->assertEquals(
194 array( 224 array(
195 'linkdate' => '20160119_200228', 225 'id' => 6,
226 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
196 'title' => 'Nested 3-2', 227 'title' => 'Nested 3-2',
197 'url' => 'http://nest.ed/3-2', 228 'url' => 'http://nest.ed/3-2',
198 'description' => '', 229 'description' => '',
199 'private' => 0, 230 'private' => 0,
200 'tags' => 'folder3 folder3-1' 231 'tags' => 'folder3 folder3-1',
232 'shorturl' => 'p8dyZg',
201 ), 233 ),
202 $this->linkDb->getLinkFromUrl('http://nest.ed/3-2') 234 $this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
203 ); 235 );
204 $this->assertEquals( 236 $this->assertEquals(
205 array( 237 array(
206 'linkdate' => '20160229_081541', 238 'id' => 7,
239 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
207 'title' => 'Nested 2', 240 'title' => 'Nested 2',
208 'url' => 'http://nest.ed/2', 241 'url' => 'http://nest.ed/2',
209 'description' => '', 242 'description' => '',
210 'private' => 0, 243 'private' => 0,
211 'tags' => 'tag4' 244 'tags' => 'tag4',
245 'shorturl' => 'Gt3Uug',
212 ), 246 ),
213 $this->linkDb->getLinkFromUrl('http://nest.ed/2') 247 $this->linkDb->getLinkFromUrl('http://nest.ed/2')
214 ); 248 );
@@ -227,28 +261,34 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
227 .' 2 links imported, 0 links overwritten, 0 links skipped.', 261 .' 2 links imported, 0 links overwritten, 0 links skipped.',
228 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache) 262 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
229 ); 263 );
264
230 $this->assertEquals(2, count($this->linkDb)); 265 $this->assertEquals(2, count($this->linkDb));
231 $this->assertEquals(1, count_private($this->linkDb)); 266 $this->assertEquals(1, count_private($this->linkDb));
232 267
233 $this->assertEquals( 268 $this->assertEquals(
234 array( 269 array(
235 'linkdate' => '20001010_105536', 270 'id' => 0,
271 // Old link - UTC+4 (note that TZ in the import file is ignored).
272 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
236 'title' => 'Secret stuff', 273 'title' => 'Secret stuff',
237 'url' => 'https://private.tld', 274 'url' => 'https://private.tld',
238 'description' => "Super-secret stuff you're not supposed to know about", 275 'description' => "Super-secret stuff you're not supposed to know about",
239 'private' => 1, 276 'private' => 1,
240 'tags' => 'private secret' 277 'tags' => 'private secret',
278 'shorturl' => 'EokDtA',
241 ), 279 ),
242 $this->linkDb->getLinkFromUrl('https://private.tld') 280 $this->linkDb->getLinkFromUrl('https://private.tld')
243 ); 281 );
244 $this->assertEquals( 282 $this->assertEquals(
245 array( 283 array(
246 'linkdate' => '20160225_205548', 284 'id' => 1,
285 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
247 'title' => 'Public stuff', 286 'title' => 'Public stuff',
248 'url' => 'http://public.tld', 287 'url' => 'http://public.tld',
249 'description' => '', 288 'description' => '',
250 'private' => 0, 289 'private' => 0,
251 'tags' => 'public hello world' 290 'tags' => 'public hello world',
291 'shorturl' => 'Er9ddA',
252 ), 292 ),
253 $this->linkDb->getLinkFromUrl('http://public.tld') 293 $this->linkDb->getLinkFromUrl('http://public.tld')
254 ); 294 );
@@ -271,23 +311,28 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
271 311
272 $this->assertEquals( 312 $this->assertEquals(
273 array( 313 array(
274 'linkdate' => '20001010_105536', 314 'id' => 0,
315 // Note that TZ in the import file is ignored.
316 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
275 'title' => 'Secret stuff', 317 'title' => 'Secret stuff',
276 'url' => 'https://private.tld', 318 'url' => 'https://private.tld',
277 'description' => "Super-secret stuff you're not supposed to know about", 319 'description' => "Super-secret stuff you're not supposed to know about",
278 'private' => 1, 320 'private' => 1,
279 'tags' => 'private secret' 321 'tags' => 'private secret',
322 'shorturl' => 'EokDtA',
280 ), 323 ),
281 $this->linkDb->getLinkFromUrl('https://private.tld') 324 $this->linkDb->getLinkFromUrl('https://private.tld')
282 ); 325 );
283 $this->assertEquals( 326 $this->assertEquals(
284 array( 327 array(
285 'linkdate' => '20160225_205548', 328 'id' => 1,
329 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
286 'title' => 'Public stuff', 330 'title' => 'Public stuff',
287 'url' => 'http://public.tld', 331 'url' => 'http://public.tld',
288 'description' => '', 332 'description' => '',
289 'private' => 0, 333 'private' => 0,
290 'tags' => 'public hello world' 334 'tags' => 'public hello world',
335 'shorturl' => 'Er9ddA',
291 ), 336 ),
292 $this->linkDb->getLinkFromUrl('http://public.tld') 337 $this->linkDb->getLinkFromUrl('http://public.tld')
293 ); 338 );
@@ -309,11 +354,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
309 $this->assertEquals(0, count_private($this->linkDb)); 354 $this->assertEquals(0, count_private($this->linkDb));
310 $this->assertEquals( 355 $this->assertEquals(
311 0, 356 0,
312 $this->linkDb['20001010_105536']['private'] 357 $this->linkDb[0]['private']
313 ); 358 );
314 $this->assertEquals( 359 $this->assertEquals(
315 0, 360 0,
316 $this->linkDb['20160225_205548']['private'] 361 $this->linkDb[1]['private']
317 ); 362 );
318 } 363 }
319 364
@@ -333,11 +378,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
333 $this->assertEquals(2, count_private($this->linkDb)); 378 $this->assertEquals(2, count_private($this->linkDb));
334 $this->assertEquals( 379 $this->assertEquals(
335 1, 380 1,
336 $this->linkDb['20001010_105536']['private'] 381 $this->linkDb['0']['private']
337 ); 382 );
338 $this->assertEquals( 383 $this->assertEquals(
339 1, 384 1,
340 $this->linkDb['20160225_205548']['private'] 385 $this->linkDb['1']['private']
341 ); 386 );
342 } 387 }
343 388
@@ -359,13 +404,12 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
359 $this->assertEquals(2, count_private($this->linkDb)); 404 $this->assertEquals(2, count_private($this->linkDb));
360 $this->assertEquals( 405 $this->assertEquals(
361 1, 406 1,
362 $this->linkDb['20001010_105536']['private'] 407 $this->linkDb[0]['private']
363 ); 408 );
364 $this->assertEquals( 409 $this->assertEquals(
365 1, 410 1,
366 $this->linkDb['20160225_205548']['private'] 411 $this->linkDb[1]['private']
367 ); 412 );
368
369 // re-import as public, enable overwriting 413 // re-import as public, enable overwriting
370 $post = array( 414 $post = array(
371 'privacy' => 'public', 415 'privacy' => 'public',
@@ -380,11 +424,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
380 $this->assertEquals(0, count_private($this->linkDb)); 424 $this->assertEquals(0, count_private($this->linkDb));
381 $this->assertEquals( 425 $this->assertEquals(
382 0, 426 0,
383 $this->linkDb['20001010_105536']['private'] 427 $this->linkDb[0]['private']
384 ); 428 );
385 $this->assertEquals( 429 $this->assertEquals(
386 0, 430 0,
387 $this->linkDb['20160225_205548']['private'] 431 $this->linkDb[1]['private']
388 ); 432 );
389 } 433 }
390 434
@@ -406,11 +450,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
406 $this->assertEquals(0, count_private($this->linkDb)); 450 $this->assertEquals(0, count_private($this->linkDb));
407 $this->assertEquals( 451 $this->assertEquals(
408 0, 452 0,
409 $this->linkDb['20001010_105536']['private'] 453 $this->linkDb['0']['private']
410 ); 454 );
411 $this->assertEquals( 455 $this->assertEquals(
412 0, 456 0,
413 $this->linkDb['20160225_205548']['private'] 457 $this->linkDb['1']['private']
414 ); 458 );
415 459
416 // re-import as private, enable overwriting 460 // re-import as private, enable overwriting
@@ -427,11 +471,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
427 $this->assertEquals(2, count_private($this->linkDb)); 471 $this->assertEquals(2, count_private($this->linkDb));
428 $this->assertEquals( 472 $this->assertEquals(
429 1, 473 1,
430 $this->linkDb['20001010_105536']['private'] 474 $this->linkDb['0']['private']
431 ); 475 );
432 $this->assertEquals( 476 $this->assertEquals(
433 1, 477 1,
434 $this->linkDb['20160225_205548']['private'] 478 $this->linkDb['1']['private']
435 ); 479 );
436 } 480 }
437 481
@@ -480,11 +524,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
480 $this->assertEquals(0, count_private($this->linkDb)); 524 $this->assertEquals(0, count_private($this->linkDb));
481 $this->assertEquals( 525 $this->assertEquals(
482 'tag1 tag2 tag3 private secret', 526 'tag1 tag2 tag3 private secret',
483 $this->linkDb['20001010_105536']['tags'] 527 $this->linkDb['0']['tags']
484 ); 528 );
485 $this->assertEquals( 529 $this->assertEquals(
486 'tag1 tag2 tag3 public hello world', 530 'tag1 tag2 tag3 public hello world',
487 $this->linkDb['20160225_205548']['tags'] 531 $this->linkDb['1']['tags']
488 ); 532 );
489 } 533 }
490 534
@@ -507,16 +551,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
507 $this->assertEquals(0, count_private($this->linkDb)); 551 $this->assertEquals(0, count_private($this->linkDb));
508 $this->assertEquals( 552 $this->assertEquals(
509 'tag1&amp; tag2 &quot;tag3&quot; private secret', 553 'tag1&amp; tag2 &quot;tag3&quot; private secret',
510 $this->linkDb['20001010_105536']['tags'] 554 $this->linkDb['0']['tags']
511 ); 555 );
512 $this->assertEquals( 556 $this->assertEquals(
513 'tag1&amp; tag2 &quot;tag3&quot; public hello world', 557 'tag1&amp; tag2 &quot;tag3&quot; public hello world',
514 $this->linkDb['20160225_205548']['tags'] 558 $this->linkDb['1']['tags']
515 ); 559 );
516 } 560 }
517 561
518 /** 562 /**
519 * Ensure each imported bookmark has a unique linkdate 563 * Ensure each imported bookmark has a unique id
520 * 564 *
521 * See https://github.com/shaarli/Shaarli/issues/351 565 * See https://github.com/shaarli/Shaarli/issues/351
522 */ 566 */
@@ -531,16 +575,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
531 $this->assertEquals(3, count($this->linkDb)); 575 $this->assertEquals(3, count($this->linkDb));
532 $this->assertEquals(0, count_private($this->linkDb)); 576 $this->assertEquals(0, count_private($this->linkDb));
533 $this->assertEquals( 577 $this->assertEquals(
534 '20160225_205548', 578 0,
535 $this->linkDb['20160225_205548']['linkdate'] 579 $this->linkDb[0]['id']
536 ); 580 );
537 $this->assertEquals( 581 $this->assertEquals(
538 '20160225_205549', 582 1,
539 $this->linkDb['20160225_205549']['linkdate'] 583 $this->linkDb[1]['id']
540 ); 584 );
541 $this->assertEquals( 585 $this->assertEquals(
542 '20160225_205550', 586 2,
543 $this->linkDb['20160225_205550']['linkdate'] 587 $this->linkDb[2]['id']
544 ); 588 );
545 } 589 }
546} 590}
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index 0d0ad922..4948fe52 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -214,6 +214,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
214 $refDB = new ReferenceLinkDB(); 214 $refDB = new ReferenceLinkDB();
215 $refDB->write(self::$testDatastore); 215 $refDB->write(self::$testDatastore);
216 $linkDB = new LinkDB(self::$testDatastore, true, false); 216 $linkDB = new LinkDB(self::$testDatastore, true, false);
217
217 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 218 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
218 $updater = new Updater(array(), $linkDB, $this->conf, true); 219 $updater = new Updater(array(), $linkDB, $this->conf, true);
219 $updater->updateMethodRenameDashTags(); 220 $updater->updateMethodRenameDashTags();
@@ -287,4 +288,101 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
287 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); 288 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
288 unlink($sandbox .'.json.php'); 289 unlink($sandbox .'.json.php');
289 } 290 }
291
292 /**
293 * Test updateMethodDatastoreIds().
294 */
295 public function testDatastoreIds()
296 {
297 $links = array(
298 '20121206_182539' => array(
299 'linkdate' => '20121206_182539',
300 'title' => 'Geek and Poke',
301 'url' => 'http://geek-and-poke.com/',
302 'description' => 'desc',
303 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ',
304 'updated' => '20121206_190301',
305 'private' => false,
306 ),
307 '20121206_172539' => array(
308 'linkdate' => '20121206_172539',
309 'title' => 'UserFriendly - Samba',
310 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306',
311 'description' => '',
312 'tags' => 'samba cartoon web',
313 'private' => false,
314 ),
315 '20121206_142300' => array(
316 'linkdate' => '20121206_142300',
317 'title' => 'UserFriendly - Web Designer',
318 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206',
319 'description' => 'Naming conventions... #private',
320 'tags' => 'samba cartoon web',
321 'private' => true,
322 ),
323 );
324 $refDB = new ReferenceLinkDB();
325 $refDB->setLinks($links);
326 $refDB->write(self::$testDatastore);
327 $linkDB = new LinkDB(self::$testDatastore, true, false);
328
329 $checksum = hash_file('sha1', self::$testDatastore);
330
331 $this->conf->set('resource.data_dir', 'sandbox');
332 $this->conf->set('resource.datastore', self::$testDatastore);
333
334 $updater = new Updater(array(), $linkDB, $this->conf, true);
335 $this->assertTrue($updater->updateMethodDatastoreIds());
336
337 $linkDB = new LinkDB(self::$testDatastore, true, false);
338
339 $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php');
340 $backup = $backup[0];
341
342 $this->assertFileExists($backup);
343 $this->assertEquals($checksum, hash_file('sha1', $backup));
344 unlink($backup);
345
346 $this->assertEquals(3, count($linkDB));
347 $this->assertTrue(isset($linkDB[0]));
348 $this->assertFalse(isset($linkDB[0]['linkdate']));
349 $this->assertEquals(0, $linkDB[0]['id']);
350 $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']);
351 $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']);
352 $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
353 $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
354 $this->assertTrue($linkDB[0]['private']);
355 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']);
356
357 $this->assertTrue(isset($linkDB[1]));
358 $this->assertFalse(isset($linkDB[1]['linkdate']));
359 $this->assertEquals(1, $linkDB[1]['id']);
360 $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
361 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']);
362
363 $this->assertTrue(isset($linkDB[2]));
364 $this->assertFalse(isset($linkDB[2]['linkdate']));
365 $this->assertEquals(2, $linkDB[2]['id']);
366 $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
367 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']);
368 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']);
369 }
370
371 /**
372 * Test updateMethodDatastoreIds() with the update already applied: nothing to do.
373 */
374 public function testDatastoreIdsNothingToDo()
375 {
376 $refDB = new ReferenceLinkDB();
377 $refDB->write(self::$testDatastore);
378 $linkDB = new LinkDB(self::$testDatastore, true, false);
379
380 $this->conf->set('resource.data_dir', 'sandbox');
381 $this->conf->set('resource.datastore', self::$testDatastore);
382
383 $checksum = hash_file('sha1', self::$testDatastore);
384 $updater = new Updater(array(), $linkDB, $this->conf, true);
385 $this->assertTrue($updater->updateMethodDatastoreIds());
386 $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore));
387 }
290} 388}
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php
index 1f545c7d..6b7904dd 100644
--- a/tests/plugins/PluginIssoTest.php
+++ b/tests/plugins/PluginIssoTest.php
@@ -47,12 +47,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
47 $conf->set('plugins.ISSO_SERVER', 'value'); 47 $conf->set('plugins.ISSO_SERVER', 'value');
48 48
49 $str = 'http://randomstr.com/test'; 49 $str = 'http://randomstr.com/test';
50 $date = '20161118_100001';
50 $data = array( 51 $data = array(
51 'title' => $str, 52 'title' => $str,
52 'links' => array( 53 'links' => array(
53 array( 54 array(
55 'id' => 12,
54 'url' => $str, 56 'url' => $str,
55 'linkdate' => 'abc', 57 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
56 ) 58 )
57 ) 59 )
58 ); 60 );
@@ -65,7 +67,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
65 67
66 // plugin data 68 // plugin data
67 $this->assertEquals(1, count($data['plugin_end_zone'])); 69 $this->assertEquals(1, count($data['plugin_end_zone']));
68 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'abc')); 70 $this->assertNotFalse(strpos(
71 $data['plugin_end_zone'][0],
72 'data-isso-id="'. $data['links'][0]['id'] .'"'
73 ));
74 $this->assertNotFalse(strpos(
75 $data['plugin_end_zone'][0],
76 'data-title="'. $data['links'][0]['id'] .'"'
77 ));
69 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js')); 78 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
70 } 79 }
71 80
@@ -78,16 +87,20 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
78 $conf->set('plugins.ISSO_SERVER', 'value'); 87 $conf->set('plugins.ISSO_SERVER', 'value');
79 88
80 $str = 'http://randomstr.com/test'; 89 $str = 'http://randomstr.com/test';
90 $date1 = '20161118_100001';
91 $date2 = '20161118_100002';
81 $data = array( 92 $data = array(
82 'title' => $str, 93 'title' => $str,
83 'links' => array( 94 'links' => array(
84 array( 95 array(
96 'id' => 12,
85 'url' => $str, 97 'url' => $str,
86 'linkdate' => 'abc', 98 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
87 ), 99 ),
88 array( 100 array(
101 'id' => 13,
89 'url' => $str . '2', 102 'url' => $str . '2',
90 'linkdate' => 'abc2', 103 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
91 ), 104 ),
92 ) 105 )
93 ); 106 );
@@ -106,12 +119,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
106 $conf->set('plugins.ISSO_SERVER', 'value'); 119 $conf->set('plugins.ISSO_SERVER', 'value');
107 120
108 $str = 'http://randomstr.com/test'; 121 $str = 'http://randomstr.com/test';
122 $date = '20161118_100001';
109 $data = array( 123 $data = array(
110 'title' => $str, 124 'title' => $str,
111 'links' => array( 125 'links' => array(
112 array( 126 array(
127 'id' => 12,
113 'url' => $str, 128 'url' => $str,
114 'linkdate' => 'abc', 129 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
115 ) 130 )
116 ), 131 ),
117 'search_term' => $str 132 'search_term' => $str
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index abca4656..36d58c68 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,7 +4,7 @@
4 */ 4 */
5class ReferenceLinkDB 5class ReferenceLinkDB
6{ 6{
7 public static $NB_LINKS_TOTAL = 7; 7 public static $NB_LINKS_TOTAL = 8;
8 8
9 private $_links = array(); 9 private $_links = array();
10 private $_publicCount = 0; 10 private $_publicCount = 0;
@@ -16,66 +16,87 @@ class ReferenceLinkDB
16 public function __construct() 16 public function __construct()
17 { 17 {
18 $this->addLink( 18 $this->addLink(
19 41,
19 'Link title: @website', 20 'Link title: @website',
20 '?WDWyig', 21 '?WDWyig',
21 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag', 22 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
22 0, 23 0,
23 '20150310_114651', 24 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
24 'sTuff' 25 'sTuff',
26 null,
27 'WDWyig'
25 ); 28 );
26 29
27 $this->addLink( 30 $this->addLink(
31 42,
32 'Note: I have a big ID but an old date',
33 '?WDWyig',
34 'Used to test links reordering.',
35 0,
36 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'),
37 'ut'
38 );
39
40 $this->addLink(
41 8,
28 'Free as in Freedom 2.0 @website', 42 'Free as in Freedom 2.0 @website',
29 'https://static.fsf.org/nosvn/faif-2.0.pdf', 43 'https://static.fsf.org/nosvn/faif-2.0.pdf',
30 'Richard Stallman and the Free Software Revolution. Read this. #hashtag', 44 'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
31 0, 45 0,
32 '20150310_114633', 46 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
33 'free gnu software stallman -exclude stuff hashtag', 47 'free gnu software stallman -exclude stuff hashtag',
34 '20160803_093033' 48 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
35 ); 49 );
36 50
37 $this->addLink( 51 $this->addLink(
52 7,
38 'MediaGoblin', 53 'MediaGoblin',
39 'http://mediagoblin.org/', 54 'http://mediagoblin.org/',
40 'A free software media publishing platform #hashtagOther', 55 'A free software media publishing platform #hashtagOther',
41 0, 56 0,
42 '20130614_184135', 57 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
43 'gnu media web .hidden hashtag' 58 'gnu media web .hidden hashtag',
59 null,
60 'IuWvgA'
44 ); 61 );
45 62
46 $this->addLink( 63 $this->addLink(
64 6,
47 'w3c-markup-validator', 65 'w3c-markup-validator',
48 'https://dvcs.w3.org/hg/markup-validator/summary', 66 'https://dvcs.w3.org/hg/markup-validator/summary',
49 'Mercurial repository for the W3C Validator #private', 67 'Mercurial repository for the W3C Validator #private',
50 1, 68 1,
51 '20141125_084734', 69 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
52 'css html w3c web Mercurial' 70 'css html w3c web Mercurial'
53 ); 71 );
54 72
55 $this->addLink( 73 $this->addLink(
74 4,
56 'UserFriendly - Web Designer', 75 'UserFriendly - Web Designer',
57 'http://ars.userfriendly.org/cartoons/?id=20121206', 76 'http://ars.userfriendly.org/cartoons/?id=20121206',
58 'Naming conventions... #private', 77 'Naming conventions... #private',
59 0, 78 0,
60 '20121206_142300', 79 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
61 'dev cartoon web' 80 'dev cartoon web'
62 ); 81 );
63 82
64 $this->addLink( 83 $this->addLink(
84 1,
65 'UserFriendly - Samba', 85 'UserFriendly - Samba',
66 'http://ars.userfriendly.org/cartoons/?id=20010306', 86 'http://ars.userfriendly.org/cartoons/?id=20010306',
67 'Tropical printing', 87 'Tropical printing',
68 0, 88 0,
69 '20121206_172539', 89 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
70 'samba cartoon web' 90 'samba cartoon web'
71 ); 91 );
72 92
73 $this->addLink( 93 $this->addLink(
94 0,
74 'Geek and Poke', 95 'Geek and Poke',
75 'http://geek-and-poke.com/', 96 'http://geek-and-poke.com/',
76 '', 97 '',
77 1, 98 1,
78 '20121206_182539', 99 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
79 'dev cartoon tag1 tag2 tag3 tag4 ' 100 'dev cartoon tag1 tag2 tag3 tag4 '
80 ); 101 );
81 } 102 }
@@ -83,18 +104,20 @@ class ReferenceLinkDB
83 /** 104 /**
84 * Adds a new link 105 * Adds a new link
85 */ 106 */
86 protected function addLink($title, $url, $description, $private, $date, $tags, $updated = '') 107 protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '')
87 { 108 {
88 $link = array( 109 $link = array(
110 'id' => $id,
89 'title' => $title, 111 'title' => $title,
90 'url' => $url, 112 'url' => $url,
91 'description' => $description, 113 'description' => $description,
92 'private' => $private, 114 'private' => $private,
93 'linkdate' => $date,
94 'tags' => $tags, 115 'tags' => $tags,
116 'created' => $date,
95 'updated' => $updated, 117 'updated' => $updated,
118 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
96 ); 119 );
97 $this->_links[$date] = $link; 120 $this->_links[$id] = $link;
98 121
99 if ($private) { 122 if ($private) {
100 $this->_privateCount++; 123 $this->_privateCount++;
@@ -142,4 +165,14 @@ class ReferenceLinkDB
142 { 165 {
143 return $this->_links; 166 return $this->_links;
144 } 167 }
168
169 /**
170 * Setter to override link creation.
171 *
172 * @param array $links List of links.
173 */
174 public function setLinks($links)
175 {
176 $this->_links = $links;
177 }
145} 178}
diff --git a/tpl/daily.html b/tpl/daily.html
index b82ad483..eba0af3b 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -49,13 +49,13 @@
49 {$link=$value} 49 {$link=$value}
50 <div class="dailyEntry"> 50 <div class="dailyEntry">
51 <div class="dailyEntryPermalink"> 51 <div class="dailyEntryPermalink">
52 <a href="?{$link.linkdate|smallHash}"> 52 <a href="?{$value.shorturl}">
53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink"> 53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
54 </a> 54 </a>
55 </div> 55 </div>
56 {if="!$hide_timestamps || isLoggedIn()"} 56 {if="!$hide_timestamps || isLoggedIn()"}
57 <div class="dailyEntryLinkdate"> 57 <div class="dailyEntryLinkdate">
58 <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a> 58 <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
59 </div> 59 </div>
60 {/if} 60 {/if}
61 {if="$link.tags"} 61 {if="$link.tags"}
diff --git a/tpl/editlink.html b/tpl/editlink.html
index 9e7621db..870cc168 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -16,6 +16,9 @@
16 <div id="editlinkform"> 16 <div id="editlinkform">
17 <form method="post" name="linkform"> 17 <form method="post" name="linkform">
18 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> 18 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
19 {if="isset($link.id)"}
20 <input type="hidden" name="lf_id" value="{$link.id}">
21 {/if}
19 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br> 22 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br>
20 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br> 23 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br>
21 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br> 24 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br>
diff --git a/tpl/linklist.html b/tpl/linklist.html
index 3fe86deb..0f1a5e8c 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -81,11 +81,11 @@
81 {if="isLoggedIn()"} 81 {if="isLoggedIn()"}
82 <div class="linkeditbuttons"> 82 <div class="linkeditbuttons">
83 <form method="GET" class="buttoneditform"> 83 <form method="GET" class="buttoneditform">
84 <input type="hidden" name="edit_link" value="{$value.linkdate}"> 84 <input type="hidden" name="edit_link" value="{$value.id}">
85 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit"> 85 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
86 </form><br> 86 </form><br>
87 <form method="POST" class="buttoneditform"> 87 <form method="POST" class="buttoneditform">
88 <input type="hidden" name="lf_linkdate" value="{$value.linkdate}"> 88 <input type="hidden" name="lf_linkdate" value="{$value.id}">
89 <input type="hidden" name="token" value="{$token}"> 89 <input type="hidden" name="token" value="{$token}">
90 <input type="hidden" name="delete_link"> 90 <input type="hidden" name="delete_link">
91 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete" 91 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
@@ -101,7 +101,7 @@
101 {if="!$hide_timestamps || isLoggedIn()"} 101 {if="!$hide_timestamps || isLoggedIn()"}
102 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'} 102 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
103 <span class="linkdate" title="Permalink"> 103 <span class="linkdate" title="Permalink">
104 <a href="?{$value.linkdate|smallHash}"> 104 <a href="?{$value.shorturl}">
105 <span title="{$updated}"> 105 <span title="{$updated}">
106 {function="strftime('%c', $value.timestamp)"} 106 {function="strftime('%c', $value.timestamp)"}
107 {if="$value.updated_timestamp"}*{/if} 107 {if="$value.updated_timestamp"}*{/if}