aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/LinkDB.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/LinkDB.php')
-rw-r--r--application/LinkDB.php152
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
261To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. 255To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
262 256
263You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 257You 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;