aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
authorVirtualTam <virtualtam@flibidi.net>2017-08-23 01:08:41 +0200
committerVirtualTam <virtualtam@flibidi.net>2017-08-23 01:08:41 +0200
commit9d7a02afcee3c740712a7c95182d332db0504b7e (patch)
tree3266e3d3bfec6a3ac075084cbec07ba4090c4cd2 /application
parentc318096c7a6fb3f6b00bd8c694ab7acb8fbb7cd0 (diff)
parent7c2460c856c1d561b8347316f3045208f9f3d24e (diff)
downloadShaarli-9d7a02afcee3c740712a7c95182d332db0504b7e.tar.gz
Shaarli-9d7a02afcee3c740712a7c95182d332db0504b7e.tar.zst
Shaarli-9d7a02afcee3c740712a7c95182d332db0504b7e.zip
Merge branch 'master' into v0.9
Diffstat (limited to 'application')
-rw-r--r--application/FeedBuilder.php5
-rw-r--r--application/HttpUtils.php14
-rw-r--r--application/LinkDB.php76
-rw-r--r--application/LinkFilter.php65
-rw-r--r--application/PageBuilder.php3
-rw-r--r--application/Router.php12
-rw-r--r--application/Updater.php15
-rw-r--r--application/Url.php24
-rw-r--r--application/Utils.php35
-rw-r--r--application/config/ConfigManager.php1
10 files changed, 192 insertions, 58 deletions
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
index a1f4da48..7377bcec 100644
--- a/application/FeedBuilder.php
+++ b/application/FeedBuilder.php
@@ -97,6 +97,11 @@ class FeedBuilder
97 */ 97 */
98 public function buildData() 98 public function buildData()
99 { 99 {
100 // Search for untagged links
101 if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) {
102 $this->userInput['searchtags'] = false;
103 }
104
100 // Optionally filter the results: 105 // Optionally filter the results:
101 $linksToDisplay = $this->linkDB->filterSearch($this->userInput); 106 $linksToDisplay = $this->linkDB->filterSearch($this->userInput);
102 107
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index a81f9056..88a1efdb 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -311,7 +311,19 @@ function server_url($server)
311 } 311 }
312 } 312 }
313 313
314 return $scheme.'://'.$server['SERVER_NAME'].$port; 314 if (isset($server['HTTP_X_FORWARDED_HOST'])) {
315 // Keep forwarded host
316 if (strpos($server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
317 $hosts = explode(',', $server['HTTP_X_FORWARDED_HOST']);
318 $host = trim($hosts[0]);
319 } else {
320 $host = $server['HTTP_X_FORWARDED_HOST'];
321 }
322 } else {
323 $host = $server['SERVER_NAME'];
324 }
325
326 return $scheme.'://'.$host.$port;
315 } 327 }
316 328
317 // SSL detection 329 // SSL detection
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 0d3c85bd..9308164a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -417,49 +417,36 @@ You use the community supported version of the original Shaarli project, by Seba
417 * - searchterm: term search 417 * - searchterm: term search
418 * @param bool $casesensitive Optional: Perform case sensitive filter 418 * @param bool $casesensitive Optional: Perform case sensitive filter
419 * @param string $visibility return only all/private/public links 419 * @param string $visibility return only all/private/public links
420 * @param string $untaggedonly return only untagged links
420 * 421 *
421 * @return array filtered links, all links if no suitable filter was provided. 422 * @return array filtered links, all links if no suitable filter was provided.
422 */ 423 */
423 public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all') 424 public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all', $untaggedonly = false)
424 { 425 {
425 // Filter link database according to parameters. 426 // Filter link database according to parameters.
426 $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; 427 $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
427 $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; 428 $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
428 429
429 // Search tags + fullsearch. 430 // Search tags + fullsearch - blank string parameter will return all links.
430 if (! empty($searchtags) && ! empty($searchterm)) { 431 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; // == "vuotext"
431 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; 432 $request = [$searchtags, $searchterm];
432 $request = array($searchtags, $searchterm);
433 }
434 // Search by tags.
435 elseif (! empty($searchtags)) {
436 $type = LinkFilter::$FILTER_TAG;
437 $request = $searchtags;
438 }
439 // Fulltext search.
440 elseif (! empty($searchterm)) {
441 $type = LinkFilter::$FILTER_TEXT;
442 $request = $searchterm;
443 }
444 // Otherwise, display without filtering.
445 else {
446 $type = '';
447 $request = '';
448 }
449 433
450 $linkFilter = new LinkFilter($this); 434 $linkFilter = new LinkFilter($this);
451 return $linkFilter->filter($type, $request, $casesensitive, $visibility); 435 return $linkFilter->filter($type, $request, $casesensitive, $visibility, $untaggedonly);
452 } 436 }
453 437
454 /** 438 /**
455 * Returns the list of all tags 439 * Returns the list tags appearing in the links with the given tags
456 * Output: associative array key=tags, value=0 440 * @param $filteringTags: tags selecting the links to consider
441 * @param $visibility: process only all/private/public links
442 * @return: a tag=>linksCount array
457 */ 443 */
458 public function allTags() 444 public function linksCountPerTag($filteringTags = [], $visibility = 'all')
459 { 445 {
446 $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
460 $tags = array(); 447 $tags = array();
461 $caseMapping = array(); 448 $caseMapping = array();
462 foreach ($this->links as $link) { 449 foreach ($links as $link) {
463 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) { 450 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
464 if (empty($tag)) { 451 if (empty($tag)) {
465 continue; 452 continue;
@@ -478,6 +465,39 @@ You use the community supported version of the original Shaarli project, by Seba
478 } 465 }
479 466
480 /** 467 /**
468 * Rename or delete a tag across all links.
469 *
470 * @param string $from Tag to rename
471 * @param string $to New tag. If none is provided, the from tag will be deleted
472 *
473 * @return array|bool List of altered links or false on error
474 */
475 public function renameTag($from, $to)
476 {
477 if (empty($from)) {
478 return false;
479 }
480 $delete = empty($to);
481 // True for case-sensitive tag search.
482 $linksToAlter = $this->filterSearch(['searchtags' => $from], true);
483 foreach($linksToAlter as $key => &$value)
484 {
485 $tags = preg_split('/\s+/', trim($value['tags']));
486 if (($pos = array_search($from, $tags)) !== false) {
487 if ($delete) {
488 unset($tags[$pos]); // Remove tag.
489 } else {
490 $tags[$pos] = trim($to);
491 }
492 $value['tags'] = trim(implode(' ', array_unique($tags)));
493 $this[$value['id']] = $value;
494 }
495 }
496
497 return $linksToAlter;
498 }
499
500 /**
481 * Returns the list of days containing articles (oldest first) 501 * Returns the list of days containing articles (oldest first)
482 * Output: An array containing days (in format YYYYMMDD). 502 * Output: An array containing days (in format YYYYMMDD).
483 */ 503 */
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index 81832a4b..95519528 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -52,10 +52,11 @@ class LinkFilter
52 * @param mixed $request Filter content. 52 * @param mixed $request Filter content.
53 * @param bool $casesensitive Optional: Perform case sensitive filter if true. 53 * @param bool $casesensitive Optional: Perform case sensitive filter if true.
54 * @param string $visibility Optional: return only all/private/public links 54 * @param string $visibility Optional: return only all/private/public links
55 * @param string $untaggedonly Optional: return only untagged links. Applies only if $type includes FILTER_TAG
55 * 56 *
56 * @return array filtered link list. 57 * @return array filtered link list.
57 */ 58 */
58 public function filter($type, $request, $casesensitive = false, $visibility = 'all') 59 public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
59 { 60 {
60 if (! in_array($visibility, ['all', 'public', 'private'])) { 61 if (! in_array($visibility, ['all', 'public', 'private'])) {
61 $visibility = 'all'; 62 $visibility = 'all';
@@ -64,23 +65,34 @@ class LinkFilter
64 switch($type) { 65 switch($type) {
65 case self::$FILTER_HASH: 66 case self::$FILTER_HASH:
66 return $this->filterSmallHash($request); 67 return $this->filterSmallHash($request);
67 case self::$FILTER_TAG | self::$FILTER_TEXT: 68 case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
68 if (!empty($request)) { 69 $noRequest = empty($request) || (empty($request[0]) && empty($request[1]));
69 $filtered = $this->links; 70 if ($noRequest) {
70 if (isset($request[0])) { 71 if ($untaggedonly) {
71 $filtered = $this->filterTags($request[0], $casesensitive, $visibility); 72 return $this->filterUntagged($visibility);
72 }
73 if (isset($request[1])) {
74 $lf = new LinkFilter($filtered);
75 $filtered = $lf->filterFulltext($request[1], $visibility);
76 } 73 }
77 return $filtered; 74 return $this->noFilter($visibility);
78 } 75 }
79 return $this->noFilter($visibility); 76 if ($untaggedonly) {
77 $filtered = $this->filterUntagged($visibility);
78 } else {
79 $filtered = $this->links;
80 }
81 if (!empty($request[0])) {
82 $filtered = (new LinkFilter($filtered))->filterTags($request[0], $casesensitive, $visibility);
83 }
84 if (!empty($request[1])) {
85 $filtered = (new LinkFilter($filtered))->filterFulltext($request[1], $visibility);
86 }
87 return $filtered;
80 case self::$FILTER_TEXT: 88 case self::$FILTER_TEXT:
81 return $this->filterFulltext($request, $visibility); 89 return $this->filterFulltext($request, $visibility);
82 case self::$FILTER_TAG: 90 case self::$FILTER_TAG:
83 return $this->filterTags($request, $casesensitive, $visibility); 91 if ($untaggedonly) {
92 return $this->filterUntagged($visibility);
93 } else {
94 return $this->filterTags($request, $casesensitive, $visibility);
95 }
84 case self::$FILTER_DAY: 96 case self::$FILTER_DAY:
85 return $this->filterDay($request); 97 return $this->filterDay($request);
86 default: 98 default:
@@ -296,6 +308,33 @@ class LinkFilter
296 } 308 }
297 309
298 /** 310 /**
311 * Return only links without any tag.
312 *
313 * @param string $visibility return only all/private/public links.
314 *
315 * @return array filtered links.
316 */
317 public function filterUntagged($visibility)
318 {
319 $filtered = [];
320 foreach ($this->links as $key => $link) {
321 if ($visibility !== 'all') {
322 if (! $link['private'] && $visibility === 'private') {
323 continue;
324 } else if ($link['private'] && $visibility === 'public') {
325 continue;
326 }
327 }
328
329 if (empty(trim($link['tags']))) {
330 $filtered[$key] = $link;
331 }
332 }
333
334 return $filtered;
335 }
336
337 /**
299 * Returns the list of articles for a given day, chronologically sorted 338 * Returns the list of articles for a given day, chronologically sorted
300 * 339 *
301 * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g. 340 * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 50e3f124..7a42400d 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -78,6 +78,7 @@ class PageBuilder
78 $this->tpl->assign('version', shaarli_version); 78 $this->tpl->assign('version', shaarli_version);
79 $this->tpl->assign('scripturl', index_url($_SERVER)); 79 $this->tpl->assign('scripturl', index_url($_SERVER));
80 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? 80 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
81 $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
81 $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli')); 82 $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli'));
82 if ($this->conf->exists('general.header_link')) { 83 if ($this->conf->exists('general.header_link')) {
83 $this->tpl->assign('titleLink', $this->conf->get('general.header_link')); 84 $this->tpl->assign('titleLink', $this->conf->get('general.header_link'));
@@ -89,7 +90,7 @@ class PageBuilder
89 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); 90 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
90 $this->tpl->assign('token', getToken($this->conf)); 91 $this->tpl->assign('token', getToken($this->conf));
91 if ($this->linkDB !== null) { 92 if ($this->linkDB !== null) {
92 $this->tpl->assign('tags', $this->linkDB->allTags()); 93 $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
93 } 94 }
94 // To be removed with a proper theme configuration. 95 // To be removed with a proper theme configuration.
95 $this->tpl->assign('conf', $this->conf); 96 $this->tpl->assign('conf', $this->conf);
diff --git a/application/Router.php b/application/Router.php
index c9a51912..4df0387c 100644
--- a/application/Router.php
+++ b/application/Router.php
@@ -13,6 +13,8 @@ class Router
13 13
14 public static $PAGE_TAGCLOUD = 'tagcloud'; 14 public static $PAGE_TAGCLOUD = 'tagcloud';
15 15
16 public static $PAGE_TAGLIST = 'taglist';
17
16 public static $PAGE_DAILY = 'daily'; 18 public static $PAGE_DAILY = 'daily';
17 19
18 public static $PAGE_FEED_ATOM = 'atom'; 20 public static $PAGE_FEED_ATOM = 'atom';
@@ -45,6 +47,8 @@ class Router
45 47
46 public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; 48 public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin';
47 49
50 public static $GET_TOKEN = 'token';
51
48 /** 52 /**
49 * Reproducing renderPage() if hell, to avoid regression. 53 * Reproducing renderPage() if hell, to avoid regression.
50 * 54 *
@@ -77,6 +81,10 @@ class Router
77 return self::$PAGE_TAGCLOUD; 81 return self::$PAGE_TAGCLOUD;
78 } 82 }
79 83
84 if (startsWith($query, 'do='. self::$PAGE_TAGLIST)) {
85 return self::$PAGE_TAGLIST;
86 }
87
80 if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { 88 if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) {
81 return self::$PAGE_OPENSEARCH; 89 return self::$PAGE_OPENSEARCH;
82 } 90 }
@@ -142,6 +150,10 @@ class Router
142 return self::$PAGE_SAVE_PLUGINSADMIN; 150 return self::$PAGE_SAVE_PLUGINSADMIN;
143 } 151 }
144 152
153 if (startsWith($query, 'do='. self::$GET_TOKEN)) {
154 return self::$GET_TOKEN;
155 }
156
145 return self::$PAGE_LINKLIST; 157 return self::$PAGE_LINKLIST;
146 } 158 }
147} 159}
diff --git a/application/Updater.php b/application/Updater.php
index 03d93a6f..40a15906 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -329,21 +329,6 @@ class Updater
329 } 329 }
330 330
331 /** 331 /**
332 * While the new default theme is in an unstable state
333 * continue to use the vintage theme
334 */
335 public function updateMethodDefaultThemeVintage()
336 {
337 if ($this->conf->get('resource.theme') !== 'default') {
338 return true;
339 }
340 $this->conf->set('resource.theme', 'vintage');
341 $this->conf->write($this->isLoggedIn);
342
343 return true;
344 }
345
346 /**
347 * * `markdown_escape` is a new setting, set to true as default. 332 * * `markdown_escape` is a new setting, set to true as default.
348 * 333 *
349 * If the markdown plugin was already enabled, escaping is disabled to avoid 334 * If the markdown plugin was already enabled, escaping is disabled to avoid
diff --git a/application/Url.php b/application/Url.php
index 25a62a8a..b3759377 100644
--- a/application/Url.php
+++ b/application/Url.php
@@ -64,6 +64,30 @@ function add_trailing_slash($url)
64} 64}
65 65
66/** 66/**
67 * Replace not whitelisted protocols by 'http://' from given URL.
68 *
69 * @param string $url URL to clean
70 * @param array $protocols List of allowed protocols (aside from http(s)).
71 *
72 * @return string URL with allowed protocol
73 */
74function whitelist_protocols($url, $protocols)
75{
76 if (startsWith($url, '?') || startsWith($url, '/')) {
77 return $url;
78 }
79 $protocols = array_merge(['http', 'https'], $protocols);
80 $protocol = preg_match('#^(\w+):/?/?#', $url, $match);
81 // Protocol not allowed: we remove it and replace it with http
82 if ($protocol === 1 && ! in_array($match[1], $protocols)) {
83 $url = str_replace($match[0], 'http://', $url);
84 } else if ($protocol !== 1) {
85 $url = 'http://' . $url;
86 }
87 return $url;
88}
89
90/**
67 * URL representation and cleanup utilities 91 * URL representation and cleanup utilities
68 * 92 *
69 * Form 93 * Form
diff --git a/application/Utils.php b/application/Utils.php
index ab463af9..4a2f5561 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -91,6 +91,10 @@ function endsWith($haystack, $needle, $case = true)
91 */ 91 */
92function escape($input) 92function escape($input)
93{ 93{
94 if (is_bool($input)) {
95 return $input;
96 }
97
94 if (is_array($input)) { 98 if (is_array($input)) {
95 $out = array(); 99 $out = array();
96 foreach($input as $key => $value) { 100 foreach($input as $key => $value) {
@@ -435,3 +439,34 @@ function get_max_upload_size($limitPost, $limitUpload, $format = true)
435 $maxsize = min($size1, $size2); 439 $maxsize = min($size1, $size2);
436 return $format ? human_bytes($maxsize) : $maxsize; 440 return $format ? human_bytes($maxsize) : $maxsize;
437} 441}
442
443/**
444 * Sort the given array alphabetically using php-intl if available.
445 * Case sensitive.
446 *
447 * Note: doesn't support multidimensional arrays
448 *
449 * @param array $data Input array, passed by reference
450 * @param bool $reverse Reverse sort if set to true
451 * @param bool $byKeys Sort the array by keys if set to true, by value otherwise.
452 */
453function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
454{
455 $callback = function($a, $b) use ($reverse) {
456 // Collator is part of PHP intl.
457 if (class_exists('Collator')) {
458 $collator = new Collator(setlocale(LC_COLLATE, 0));
459 if (!intl_is_failure(intl_get_error_code())) {
460 return $collator->compare($a, $b) * ($reverse ? -1 : 1);
461 }
462 }
463
464 return strcasecmp($a, $b) * ($reverse ? -1 : 1);
465 };
466
467 if ($byKeys) {
468 uksort($data, $callback);
469 } else {
470 usort($data, $callback);
471 }
472}
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 86a917fb..8eab26f1 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -312,6 +312,7 @@ class ConfigManager
312 $this->setEmpty('security.ban_duration', 1800); 312 $this->setEmpty('security.ban_duration', 1800);
313 $this->setEmpty('security.session_protection_disabled', false); 313 $this->setEmpty('security.session_protection_disabled', false);
314 $this->setEmpty('security.open_shaarli', false); 314 $this->setEmpty('security.open_shaarli', false);
315 $this->setEmpty('security.allowed_protocols', ['ftp', 'ftps', 'magnet']);
315 316
316 $this->setEmpty('general.header_link', '?'); 317 $this->setEmpty('general.header_link', '?');
317 $this->setEmpty('general.links_per_page', 20); 318 $this->setEmpty('general.links_per_page', 20);