aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2016-11-28 15:30:17 +0100
committerArthurHoaro <arthur@hoa.ro>2016-12-12 03:02:01 +0100
commit29d108820f05615d5c36608fde86f64f0750531a (patch)
treed3d241f80ffbb8f26d3add96001bbf5f285689a1
parentbea80e43a3714663b0c32879f7bdf4fd19161b2e (diff)
downloadShaarli-29d108820f05615d5c36608fde86f64f0750531a.tar.gz
Shaarli-29d108820f05615d5c36608fde86f64f0750531a.tar.zst
Shaarli-29d108820f05615d5c36608fde86f64f0750531a.zip
Link ID refactoring
Links now use an incremental unique numeric identifier. This ID is persistent and must never change. ArrayAccess is used to match the link ID with the array keys (see the comment in LinkDB for more details) Key 'created' added, with creation date as a DateTime object. 'updated' is now also a DateTime.
-rw-r--r--application/LinkDB.php178
1 files changed, 135 insertions, 43 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php
index c8b162b6..e429ab4f 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
@@ -27,6 +27,19 @@
27 * - ArrayAccess: behaves like an associative array; 27 * - ArrayAccess: behaves like an associative array;
28 * - Countable: there is a count() method; 28 * - Countable: there is a count() method;
29 * - Iterator: usable in foreach () loops. 29 * - Iterator: usable in foreach () loops.
30 *
31 * ID mechanism:
32 * ArrayAccess is implemented in a way that will allow to access a link
33 * with the unique identifier ID directly with $link[ID].
34 * Note that it's not the real key of the link array attribute.
35 * This mechanism is in place to have persistent link IDs,
36 * even though the internal array is reordered by date.
37 * Example:
38 * - DB: link #1 (2010-01-01) link #2 (2016-01-01)
39 * - Order: #2 #1
40 * - Import links containing: link #3 (2013-01-01)
41 * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
42 * - Real order: #2 #3 #1
30 */ 43 */
31class LinkDB implements Iterator, Countable, ArrayAccess 44class LinkDB implements Iterator, Countable, ArrayAccess
32{ 45{
@@ -47,11 +60,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess
47 // - value: associative array (keys: title, description...) 60 // - value: associative array (keys: title, description...)
48 private $links; 61 private $links;
49 62
50 // List of all recorded URLs (key=url, value=linkdate) 63 // List of all recorded URLs (key=url, value=link offset)
51 // for fast reserve search (url-->linkdate) 64 // for fast reserve search (url-->link offset)
52 private $urls; 65 private $urls;
53 66
54 // List of linkdate keys (for the Iterator interface implementation) 67 /**
68 * @var array List of all links IDS mapped with their array offset.
69 * Map: id->offset.
70 */
71 protected $ids;
72
73 // List of offset keys (for the Iterator interface implementation)
55 private $keys; 74 private $keys;
56 75
57 // Position in the $this->keys array (for the Iterator interface) 76 // Position in the $this->keys array (for the Iterator interface)
@@ -121,14 +140,26 @@ class LinkDB implements Iterator, Countable, ArrayAccess
121 if (!$this->loggedIn) { 140 if (!$this->loggedIn) {
122 die('You are not authorized to add a link.'); 141 die('You are not authorized to add a link.');
123 } 142 }
124 if (empty($value['linkdate']) || empty($value['url'])) { 143 if (!isset($value['id']) || empty($value['url'])) {
125 die('Internal Error: A link should always have a linkdate and URL.'); 144 die('Internal Error: A link should always have an id and URL.');
126 } 145 }
127 if (empty($offset)) { 146 if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
128 die('You must specify a key.'); 147 die('You must specify an integer as a key.');
148 }
149 if (! empty($offset) && $offset !== $value['id']) {
150 die('Array offset and link ID must be equal.');
151 }
152
153 // If the link exists, we reuse the real offset, otherwise new entry
154 $existing = $this->getLinkOffset($offset);
155 if ($existing !== null) {
156 $offset = $existing;
157 } else {
158 $offset = count($this->links);
129 } 159 }
130 $this->links[$offset] = $value; 160 $this->links[$offset] = $value;
131 $this->urls[$value['url']]=$offset; 161 $this->urls[$value['url']] = $offset;
162 $this->ids[$value['id']] = $offset;
132 } 163 }
133 164
134 /** 165 /**
@@ -136,7 +167,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
136 */ 167 */
137 public function offsetExists($offset) 168 public function offsetExists($offset)
138 { 169 {
139 return array_key_exists($offset, $this->links); 170 return array_key_exists($this->getLinkOffset($offset), $this->links);
140 } 171 }
141 172
142 /** 173 /**
@@ -148,9 +179,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
148 // TODO: raise an exception 179 // TODO: raise an exception
149 die('You are not authorized to delete a link.'); 180 die('You are not authorized to delete a link.');
150 } 181 }
151 $url = $this->links[$offset]['url']; 182 $realOffset = $this->getLinkOffset($offset);
183 $url = $this->links[$realOffset]['url'];
152 unset($this->urls[$url]); 184 unset($this->urls[$url]);
153 unset($this->links[$offset]); 185 unset($this->ids[$realOffset]);
186 unset($this->links[$realOffset]);
154 } 187 }
155 188
156 /** 189 /**
@@ -158,7 +191,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
158 */ 191 */
159 public function offsetGet($offset) 192 public function offsetGet($offset)
160 { 193 {
161 return isset($this->links[$offset]) ? $this->links[$offset] : null; 194 $realOffset = $this->getLinkOffset($offset);
195 return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
162 } 196 }
163 197
164 /** 198 /**
@@ -166,7 +200,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
166 */ 200 */
167 public function current() 201 public function current()
168 { 202 {
169 return $this->links[$this->keys[$this->position]]; 203 return $this[$this->keys[$this->position]];
170 } 204 }
171 205
172 /** 206 /**
@@ -192,8 +226,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
192 */ 226 */
193 public function rewind() 227 public function rewind()
194 { 228 {
195 $this->keys = array_keys($this->links); 229 $this->keys = array_keys($this->ids);
196 rsort($this->keys);
197 $this->position = 0; 230 $this->position = 0;
198 } 231 }
199 232
@@ -219,6 +252,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
219 // Create a dummy database for example 252 // Create a dummy database for example
220 $this->links = array(); 253 $this->links = array();
221 $link = array( 254 $link = array(
255 'id' => 1,
222 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 256 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
223 'url'=>'https://github.com/shaarli/Shaarli/wiki', 257 '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. 258 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@@ -227,20 +261,21 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
227 261
228You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 262You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
229 'private'=>0, 263 'private'=>0,
230 'linkdate'=> date('Ymd_His'), 264 'created'=> new DateTime(),
231 'tags'=>'opensource software' 265 'tags'=>'opensource software'
232 ); 266 );
233 $this->links[$link['linkdate']] = $link; 267 $this->links[1] = $link;
234 268
235 $link = array( 269 $link = array(
270 'id' => 0,
236 'title'=>'My secret stuff... - Pastebin.com', 271 'title'=>'My secret stuff... - Pastebin.com',
237 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 272 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
238 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', 273 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
239 'private'=>1, 274 'private'=>1,
240 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 275 'created'=> new DateTime('1 minute ago'),
241 'tags'=>'secretstuff' 276 'tags'=>'secretstuff'
242 ); 277 );
243 $this->links[$link['linkdate']] = $link; 278 $this->links[0] = $link;
244 279
245 // Write database to disk 280 // Write database to disk
246 $this->write(); 281 $this->write();
@@ -251,7 +286,6 @@ You use the community supported version of the original Shaarli project, by Seba
251 */ 286 */
252 private function read() 287 private function read()
253 { 288 {
254
255 // Public links are hidden and user not logged in => nothing to show 289 // Public links are hidden and user not logged in => nothing to show
256 if ($this->hidePublicLinks && !$this->loggedIn) { 290 if ($this->hidePublicLinks && !$this->loggedIn) {
257 $this->links = array(); 291 $this->links = array();
@@ -269,23 +303,13 @@ You use the community supported version of the original Shaarli project, by Seba
269 strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); 303 strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
270 } 304 }
271 305
272 // If user is not logged in, filter private links. 306 $toremove = array();
273 if (!$this->loggedIn) { 307 foreach ($this->links as $key => &$link) {
274 $toremove = array(); 308 if (! $this->loggedIn && $link['private'] != 0) {
275 foreach ($this->links as $link) { 309 // Transition for not upgraded databases.
276 if ($link['private'] != 0) { 310 $toremove[] = $key;
277 $toremove[] = $link['linkdate']; 311 continue;
278 }
279 }
280 foreach ($toremove as $linkdate) {
281 unset($this->links[$linkdate]);
282 } 312 }
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 313
290 // Sanitize data fields. 314 // Sanitize data fields.
291 sanitizeLink($link); 315 sanitizeLink($link);
@@ -307,7 +331,23 @@ You use the community supported version of the original Shaarli project, by Seba
307 else { 331 else {
308 $link['real_url'] = $link['url']; 332 $link['real_url'] = $link['url'];
309 } 333 }
334
335 // To be able to load links before running the update, and prepare the update
336 if (! isset($link['created'])) {
337 $link['id'] = $link['linkdate'];
338 $link['created'] = DateTime::createFromFormat('Ymd_His', $link['linkdate']);
339 if (! empty($link['updated'])) {
340 $link['updated'] = DateTime::createFromFormat('Ymd_His', $link['updated']);
341 }
342 }
343 }
344
345 // If user is not logged in, filter private links.
346 foreach ($toremove as $offset) {
347 unset($this->links[$offset]);
310 } 348 }
349
350 $this->reorder();
311 } 351 }
312 352
313 /** 353 /**
@@ -430,7 +470,7 @@ You use the community supported version of the original Shaarli project, by Seba
430 $request = ''; 470 $request = '';
431 } 471 }
432 472
433 $linkFilter = new LinkFilter($this->links); 473 $linkFilter = new LinkFilter($this);
434 return $linkFilter->filter($type, $request, $casesensitive, $privateonly); 474 return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
435 } 475 }
436 476
@@ -467,12 +507,64 @@ You use the community supported version of the original Shaarli project, by Seba
467 public function days() 507 public function days()
468 { 508 {
469 $linkDays = array(); 509 $linkDays = array();
470 foreach (array_keys($this->links) as $day) { 510 foreach ($this->links as $link) {
471 $linkDays[substr($day, 0, 8)] = 0; 511 $linkDays[$link['created']->format('Ymd')] = 0;
472 } 512 }
473 $linkDays = array_keys($linkDays); 513 $linkDays = array_keys($linkDays);
474 sort($linkDays); 514 sort($linkDays);
475 515
476 return $linkDays; 516 return $linkDays;
477 } 517 }
518
519 /**
520 * Reorder links by creation date (newest first).
521 *
522 * Also update the urls and ids mapping arrays.
523 *
524 * @param string $order ASC|DESC
525 */
526 public function reorder($order = 'DESC')
527 {
528 $order = $order === 'ASC' ? -1 : 1;
529 // Reorder array by dates.
530 usort($this->links, function($a, $b) use ($order) {
531 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
532 });
533
534 $this->urls = array();
535 $this->ids = array();
536 foreach ($this->links as $key => $link) {
537 $this->urls[$link['url']] = $key;
538 $this->ids[$link['id']] = $key;
539 }
540 }
541
542 /**
543 * Return the next key for link creation.
544 * E.g. If the last ID is 597, the next will be 598.
545 *
546 * @return int next ID.
547 */
548 public function getNextId()
549 {
550 if (!empty($this->ids)) {
551 return max(array_keys($this->ids)) + 1;
552 }
553 return 0;
554 }
555
556 /**
557 * Returns a link offset in links array from its unique ID.
558 *
559 * @param int $id Persistent ID of a link.
560 *
561 * @return int Real offset in local array, or null if doesn't exists.
562 */
563 protected function getLinkOffset($id)
564 {
565 if (isset($this->ids[$id])) {
566 return $this->ids[$id];
567 }
568 return null;
569 }
478} 570}