aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/LinkDB.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r--application/LinkDB.php318
1 files changed, 211 insertions, 107 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 1cb70de0..1e13286a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -6,14 +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: date of the creation of this entry, in the form 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 * - private: Is this link private? 0=no, other value=yes 18 * - private: Is this link private? 0=no, other value=yes
18 * - tags: tags attached to this entry (separated by spaces) 19 * - tags: tags attached to this entry (separated by spaces)
19 * - title Title of the link 20 * - title Title of the link
@@ -21,16 +22,30 @@
21 * Can be absolute or relative. 22 * Can be absolute or relative.
22 * Relative URLs are permalinks (e.g.'?m-ukcw') 23 * Relative URLs are permalinks (e.g.'?m-ukcw')
23 * - real_url Absolute processed URL. 24 * - real_url Absolute processed URL.
25 * - shorturl Permalink smallhash
24 * 26 *
25 * Implements 3 interfaces: 27 * Implements 3 interfaces:
26 * - ArrayAccess: behaves like an associative array; 28 * - ArrayAccess: behaves like an associative array;
27 * - Countable: there is a count() method; 29 * - Countable: there is a count() method;
28 * - 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
29 */ 44 */
30class LinkDB implements Iterator, Countable, ArrayAccess 45class LinkDB implements Iterator, Countable, ArrayAccess
31{ 46{
32 // Links are stored as a PHP serialized string 47 // Links are stored as a PHP serialized string
33 private $_datastore; 48 private $datastore;
34 49
35 // Link date storage format 50 // Link date storage format
36 const LINK_DATE_FORMAT = 'Ymd_His'; 51 const LINK_DATE_FORMAT = 'Ymd_His';
@@ -44,26 +59,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess
44 // List of links (associative array) 59 // List of links (associative array)
45 // - key: link date (e.g. "20110823_124546"), 60 // - key: link date (e.g. "20110823_124546"),
46 // - value: associative array (keys: title, description...) 61 // - value: associative array (keys: title, description...)
47 private $_links; 62 private $links;
63
64 // List of all recorded URLs (key=url, value=link offset)
65 // for fast reserve search (url-->link offset)
66 private $urls;
48 67
49 // List of all recorded URLs (key=url, value=linkdate) 68 /**
50 // for fast reserve search (url-->linkdate) 69 * @var array List of all links IDS mapped with their array offset.
51 private $_urls; 70 * Map: id->offset.
71 */
72 protected $ids;
52 73
53 // List of linkdate keys (for the Iterator interface implementation) 74 // List of offset keys (for the Iterator interface implementation)
54 private $_keys; 75 private $keys;
55 76
56 // Position in the $this->_keys array (for the Iterator interface) 77 // Position in the $this->keys array (for the Iterator interface)
57 private $_position; 78 private $position;
58 79
59 // Is the user logged in? (used to filter private links) 80 // Is the user logged in? (used to filter private links)
60 private $_loggedIn; 81 private $loggedIn;
61 82
62 // Hide public links 83 // Hide public links
63 private $_hidePublicLinks; 84 private $hidePublicLinks;
64 85
65 // link redirector set in user settings. 86 // link redirector set in user settings.
66 private $_redirector; 87 private $redirector;
67 88
68 /** 89 /**
69 * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched. 90 * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched.
@@ -86,7 +107,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
86 * @param string $redirector link redirector set in user settings. 107 * @param string $redirector link redirector set in user settings.
87 * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true). 108 * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true).
88 */ 109 */
89 function __construct( 110 public function __construct(
90 $datastore, 111 $datastore,
91 $isLoggedIn, 112 $isLoggedIn,
92 $hidePublicLinks, 113 $hidePublicLinks,
@@ -94,13 +115,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess
94 $redirectorEncode = true 115 $redirectorEncode = true
95 ) 116 )
96 { 117 {
97 $this->_datastore = $datastore; 118 $this->datastore = $datastore;
98 $this->_loggedIn = $isLoggedIn; 119 $this->loggedIn = $isLoggedIn;
99 $this->_hidePublicLinks = $hidePublicLinks; 120 $this->hidePublicLinks = $hidePublicLinks;
100 $this->_redirector = $redirector; 121 $this->redirector = $redirector;
101 $this->redirectorEncode = $redirectorEncode === true; 122 $this->redirectorEncode = $redirectorEncode === true;
102 $this->_checkDB(); 123 $this->check();
103 $this->_readDB(); 124 $this->read();
104 } 125 }
105 126
106 /** 127 /**
@@ -108,7 +129,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
108 */ 129 */
109 public function count() 130 public function count()
110 { 131 {
111 return count($this->_links); 132 return count($this->links);
112 } 133 }
113 134
114 /** 135 /**
@@ -117,17 +138,29 @@ class LinkDB implements Iterator, Countable, ArrayAccess
117 public function offsetSet($offset, $value) 138 public function offsetSet($offset, $value)
118 { 139 {
119 // TODO: use exceptions instead of "die" 140 // TODO: use exceptions instead of "die"
120 if (!$this->_loggedIn) { 141 if (!$this->loggedIn) {
121 die('You are not authorized to add a link.'); 142 die('You are not authorized to add a link.');
122 } 143 }
123 if (empty($value['linkdate']) || empty($value['url'])) { 144 if (!isset($value['id']) || empty($value['url'])) {
124 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.');
146 }
147 if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
148 die('You must specify an integer as a key.');
125 } 149 }
126 if (empty($offset)) { 150 if (! empty($offset) && $offset !== $value['id']) {
127 die('You must specify a key.'); 151 die('Array offset and link ID must be equal.');
128 } 152 }
129 $this->_links[$offset] = $value; 153
130 $this->_urls[$value['url']]=$offset; 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);
160 }
161 $this->links[$offset] = $value;
162 $this->urls[$value['url']] = $offset;
163 $this->ids[$value['id']] = $offset;
131 } 164 }
132 165
133 /** 166 /**
@@ -135,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
135 */ 168 */
136 public function offsetExists($offset) 169 public function offsetExists($offset)
137 { 170 {
138 return array_key_exists($offset, $this->_links); 171 return array_key_exists($this->getLinkOffset($offset), $this->links);
139 } 172 }
140 173
141 /** 174 /**
@@ -143,13 +176,15 @@ class LinkDB implements Iterator, Countable, ArrayAccess
143 */ 176 */
144 public function offsetUnset($offset) 177 public function offsetUnset($offset)
145 { 178 {
146 if (!$this->_loggedIn) { 179 if (!$this->loggedIn) {
147 // TODO: raise an exception 180 // TODO: raise an exception
148 die('You are not authorized to delete a link.'); 181 die('You are not authorized to delete a link.');
149 } 182 }
150 $url = $this->_links[$offset]['url']; 183 $realOffset = $this->getLinkOffset($offset);
151 unset($this->_urls[$url]); 184 $url = $this->links[$realOffset]['url'];
152 unset($this->_links[$offset]); 185 unset($this->urls[$url]);
186 unset($this->ids[$realOffset]);
187 unset($this->links[$realOffset]);
153 } 188 }
154 189
155 /** 190 /**
@@ -157,31 +192,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess
157 */ 192 */
158 public function offsetGet($offset) 193 public function offsetGet($offset)
159 { 194 {
160 return isset($this->_links[$offset]) ? $this->_links[$offset] : null; 195 $realOffset = $this->getLinkOffset($offset);
196 return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
161 } 197 }
162 198
163 /** 199 /**
164 * Iterator - Returns the current element 200 * Iterator - Returns the current element
165 */ 201 */
166 function current() 202 public function current()
167 { 203 {
168 return $this->_links[$this->_keys[$this->_position]]; 204 return $this[$this->keys[$this->position]];
169 } 205 }
170 206
171 /** 207 /**
172 * Iterator - Returns the key of the current element 208 * Iterator - Returns the key of the current element
173 */ 209 */
174 function key() 210 public function key()
175 { 211 {
176 return $this->_keys[$this->_position]; 212 return $this->keys[$this->position];
177 } 213 }
178 214
179 /** 215 /**
180 * Iterator - Moves forward to next element 216 * Iterator - Moves forward to next element
181 */ 217 */
182 function next() 218 public function next()
183 { 219 {
184 ++$this->_position; 220 ++$this->position;
185 } 221 }
186 222
187 /** 223 /**
@@ -189,19 +225,18 @@ class LinkDB implements Iterator, Countable, ArrayAccess
189 * 225 *
190 * Entries are sorted by date (latest first) 226 * Entries are sorted by date (latest first)
191 */ 227 */
192 function rewind() 228 public function rewind()
193 { 229 {
194 $this->_keys = array_keys($this->_links); 230 $this->keys = array_keys($this->ids);
195 rsort($this->_keys); 231 $this->position = 0;
196 $this->_position = 0;
197 } 232 }
198 233
199 /** 234 /**
200 * Iterator - Checks if current position is valid 235 * Iterator - Checks if current position is valid
201 */ 236 */
202 function valid() 237 public function valid()
203 { 238 {
204 return isset($this->_keys[$this->_position]); 239 return isset($this->keys[$this->position]);
205 } 240 }
206 241
207 /** 242 /**
@@ -209,15 +244,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
209 * 244 *
210 * If no DB file is found, creates a dummy DB. 245 * If no DB file is found, creates a dummy DB.
211 */ 246 */
212 private function _checkDB() 247 private function check()
213 { 248 {
214 if (file_exists($this->_datastore)) { 249 if (file_exists($this->datastore)) {
215 return; 250 return;
216 } 251 }
217 252
218 // Create a dummy database for example 253 // Create a dummy database for example
219 $this->_links = array(); 254 $this->links = array();
220 $link = array( 255 $link = array(
256 'id' => 1,
221 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 257 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
222 'url'=>'https://github.com/shaarli/Shaarli/wiki', 258 'url'=>'https://github.com/shaarli/Shaarli/wiki',
223 '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.
@@ -226,77 +262,69 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
226 262
227You 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.',
228 'private'=>0, 264 'private'=>0,
229 'linkdate'=> date('Ymd_His'), 265 'created'=> new DateTime(),
230 'tags'=>'opensource software' 266 'tags'=>'opensource software'
231 ); 267 );
232 $this->_links[$link['linkdate']] = $link; 268 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
269 $this->links[1] = $link;
233 270
234 $link = array( 271 $link = array(
272 'id' => 0,
235 'title'=>'My secret stuff... - Pastebin.com', 273 'title'=>'My secret stuff... - Pastebin.com',
236 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 274 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
237 '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.',
238 'private'=>1, 276 'private'=>1,
239 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 277 'created'=> new DateTime('1 minute ago'),
240 'tags'=>'secretstuff' 278 'tags'=>'secretstuff',
241 ); 279 );
242 $this->_links[$link['linkdate']] = $link; 280 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
281 $this->links[0] = $link;
243 282
244 // Write database to disk 283 // Write database to disk
245 $this->writeDB(); 284 $this->write();
246 } 285 }
247 286
248 /** 287 /**
249 * Reads database from disk to memory 288 * Reads database from disk to memory
250 */ 289 */
251 private function _readDB() 290 private function read()
252 { 291 {
253
254 // 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
255 if ($this->_hidePublicLinks && !$this->_loggedIn) { 293 if ($this->hidePublicLinks && !$this->loggedIn) {
256 $this->_links = array(); 294 $this->links = array();
257 return; 295 return;
258 } 296 }
259 297
260 // Read data 298 // Read data
261 // Note that gzinflate is faster than gzuncompress. 299 // Note that gzinflate is faster than gzuncompress.
262 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 300 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
263 $this->_links = array(); 301 $this->links = array();
264 302
265 if (file_exists($this->_datastore)) { 303 if (file_exists($this->datastore)) {
266 $this->_links = unserialize(gzinflate(base64_decode( 304 $this->links = unserialize(gzinflate(base64_decode(
267 substr(file_get_contents($this->_datastore), 305 substr(file_get_contents($this->datastore),
268 strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); 306 strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
269 } 307 }
270 308
271 // If user is not logged in, filter private links. 309 $toremove = array();
272 if (!$this->_loggedIn) { 310 foreach ($this->links as $key => &$link) {
273 $toremove = array(); 311 if (! $this->loggedIn && $link['private'] != 0) {
274 foreach ($this->_links as $link) { 312 // Transition for not upgraded databases.
275 if ($link['private'] != 0) { 313 $toremove[] = $key;
276 $toremove[] = $link['linkdate']; 314 continue;
277 }
278 }
279 foreach ($toremove as $linkdate) {
280 unset($this->_links[$linkdate]);
281 } 315 }
282 }
283
284 $this->_urls = array();
285 foreach ($this->_links as &$link) {
286 // Keep the list of the mapping URLs-->linkdate up-to-date.
287 $this->_urls[$link['url']] = $link['linkdate'];
288 316
289 // Sanitize data fields. 317 // Sanitize data fields.
290 sanitizeLink($link); 318 sanitizeLink($link);
291 319
292 // Remove private tags if the user is not logged in. 320 // Remove private tags if the user is not logged in.
293 if (! $this->_loggedIn) { 321 if (! $this->loggedIn) {
294 $link['tags'] = preg_replace('/(^| )\.[^($| )]+/', '', $link['tags']); 322 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
295 } 323 }
296 324
297 // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). 325 // Do not use the redirector for internal links (Shaarli note URL starting with a '?').
298 if (!empty($this->_redirector) && !startsWith($link['url'], '?')) { 326 if (!empty($this->redirector) && !startsWith($link['url'], '?')) {
299 $link['real_url'] = $this->_redirector; 327 $link['real_url'] = $this->redirector;
300 if ($this->redirectorEncode) { 328 if ($this->redirectorEncode) {
301 $link['real_url'] .= urlencode(unescape($link['url'])); 329 $link['real_url'] .= urlencode(unescape($link['url']));
302 } else { 330 } else {
@@ -306,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba
306 else { 334 else {
307 $link['real_url'] = $link['url']; 335 $link['real_url'] = $link['url'];
308 } 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 }
309 } 347 }
348
349 // If user is not logged in, filter private links.
350 foreach ($toremove as $offset) {
351 unset($this->links[$offset]);
352 }
353
354 $this->reorder();
310 } 355 }
311 356
312 /** 357 /**
@@ -314,19 +359,19 @@ You use the community supported version of the original Shaarli project, by Seba
314 * 359 *
315 * @throws IOException the datastore is not writable 360 * @throws IOException the datastore is not writable
316 */ 361 */
317 private function writeDB() 362 private function write()
318 { 363 {
319 if (is_file($this->_datastore) && !is_writeable($this->_datastore)) { 364 if (is_file($this->datastore) && !is_writeable($this->datastore)) {
320 // The datastore exists but is not writeable 365 // The datastore exists but is not writeable
321 throw new IOException($this->_datastore); 366 throw new IOException($this->datastore);
322 } else if (!is_file($this->_datastore) && !is_writeable(dirname($this->_datastore))) { 367 } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
323 // The datastore does not exist and its parent directory is not writeable 368 // The datastore does not exist and its parent directory is not writeable
324 throw new IOException(dirname($this->_datastore)); 369 throw new IOException(dirname($this->datastore));
325 } 370 }
326 371
327 file_put_contents( 372 file_put_contents(
328 $this->_datastore, 373 $this->datastore,
329 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix 374 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
330 ); 375 );
331 376
332 } 377 }
@@ -336,14 +381,14 @@ You use the community supported version of the original Shaarli project, by Seba
336 * 381 *
337 * @param string $pageCacheDir page cache directory 382 * @param string $pageCacheDir page cache directory
338 */ 383 */
339 public function savedb($pageCacheDir) 384 public function save($pageCacheDir)
340 { 385 {
341 if (!$this->_loggedIn) { 386 if (!$this->loggedIn) {
342 // TODO: raise an Exception instead 387 // TODO: raise an Exception instead
343 die('You are not authorized to change the database.'); 388 die('You are not authorized to change the database.');
344 } 389 }
345 390
346 $this->writeDB(); 391 $this->write();
347 392
348 invalidateCaches($pageCacheDir); 393 invalidateCaches($pageCacheDir);
349 } 394 }
@@ -357,8 +402,8 @@ You use the community supported version of the original Shaarli project, by Seba
357 */ 402 */
358 public function getLinkFromUrl($url) 403 public function getLinkFromUrl($url)
359 { 404 {
360 if (isset($this->_urls[$url])) { 405 if (isset($this->urls[$url])) {
361 return $this->_links[$this->_urls[$url]]; 406 return $this->links[$this->urls[$url]];
362 } 407 }
363 return false; 408 return false;
364 } 409 }
@@ -375,7 +420,7 @@ You use the community supported version of the original Shaarli project, by Seba
375 public function filterHash($request) 420 public function filterHash($request)
376 { 421 {
377 $request = substr($request, 0, 6); 422 $request = substr($request, 0, 6);
378 $linkFilter = new LinkFilter($this->_links); 423 $linkFilter = new LinkFilter($this->links);
379 return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); 424 return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request);
380 } 425 }
381 426
@@ -387,7 +432,7 @@ You use the community supported version of the original Shaarli project, by Seba
387 * @return array list of shaare found. 432 * @return array list of shaare found.
388 */ 433 */
389 public function filterDay($request) { 434 public function filterDay($request) {
390 $linkFilter = new LinkFilter($this->_links); 435 $linkFilter = new LinkFilter($this->links);
391 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); 436 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
392 } 437 }
393 438
@@ -409,7 +454,7 @@ You use the community supported version of the original Shaarli project, by Seba
409 $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; 454 $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
410 455
411 // Search tags + fullsearch. 456 // Search tags + fullsearch.
412 if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { 457 if (! empty($searchtags) && ! empty($searchterm)) {
413 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; 458 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
414 $request = array($searchtags, $searchterm); 459 $request = array($searchtags, $searchterm);
415 } 460 }
@@ -429,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba
429 $request = ''; 474 $request = '';
430 } 475 }
431 476
432 $linkFilter = new LinkFilter($this->_links); 477 $linkFilter = new LinkFilter($this);
433 return $linkFilter->filter($type, $request, $casesensitive, $privateonly); 478 return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
434 } 479 }
435 480
@@ -440,11 +485,18 @@ You use the community supported version of the original Shaarli project, by Seba
440 public function allTags() 485 public function allTags()
441 { 486 {
442 $tags = array(); 487 $tags = array();
443 foreach ($this->_links as $link) { 488 $caseMapping = array();
444 foreach (explode(' ', $link['tags']) as $tag) { 489 foreach ($this->links as $link) {
445 if (!empty($tag)) { 490 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
446 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); 491 if (empty($tag)) {
492 continue;
447 } 493 }
494 // The first case found will be displayed.
495 if (!isset($caseMapping[strtolower($tag)])) {
496 $caseMapping[strtolower($tag)] = $tag;
497 $tags[$caseMapping[strtolower($tag)]] = 0;
498 }
499 $tags[$caseMapping[strtolower($tag)]]++;
448 } 500 }
449 } 501 }
450 // Sort tags by usage (most used tag first) 502 // Sort tags by usage (most used tag first)
@@ -459,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba
459 public function days() 511 public function days()
460 { 512 {
461 $linkDays = array(); 513 $linkDays = array();
462 foreach (array_keys($this->_links) as $day) { 514 foreach ($this->links as $link) {
463 $linkDays[substr($day, 0, 8)] = 0; 515 $linkDays[$link['created']->format('Ymd')] = 0;
464 } 516 }
465 $linkDays = array_keys($linkDays); 517 $linkDays = array_keys($linkDays);
466 sort($linkDays); 518 sort($linkDays);
467 519
468 return $linkDays; 520 return $linkDays;
469 } 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 }
470} 574}