aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/LinkDB.php
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 /application/LinkDB.php
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
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r--application/LinkDB.php184
1 files changed, 140 insertions, 44 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php
index c8b162b6..1e13286a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -6,15 +6,15 @@
6 * 6 *
7 * Example: 7 * Example:
8 * $myLinks = new LinkDB(); 8 * $myLinks = new LinkDB();
9 * echo $myLinks['20110826_161819']['title']; 9 * echo $myLinks[350]['title'];
10 * foreach ($myLinks as $link) 10 * foreach ($myLinks as $link)
11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; 11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
12 * 12 *
13 * Available keys: 13 * Available keys:
14 * - id: primary key, incremental integer identifier (persistent)
14 * - description: description of the entry 15 * - description: description of the entry
15 * - linkdate: creation date of this entry, format: YYYYMMDD_HHMMSS 16 * - created: creation date of this entry, DateTime object.
16 * (e.g.'20110914_192317') 17 * - updated: last modification date of this entry, DateTime object.
17 * - updated: last modification date of this entry, format: YYYYMMDD_HHMMSS
18 * - private: Is this link private? 0=no, other value=yes 18 * - private: Is this link private? 0=no, other value=yes
19 * - tags: tags attached to this entry (separated by spaces) 19 * - tags: tags attached to this entry (separated by spaces)
20 * - title Title of the link 20 * - title Title of the link
@@ -22,11 +22,25 @@
22 * Can be absolute or relative. 22 * Can be absolute or relative.
23 * Relative URLs are permalinks (e.g.'?m-ukcw') 23 * Relative URLs are permalinks (e.g.'?m-ukcw')
24 * - real_url Absolute processed URL. 24 * - real_url Absolute processed URL.
25 * - shorturl Permalink smallhash
25 * 26 *
26 * Implements 3 interfaces: 27 * Implements 3 interfaces:
27 * - ArrayAccess: behaves like an associative array; 28 * - ArrayAccess: behaves like an associative array;
28 * - Countable: there is a count() method; 29 * - Countable: there is a count() method;
29 * - Iterator: usable in foreach () loops. 30 * - Iterator: usable in foreach () loops.
31 *
32 * ID mechanism:
33 * ArrayAccess is implemented in a way that will allow to access a link
34 * with the unique identifier ID directly with $link[ID].
35 * Note that it's not the real key of the link array attribute.
36 * This mechanism is in place to have persistent link IDs,
37 * even though the internal array is reordered by date.
38 * Example:
39 * - DB: link #1 (2010-01-01) link #2 (2016-01-01)
40 * - Order: #2 #1
41 * - Import links containing: link #3 (2013-01-01)
42 * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
43 * - Real order: #2 #3 #1
30 */ 44 */
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}