diff options
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r-- | application/LinkDB.php | 152 |
1 files changed, 72 insertions, 80 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php index 1e13286a..c1661d52 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -50,12 +50,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
50 | // Link date storage format | 50 | // Link date storage format |
51 | const LINK_DATE_FORMAT = 'Ymd_His'; | 51 | const LINK_DATE_FORMAT = 'Ymd_His'; |
52 | 52 | ||
53 | // Datastore PHP prefix | ||
54 | protected static $phpPrefix = '<?php /* '; | ||
55 | |||
56 | // Datastore PHP suffix | ||
57 | protected static $phpSuffix = ' */ ?>'; | ||
58 | |||
59 | // List of links (associative array) | 53 | // List of links (associative array) |
60 | // - key: link date (e.g. "20110823_124546"), | 54 | // - key: link date (e.g. "20110823_124546"), |
61 | // - value: associative array (keys: title, description...) | 55 | // - value: associative array (keys: title, description...) |
@@ -139,16 +133,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
139 | { | 133 | { |
140 | // TODO: use exceptions instead of "die" | 134 | // TODO: use exceptions instead of "die" |
141 | if (!$this->loggedIn) { | 135 | if (!$this->loggedIn) { |
142 | die('You are not authorized to add a link.'); | 136 | die(t('You are not authorized to add a link.')); |
143 | } | 137 | } |
144 | if (!isset($value['id']) || empty($value['url'])) { | 138 | if (!isset($value['id']) || empty($value['url'])) { |
145 | die('Internal Error: A link should always have an id and URL.'); | 139 | die(t('Internal Error: A link should always have an id and URL.')); |
146 | } | 140 | } |
147 | if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { | 141 | if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { |
148 | die('You must specify an integer as a key.'); | 142 | die(t('You must specify an integer as a key.')); |
149 | } | 143 | } |
150 | if (! empty($offset) && $offset !== $value['id']) { | 144 | if ($offset !== null && $offset !== $value['id']) { |
151 | die('Array offset and link ID must be equal.'); | 145 | die(t('Array offset and link ID must be equal.')); |
152 | } | 146 | } |
153 | 147 | ||
154 | // If the link exists, we reuse the real offset, otherwise new entry | 148 | // If the link exists, we reuse the real offset, otherwise new entry |
@@ -254,13 +248,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
254 | $this->links = array(); | 248 | $this->links = array(); |
255 | $link = array( | 249 | $link = array( |
256 | 'id' => 1, | 250 | 'id' => 1, |
257 | 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', | 251 | 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'), |
258 | 'url'=>'https://github.com/shaarli/Shaarli/wiki', | 252 | 'url'=>'https://shaarli.readthedocs.io', |
259 | 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. | 253 | 'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. |
260 | 254 | ||
261 | To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. | 255 | To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page. |
262 | 256 | ||
263 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', | 257 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'), |
264 | 'private'=>0, | 258 | 'private'=>0, |
265 | 'created'=> new DateTime(), | 259 | 'created'=> new DateTime(), |
266 | 'tags'=>'opensource software' | 260 | 'tags'=>'opensource software' |
@@ -270,9 +264,9 @@ You use the community supported version of the original Shaarli project, by Seba | |||
270 | 264 | ||
271 | $link = array( | 265 | $link = array( |
272 | 'id' => 0, | 266 | 'id' => 0, |
273 | 'title'=>'My secret stuff... - Pastebin.com', | 267 | 'title'=> t('My secret stuff... - Pastebin.com'), |
274 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', | 268 | 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', |
275 | 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', | 269 | 'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'), |
276 | 'private'=>1, | 270 | 'private'=>1, |
277 | 'created'=> new DateTime('1 minute ago'), | 271 | 'created'=> new DateTime('1 minute ago'), |
278 | 'tags'=>'secretstuff', | 272 | 'tags'=>'secretstuff', |
@@ -295,22 +289,15 @@ You use the community supported version of the original Shaarli project, by Seba | |||
295 | return; | 289 | return; |
296 | } | 290 | } |
297 | 291 | ||
298 | // Read data | 292 | $this->urls = []; |
299 | // Note that gzinflate is faster than gzuncompress. | 293 | $this->ids = []; |
300 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | 294 | $this->links = FileUtils::readFlatDB($this->datastore, []); |
301 | $this->links = array(); | ||
302 | |||
303 | if (file_exists($this->datastore)) { | ||
304 | $this->links = unserialize(gzinflate(base64_decode( | ||
305 | substr(file_get_contents($this->datastore), | ||
306 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | ||
307 | } | ||
308 | 295 | ||
309 | $toremove = array(); | 296 | $toremove = array(); |
310 | foreach ($this->links as $key => &$link) { | 297 | foreach ($this->links as $key => &$link) { |
311 | if (! $this->loggedIn && $link['private'] != 0) { | 298 | if (! $this->loggedIn && $link['private'] != 0) { |
312 | // Transition for not upgraded databases. | 299 | // Transition for not upgraded databases. |
313 | $toremove[] = $key; | 300 | unset($this->links[$key]); |
314 | continue; | 301 | continue; |
315 | } | 302 | } |
316 | 303 | ||
@@ -344,14 +331,10 @@ You use the community supported version of the original Shaarli project, by Seba | |||
344 | } | 331 | } |
345 | $link['shorturl'] = smallHash($link['linkdate']); | 332 | $link['shorturl'] = smallHash($link['linkdate']); |
346 | } | 333 | } |
347 | } | ||
348 | 334 | ||
349 | // If user is not logged in, filter private links. | 335 | $this->urls[$link['url']] = $key; |
350 | foreach ($toremove as $offset) { | 336 | $this->ids[$link['id']] = $key; |
351 | unset($this->links[$offset]); | ||
352 | } | 337 | } |
353 | |||
354 | $this->reorder(); | ||
355 | } | 338 | } |
356 | 339 | ||
357 | /** | 340 | /** |
@@ -361,19 +344,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
361 | */ | 344 | */ |
362 | private function write() | 345 | private function write() |
363 | { | 346 | { |
364 | if (is_file($this->datastore) && !is_writeable($this->datastore)) { | 347 | $this->reorder(); |
365 | // The datastore exists but is not writeable | 348 | FileUtils::writeFlatDB($this->datastore, $this->links); |
366 | throw new IOException($this->datastore); | ||
367 | } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { | ||
368 | // The datastore does not exist and its parent directory is not writeable | ||
369 | throw new IOException(dirname($this->datastore)); | ||
370 | } | ||
371 | |||
372 | file_put_contents( | ||
373 | $this->datastore, | ||
374 | self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix | ||
375 | ); | ||
376 | |||
377 | } | 349 | } |
378 | 350 | ||
379 | /** | 351 | /** |
@@ -443,50 +415,37 @@ You use the community supported version of the original Shaarli project, by Seba | |||
443 | * - searchtags: list of tags | 415 | * - searchtags: list of tags |
444 | * - searchterm: term search | 416 | * - searchterm: term search |
445 | * @param bool $casesensitive Optional: Perform case sensitive filter | 417 | * @param bool $casesensitive Optional: Perform case sensitive filter |
446 | * @param bool $privateonly Optional: Returns private links only if true. | 418 | * @param string $visibility return only all/private/public links |
419 | * @param string $untaggedonly return only untagged links | ||
447 | * | 420 | * |
448 | * @return array filtered links, all links if no suitable filter was provided. | 421 | * @return array filtered links, all links if no suitable filter was provided. |
449 | */ | 422 | */ |
450 | public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false) | 423 | public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all', $untaggedonly = false) |
451 | { | 424 | { |
452 | // Filter link database according to parameters. | 425 | // Filter link database according to parameters. |
453 | $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; | 426 | $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; |
454 | $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; | 427 | $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; |
455 | 428 | ||
456 | // Search tags + fullsearch. | 429 | // Search tags + fullsearch - blank string parameter will return all links. |
457 | if (! empty($searchtags) && ! empty($searchterm)) { | 430 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; // == "vuotext" |
458 | $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; | 431 | $request = [$searchtags, $searchterm]; |
459 | $request = array($searchtags, $searchterm); | ||
460 | } | ||
461 | // Search by tags. | ||
462 | elseif (! empty($searchtags)) { | ||
463 | $type = LinkFilter::$FILTER_TAG; | ||
464 | $request = $searchtags; | ||
465 | } | ||
466 | // Fulltext search. | ||
467 | elseif (! empty($searchterm)) { | ||
468 | $type = LinkFilter::$FILTER_TEXT; | ||
469 | $request = $searchterm; | ||
470 | } | ||
471 | // Otherwise, display without filtering. | ||
472 | else { | ||
473 | $type = ''; | ||
474 | $request = ''; | ||
475 | } | ||
476 | 432 | ||
477 | $linkFilter = new LinkFilter($this); | 433 | $linkFilter = new LinkFilter($this); |
478 | return $linkFilter->filter($type, $request, $casesensitive, $privateonly); | 434 | return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly); |
479 | } | 435 | } |
480 | 436 | ||
481 | /** | 437 | /** |
482 | * Returns the list of all tags | 438 | * Returns the list tags appearing in the links with the given tags |
483 | * Output: associative array key=tags, value=0 | 439 | * @param $filteringTags: tags selecting the links to consider |
440 | * @param $visibility: process only all/private/public links | ||
441 | * @return: a tag=>linksCount array | ||
484 | */ | 442 | */ |
485 | public function allTags() | 443 | public function linksCountPerTag($filteringTags = [], $visibility = 'all') |
486 | { | 444 | { |
445 | $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility); | ||
487 | $tags = array(); | 446 | $tags = array(); |
488 | $caseMapping = array(); | 447 | $caseMapping = array(); |
489 | foreach ($this->links as $link) { | 448 | foreach ($links as $link) { |
490 | foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { | 449 | foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { |
491 | if (empty($tag)) { | 450 | if (empty($tag)) { |
492 | continue; | 451 | continue; |
@@ -505,6 +464,39 @@ You use the community supported version of the original Shaarli project, by Seba | |||
505 | } | 464 | } |
506 | 465 | ||
507 | /** | 466 | /** |
467 | * Rename or delete a tag across all links. | ||
468 | * | ||
469 | * @param string $from Tag to rename | ||
470 | * @param string $to New tag. If none is provided, the from tag will be deleted | ||
471 | * | ||
472 | * @return array|bool List of altered links or false on error | ||
473 | */ | ||
474 | public function renameTag($from, $to) | ||
475 | { | ||
476 | if (empty($from)) { | ||
477 | return false; | ||
478 | } | ||
479 | $delete = empty($to); | ||
480 | // True for case-sensitive tag search. | ||
481 | $linksToAlter = $this->filterSearch(['searchtags' => $from], true); | ||
482 | foreach($linksToAlter as $key => &$value) | ||
483 | { | ||
484 | $tags = preg_split('/\s+/', trim($value['tags'])); | ||
485 | if (($pos = array_search($from, $tags)) !== false) { | ||
486 | if ($delete) { | ||
487 | unset($tags[$pos]); // Remove tag. | ||
488 | } else { | ||
489 | $tags[$pos] = trim($to); | ||
490 | } | ||
491 | $value['tags'] = trim(implode(' ', array_unique($tags))); | ||
492 | $this[$value['id']] = $value; | ||
493 | } | ||
494 | } | ||
495 | |||
496 | return $linksToAlter; | ||
497 | } | ||
498 | |||
499 | /** | ||
508 | * Returns the list of days containing articles (oldest first) | 500 | * Returns the list of days containing articles (oldest first) |
509 | * Output: An array containing days (in format YYYYMMDD). | 501 | * Output: An array containing days (in format YYYYMMDD). |
510 | */ | 502 | */ |
@@ -535,8 +527,8 @@ You use the community supported version of the original Shaarli project, by Seba | |||
535 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; | 527 | return $a['created'] < $b['created'] ? 1 * $order : -1 * $order; |
536 | }); | 528 | }); |
537 | 529 | ||
538 | $this->urls = array(); | 530 | $this->urls = []; |
539 | $this->ids = array(); | 531 | $this->ids = []; |
540 | foreach ($this->links as $key => $link) { | 532 | foreach ($this->links as $key => $link) { |
541 | $this->urls[$link['url']] = $key; | 533 | $this->urls[$link['url']] = $key; |
542 | $this->ids[$link['id']] = $key; | 534 | $this->ids[$link['id']] = $key; |