aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml9
-rw-r--r--Makefile24
-rw-r--r--application/HttpUtils.php28
-rw-r--r--application/config/ConfigManager.php3
-rw-r--r--doc/md/Release-Shaarli.md6
-rw-r--r--doc/md/Shaarli-configuration.md7
-rw-r--r--doc/md/Unit-tests-Docker.md56
-rw-r--r--docker/test/alpine36/Dockerfile34
-rw-r--r--docker/test/debian8/Dockerfile35
-rw-r--r--docker/test/debian9/Dockerfile36
-rw-r--r--docker/test/ubuntu16/Dockerfile36
-rw-r--r--index.php92
-rw-r--r--mkdocs.yml1
-rw-r--r--tests/HttpUtils/IsHttpsTest.php36
-rw-r--r--tests/languages/de/UtilsDeTest.php12
-rw-r--r--tests/languages/en/UtilsEnTest.php12
-rw-r--r--tests/languages/fr/UtilsFrTest.php12
-rw-r--r--tpl/default/css/shaarli.css2
-rw-r--r--tpl/default/js/shaarli.js9
-rw-r--r--tpl/default/loginform.html3
-rw-r--r--tpl/vintage/loginform.html4
21 files changed, 385 insertions, 72 deletions
diff --git a/.travis.yml b/.travis.yml
index 26535ad3..b6b9bddf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,6 @@
1sudo: false 1sudo: false
2dist: precise 2dist: trusty
3language: php 3language: php
4addons:
5 apt:
6 packages:
7 - locales
8 - language-pack-de
9 - language-pack-fr
10cache: 4cache:
11 directories: 5 directories:
12 - $HOME/.composer/cache 6 - $HOME/.composer/cache
@@ -18,6 +12,7 @@ php:
18install: 12install:
19 - composer self-update 13 - composer self-update
20 - composer install --prefer-dist 14 - composer install --prefer-dist
15 - locale -a
21script: 16script:
22 - make clean 17 - make clean
23 - make check_permissions 18 - make check_permissions
diff --git a/Makefile b/Makefile
index 6483fca7..a3696ec9 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,16 @@ PHP_COMMA_SOURCE = index.php,application,tests,plugins
19all: static_analysis_summary check_permissions test 19all: static_analysis_summary check_permissions test
20 20
21## 21##
22# Docker test adapter
23#
24# Shaarli sources and vendored libraries are copied from a shared volume
25# to a user-owned directory to enable running tests as a non-root user.
26##
27docker_%:
28 rsync -az /shaarli/ ~/shaarli/
29 cd ~/shaarli && make $*
30
31##
22# Concise status of the project 32# Concise status of the project
23# These targets are non-blocking: || exit 0 33# These targets are non-blocking: || exit 0
24## 34##
@@ -159,14 +169,14 @@ composer_dependencies: clean
159 find vendor/ -name ".git" -type d -exec rm -rf {} + 169 find vendor/ -name ".git" -type d -exec rm -rf {} +
160 170
161### generate a release tarball and include 3rd-party dependencies 171### generate a release tarball and include 3rd-party dependencies
162release_tar: composer_dependencies doc_html 172release_tar: composer_dependencies htmldoc
163 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD 173 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
164 tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/ 174 tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
165 tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/ 175 tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
166 gzip $(ARCHIVE_VERSION).tar 176 gzip $(ARCHIVE_VERSION).tar
167 177
168### generate a release zip and include 3rd-party dependencies 178### generate a release zip and include 3rd-party dependencies
169release_zip: composer_dependencies doc_html 179release_zip: composer_dependencies htmldoc
170 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD 180 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
171 mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor} 181 mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
172 rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/ 182 rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
@@ -195,17 +205,11 @@ doxygen: clean
195 @rm -rf doxygen 205 @rm -rf doxygen
196 @( cat Doxyfile ; echo "PROJECT_NUMBER=`git describe`" ) | doxygen - 206 @( cat Doxyfile ; echo "PROJECT_NUMBER=`git describe`" ) | doxygen -
197 207
198### Convert local markdown documentation to HTML 208### generate HTML documentation from Markdown pages with MkDocs
199# 209htmldoc:
200# For all pages:
201# - convert GitHub-flavoured relative links to standard Markdown
202# - generate html documentation with mkdocs
203htmlpages:
204 python3 -m venv venv/ 210 python3 -m venv venv/
205 bash -c 'source venv/bin/activate; \ 211 bash -c 'source venv/bin/activate; \
206 pip install mkdocs; \ 212 pip install mkdocs; \
207 mkdocs build' 213 mkdocs build'
208 find doc/html/ -type f -exec chmod a-x '{}' \; 214 find doc/html/ -type f -exec chmod a-x '{}' \;
209 rm -r venv 215 rm -r venv
210
211doc_html: authors htmlpages
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index 88a1efdb..00835966 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -401,3 +401,31 @@ function getIpAddressFromProxy($server, $trustedIps)
401 401
402 return array_pop($ips); 402 return array_pop($ips);
403} 403}
404
405/**
406 * Returns true if Shaarli's currently browsed in HTTPS.
407 * Supports reverse proxies (if the headers are correctly set).
408 *
409 * @param array $server $_SERVER.
410 *
411 * @return bool true if HTTPS, false otherwise.
412 */
413function is_https($server)
414{
415
416 if (isset($server['HTTP_X_FORWARDED_PORT'])) {
417 // Keep forwarded port
418 if (strpos($server['HTTP_X_FORWARDED_PORT'], ',') !== false) {
419 $ports = explode(',', $server['HTTP_X_FORWARDED_PORT']);
420 $port = trim($ports[0]);
421 } else {
422 $port = $server['HTTP_X_FORWARDED_PORT'];
423 }
424
425 if ($port == '443') {
426 return true;
427 }
428 }
429
430 return ! empty($server['HTTPS']);
431}
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 0fc5a5c7..32f6ef6d 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -327,7 +327,10 @@ class ConfigManager
327 327
328 $this->setEmpty('privacy.default_private_links', false); 328 $this->setEmpty('privacy.default_private_links', false);
329 $this->setEmpty('privacy.hide_public_links', false); 329 $this->setEmpty('privacy.hide_public_links', false);
330 $this->setEmpty('privacy.force_login', false);
330 $this->setEmpty('privacy.hide_timestamps', false); 331 $this->setEmpty('privacy.hide_timestamps', false);
332 // default state of the 'remember me' checkbox of the login form
333 $this->setEmpty('privacy.remember_user_default', true);
331 334
332 $this->setEmpty('thumbnail.enable_thumbnails', true); 335 $this->setEmpty('thumbnail.enable_thumbnails', true);
333 $this->setEmpty('thumbnail.enable_localcache', true); 336 $this->setEmpty('thumbnail.enable_localcache', true);
diff --git a/doc/md/Release-Shaarli.md b/doc/md/Release-Shaarli.md
index 974a7438..e22eabc9 100644
--- a/doc/md/Release-Shaarli.md
+++ b/doc/md/Release-Shaarli.md
@@ -46,6 +46,12 @@ TBA
46 46
47 47
48## Increment the version code, update docs, create and push a signed tag 48## Increment the version code, update docs, create and push a signed tag
49### Update the list of Git contributors
50```bash
51$ make authors
52$ git commit -s -m "Update AUTHORS"
53```
54
49### Create and merge a Pull Request 55### Create and merge a Pull Request
50This one is pretty straightforward ;-) 56This one is pretty straightforward ;-)
51 57
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md
index 188a3c09..37486414 100644
--- a/doc/md/Shaarli-configuration.md
+++ b/doc/md/Shaarli-configuration.md
@@ -90,7 +90,10 @@ _These settings should not be edited_
90 90
91- **default_private_links**: Check the private checkbox by default for every new link. 91- **default_private_links**: Check the private checkbox by default for every new link.
92- **hide_public_links**: All links are hidden while logged out. 92- **hide_public_links**: All links are hidden while logged out.
93- **force_login**: if **hide_public_links** and this are set to `true`, all anonymous users are redirected to the login page.
93- **hide_timestamps**: Timestamps are hidden. 94- **hide_timestamps**: Timestamps are hidden.
95- **remember_user_default**: Default state of the login page's *remember me* checkbox
96 - `true`: checked by default, `false`: unchecked by default
94 97
95### Feed 98### Feed
96 99
@@ -192,7 +195,9 @@ _These settings should not be edited_
192 "privacy": { 195 "privacy": {
193 "default_private_links": true, 196 "default_private_links": true,
194 "hide_public_links": false, 197 "hide_public_links": false,
195 "hide_timestamps": false 198 "force_login": false,
199 "hide_timestamps": false,
200 "remember_user_default": true
196 }, 201 },
197 "thumbnail": { 202 "thumbnail": {
198 "enable_thumbnails": true, 203 "enable_thumbnails": true,
diff --git a/doc/md/Unit-tests-Docker.md b/doc/md/Unit-tests-Docker.md
new file mode 100644
index 00000000..c2de7cc7
--- /dev/null
+++ b/doc/md/Unit-tests-Docker.md
@@ -0,0 +1,56 @@
1## Running tests inside Docker containers
2
3Read first:
4
5- [Docker 101](docker/docker-101.md)
6- [Docker resources](docker/resources.md)
7- [Unit tests](Unit-tests.md)
8
9### Docker test images
10
11Test Dockerfiles are located under `docker/tests/<distribution>/Dockerfile`,
12and can be used to build Docker images to run Shaarli test suites under common
13Linux environments.
14
15Dockerfiles are provided for the following environments:
16
17- `alpine36` - [Alpine 3.6](https://www.alpinelinux.org/downloads/)
18- `debian8` - [Debian 8 Jessie](https://www.debian.org/DebianJessie) (oldstable)
19- `debian9` - [Debian 9 Stretch](https://wiki.debian.org/DebianStretch) (stable)
20- `ubuntu16` - [Ubuntu 16.04 Xenial Xerus](http://releases.ubuntu.com/16.04/) (LTS)
21
22What's behind the curtains:
23
24- each image provides:
25 - a base Linux OS
26 - Shaarli PHP dependencies (OS packages)
27 - test PHP dependencies (OS packages)
28 - Composer
29- the local workspace is mapped to the container's `/shaarli/` directory,
30- the files are rsync'd to so tests are run using a standard Linux user account
31 (running tests as `root` would bypass permission checks and may hide issues)
32- the tests are run inside the container.
33
34### Building test images
35
36```bash
37# build the Debian 9 Docker image
38$ cd /path/to/shaarli
39$ cd docker/test/debian9
40$ docker build -t shaarli-test:debian9 .
41```
42
43### Running tests
44
45```bash
46$ cd /path/to/shaarli
47
48# install/update 3rd-party test dependencies
49$ composer install --prefer-dist
50
51# run tests using the freshly built image
52$ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_test
53
54# run the full test campaign
55$ docker run -v $PWD:/shaarli shaarli-test:debian9 docker_all_tests
56```
diff --git a/docker/test/alpine36/Dockerfile b/docker/test/alpine36/Dockerfile
new file mode 100644
index 00000000..fa84f6e2
--- /dev/null
+++ b/docker/test/alpine36/Dockerfile
@@ -0,0 +1,34 @@
1FROM alpine:3.6
2MAINTAINER Shaarli Community
3
4RUN apk --update --no-cache add \
5 ca-certificates \
6 curl \
7 make \
8 php7 \
9 php7-ctype \
10 php7-curl \
11 php7-dom \
12 php7-gd \
13 php7-iconv \
14 php7-intl \
15 php7-json \
16 php7-mbstring \
17 php7-openssl \
18 php7-phar \
19 php7-session \
20 php7-simplexml \
21 php7-tokenizer \
22 php7-xdebug \
23 php7-xml \
24 php7-zlib \
25 rsync
26
27RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
28
29RUN mkdir /shaarli
30WORKDIR /shaarli
31VOLUME /shaarli
32
33ENTRYPOINT ["make"]
34CMD []
diff --git a/docker/test/debian8/Dockerfile b/docker/test/debian8/Dockerfile
new file mode 100644
index 00000000..eaa34e9b
--- /dev/null
+++ b/docker/test/debian8/Dockerfile
@@ -0,0 +1,35 @@
1FROM debian:jessie
2MAINTAINER Shaarli Community
3
4ENV TERM dumb
5ENV DEBIAN_FRONTEND noninteractive
6ENV LANG en_US.UTF-8
7ENV LANGUAGE en_US:en
8
9RUN apt-get update \
10 && apt-get install --no-install-recommends -y \
11 ca-certificates \
12 curl \
13 locales \
14 make \
15 php5 \
16 php5-curl \
17 php5-gd \
18 php5-intl \
19 php5-xdebug \
20 rsync \
21 && apt-get clean
22
23RUN locale-gen en_US.UTF-8 \
24 && locale-gen de_DE.UTF-8 \
25 && locale-gen fr_FR.UTF-8
26
27ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
28RUN chmod 755 /usr/local/bin/composer
29
30RUN mkdir /shaarli
31WORKDIR /shaarli
32VOLUME /shaarli
33
34ENTRYPOINT ["make"]
35CMD []
diff --git a/docker/test/debian9/Dockerfile b/docker/test/debian9/Dockerfile
new file mode 100644
index 00000000..3ab4b93d
--- /dev/null
+++ b/docker/test/debian9/Dockerfile
@@ -0,0 +1,36 @@
1FROM debian:stretch
2MAINTAINER Shaarli Community
3
4ENV TERM dumb
5ENV DEBIAN_FRONTEND noninteractive
6ENV LANG en_US.UTF-8
7ENV LANGUAGE en_US:en
8
9RUN apt-get update \
10 && apt-get install --no-install-recommends -y \
11 ca-certificates \
12 curl \
13 locales \
14 make \
15 php7.0 \
16 php7.0-curl \
17 php7.0-gd \
18 php7.0-intl \
19 php7.0-xml \
20 php-xdebug \
21 rsync \
22 && apt-get clean
23
24RUN locale-gen en_US.UTF-8 \
25 && locale-gen de_DE.UTF-8 \
26 && locale-gen fr_FR.UTF-8
27
28ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
29RUN chmod 755 /usr/local/bin/composer
30
31RUN mkdir /shaarli
32WORKDIR /shaarli
33VOLUME /shaarli
34
35ENTRYPOINT ["make"]
36CMD []
diff --git a/docker/test/ubuntu16/Dockerfile b/docker/test/ubuntu16/Dockerfile
new file mode 100644
index 00000000..e53ed9e3
--- /dev/null
+++ b/docker/test/ubuntu16/Dockerfile
@@ -0,0 +1,36 @@
1FROM ubuntu:16.04
2MAINTAINER Shaarli Community
3
4ENV TERM dumb
5ENV DEBIAN_FRONTEND noninteractive
6ENV LANG en_US.UTF-8
7ENV LANGUAGE en_US:en
8
9RUN apt-get update \
10 && apt-get install --no-install-recommends -y \
11 ca-certificates \
12 curl \
13 language-pack-de \
14 language-pack-en \
15 language-pack-fr \
16 locales \
17 make \
18 php7.0 \
19 php7.0-curl \
20 php7.0-gd \
21 php7.0-intl \
22 php7.0-xml \
23 php-xdebug \
24 rsync \
25 && apt-get clean
26
27ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
28RUN chmod 755 /usr/local/bin/composer
29
30RUN useradd -m dev \
31 && mkdir /shaarli
32USER dev
33WORKDIR /shaarli
34
35ENTRYPOINT ["make"]
36CMD []
diff --git a/index.php b/index.php
index 7df6d819..fb00a9fa 100644
--- a/index.php
+++ b/index.php
@@ -583,20 +583,29 @@ function showDailyRSS($conf) {
583 */ 583 */
584function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) 584function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
585{ 585{
586 $day=date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 586 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
587 if (isset($_GET['day'])) $day=$_GET['day']; 587 if (isset($_GET['day'])) {
588 $day = $_GET['day'];
589 }
588 590
589 $days = $LINKSDB->days(); 591 $days = $LINKSDB->days();
590 $i = array_search($day,$days); 592 $i = array_search($day, $days);
591 if ($i===false) { $i=count($days)-1; $day=$days[$i]; } 593 if ($i === false && count($days)) {
592 $previousday=''; 594 // no links for day, but at least one day with links
593 $nextday=''; 595 $i = count($days) - 1;
594 if ($i!==false) 596 $day = $days[$i];
595 {
596 if ($i>=1) $previousday=$days[$i-1];
597 if ($i<count($days)-1) $nextday=$days[$i+1];
598 } 597 }
598 $previousday = '';
599 $nextday = '';
599 600
601 if ($i !== false) {
602 if ($i >= 1) {
603 $previousday=$days[$i - 1];
604 }
605 if ($i < count($days) - 1) {
606 $nextday = $days[$i + 1];
607 }
608 }
600 try { 609 try {
601 $linksToDisplay = $LINKSDB->filterDay($day); 610 $linksToDisplay = $LINKSDB->filterDay($day);
602 } catch (Exception $exc) { 611 } catch (Exception $exc) {
@@ -605,9 +614,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
605 } 614 }
606 615
607 // We pre-format some fields for proper output. 616 // We pre-format some fields for proper output.
608 foreach($linksToDisplay as $key=>$link) 617 foreach($linksToDisplay as $key => $link) {
609 {
610
611 $taglist = explode(' ',$link['tags']); 618 $taglist = explode(' ',$link['tags']);
612 uasort($taglist, 'strcasecmp'); 619 uasort($taglist, 'strcasecmp');
613 $linksToDisplay[$key]['taglist']=$taglist; 620 $linksToDisplay[$key]['taglist']=$taglist;
@@ -621,21 +628,22 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
621 so I manually spread entries with a simple method: I roughly evaluate the 628 so I manually spread entries with a simple method: I roughly evaluate the
622 height of a div according to title and description length. 629 height of a div according to title and description length.
623 */ 630 */
624 $columns=array(array(),array(),array()); // Entries to display, for each column. 631 $columns = array(array(), array(), array()); // Entries to display, for each column.
625 $fill=array(0,0,0); // Rough estimate of columns fill. 632 $fill = array(0, 0, 0); // Rough estimate of columns fill.
626 foreach($linksToDisplay as $key=>$link) 633 foreach($linksToDisplay as $key => $link) {
627 {
628 // Roughly estimate length of entry (by counting characters) 634 // Roughly estimate length of entry (by counting characters)
629 // Title: 30 chars = 1 line. 1 line is 30 pixels height. 635 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
630 // Description: 836 characters gives roughly 342 pixel height. 636 // Description: 836 characters gives roughly 342 pixel height.
631 // This is not perfect, but it's usually OK. 637 // This is not perfect, but it's usually OK.
632 $length=strlen($link['title'])+(342*strlen($link['description']))/836; 638 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836;
633 if ($link['thumbnail']) $length +=100; // 1 thumbnails roughly takes 100 pixels height. 639 if ($link['thumbnail']) {
640 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
641 }
634 // Then put in column which is the less filled: 642 // Then put in column which is the less filled:
635 $smallest=min($fill); // find smallest value in array. 643 $smallest = min($fill); // find smallest value in array.
636 $index=array_search($smallest,$fill); // find index of this smallest value. 644 $index = array_search($smallest, $fill); // find index of this smallest value.
637 array_push($columns[$index],$link); // Put entry in this column. 645 array_push($columns[$index], $link); // Put entry in this column.
638 $fill[$index]+=$length; 646 $fill[$index] += $length;
639 } 647 }
640 648
641 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 649 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
@@ -710,6 +718,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
710 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 718 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
711 $targetPage = Router::findPage($query, $_GET, isLoggedIn()); 719 $targetPage = Router::findPage($query, $_GET, isLoggedIn());
712 720
721 if (
722 // if the user isn't logged in
723 !isLoggedIn() &&
724 // and Shaarli doesn't have public content...
725 $conf->get('privacy.hide_public_links') &&
726 // and is configured to enforce the login
727 $conf->get('privacy.force_login') &&
728 // and the current page isn't already the login page
729 $targetPage !== Router::$PAGE_LOGIN &&
730 // and the user is not requesting a feed (which would lead to a different content-type as expected)
731 $targetPage !== Router::$PAGE_FEED_ATOM &&
732 $targetPage !== Router::$PAGE_FEED_RSS
733 ) {
734 // force current page to be the login page
735 $targetPage = Router::$PAGE_LOGIN;
736 }
737
713 // Call plugin hooks for header, footer and includes, specifying which page will be rendered. 738 // Call plugin hooks for header, footer and includes, specifying which page will be rendered.
714 // Then assign generated data to RainTPL. 739 // Then assign generated data to RainTPL.
715 $common_hooks = array( 740 $common_hooks = array(
@@ -737,6 +762,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
737 $PAGE->assign('username', escape($_GET['username'])); 762 $PAGE->assign('username', escape($_GET['username']));
738 } 763 }
739 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); 764 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
765 // add default state of the 'remember me' checkbox
766 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default'));
740 $PAGE->renderPage('loginform'); 767 $PAGE->renderPage('loginform');
741 exit; 768 exit;
742 } 769 }
@@ -1055,10 +1082,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1055 // -------- Display the Tools menu if requested (import/export/bookmarklet...) 1082 // -------- Display the Tools menu if requested (import/export/bookmarklet...)
1056 if ($targetPage == Router::$PAGE_TOOLS) 1083 if ($targetPage == Router::$PAGE_TOOLS)
1057 { 1084 {
1058 $data = array( 1085 $data = [
1059 'pageabsaddr' => index_url($_SERVER), 1086 'pageabsaddr' => index_url($_SERVER),
1060 'sslenabled' => !empty($_SERVER['HTTPS']) 1087 'sslenabled' => is_https($_SERVER),
1061 ); 1088 ];
1062 $pluginManager->executeHooks('render_tools', $data); 1089 $pluginManager->executeHooks('render_tools', $data);
1063 1090
1064 foreach ($data as $key => $value) { 1091 foreach ($data as $key => $value) {
@@ -1320,10 +1347,17 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
1320 die('Wrong token.'); 1347 die('Wrong token.');
1321 } 1348 }
1322 1349
1323 if (strpos($_GET['lf_linkdate'], ' ') !== false) { 1350 $ids = trim($_GET['lf_linkdate']);
1324 $ids = array_values(array_filter(preg_split('/\s+/', escape($_GET['lf_linkdate'])))); 1351 if (strpos($ids, ' ') !== false) {
1352 // multiple, space-separated ids provided
1353 $ids = array_values(array_filter(preg_split('/\s+/', escape($ids))));
1325 } else { 1354 } else {
1326 $ids = [$_GET['lf_linkdate']]; 1355 // only a single id provided
1356 $ids = [$ids];
1357 }
1358 // assert at least one id is given
1359 if(!count($ids)){
1360 die('no id provided');
1327 } 1361 }
1328 foreach ($ids as $id) { 1362 foreach ($ids as $id) {
1329 $id = (int) escape($id); 1363 $id = (int) escape($id);
diff --git a/mkdocs.yml b/mkdocs.yml
index 648d8f67..03a7a34e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -45,6 +45,7 @@ pages:
45 - Static analysis: Static-analysis.md 45 - Static analysis: Static-analysis.md
46 - Theming: Theming.md 46 - Theming: Theming.md
47 - Unit tests: Unit-tests.md 47 - Unit tests: Unit-tests.md
48 - Unit tests inside Docker: Unit-tests-Docker.md
48- About: 49- About:
49 - FAQ: FAQ.md 50 - FAQ: FAQ.md
50 - Community & Related software: Community-&-Related-software.md 51 - Community & Related software: Community-&-Related-software.md
diff --git a/tests/HttpUtils/IsHttpsTest.php b/tests/HttpUtils/IsHttpsTest.php
new file mode 100644
index 00000000..097f2bcf
--- /dev/null
+++ b/tests/HttpUtils/IsHttpsTest.php
@@ -0,0 +1,36 @@
1<?php
2
3
4/**
5 * Class IsHttpsTest
6 *
7 * Test class for is_https() function.
8 */
9class IsHttpsTest extends PHPUnit_Framework_TestCase
10{
11
12 /**
13 * Test is_https with HTTPS values.
14 */
15 public function testIsHttpsTrue()
16 {
17 $this->assertTrue(is_https(['HTTPS' => true]));
18 $this->assertTrue(is_https(['HTTPS' => '1']));
19 $this->assertTrue(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => 443]));
20 $this->assertTrue(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => '443']));
21 $this->assertTrue(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => '443,123,456,']));
22 }
23
24 /**
25 * Test is_https with HTTP values.
26 */
27 public function testIsHttpsFalse()
28 {
29 $this->assertFalse(is_https([]));
30 $this->assertFalse(is_https(['HTTPS' => false]));
31 $this->assertFalse(is_https(['HTTPS' => '0']));
32 $this->assertFalse(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => 123]));
33 $this->assertFalse(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => '123']));
34 $this->assertFalse(is_https(['HTTPS' => false, 'HTTP_X_FORWARDED_PORT' => ',123,456,']));
35 }
36}
diff --git a/tests/languages/de/UtilsDeTest.php b/tests/languages/de/UtilsDeTest.php
index 6c9c9adc..4569c923 100644
--- a/tests/languages/de/UtilsDeTest.php
+++ b/tests/languages/de/UtilsDeTest.php
@@ -81,12 +81,12 @@ class UtilsDeTest extends UtilsTest
81 } 81 }
82 82
83 /** 83 /**
84 * Test autoLocale with multiples value, the second one is valid 84 * Test autoLocale with multiples value, the second one is available
85 */ 85 */
86 public function testAutoLocaleMultipleSecondValid() 86 public function testAutoLocaleMultipleSecondAvailable()
87 { 87 {
88 $current = setlocale(LC_ALL, 0); 88 $current = setlocale(LC_ALL, 0);
89 $header = 'pt_BR,fr-fr'; 89 $header = 'mag_IN,fr-fr';
90 autoLocale($header); 90 autoLocale($header);
91 $this->assertEquals('fr_FR.utf8', setlocale(LC_ALL, 0)); 91 $this->assertEquals('fr_FR.utf8', setlocale(LC_ALL, 0));
92 92
@@ -106,12 +106,12 @@ class UtilsDeTest extends UtilsTest
106 } 106 }
107 107
108 /** 108 /**
109 * Test autoLocale with an invalid value: defaults to en_US. 109 * Test autoLocale with an unavailable value: defaults to en_US.
110 */ 110 */
111 public function testAutoLocaleInvalid() 111 public function testAutoLocaleUnavailable()
112 { 112 {
113 $current = setlocale(LC_ALL, 0); 113 $current = setlocale(LC_ALL, 0);
114 autoLocale('pt_BR'); 114 autoLocale('mag_IN');
115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0)); 115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0));
116 116
117 setlocale(LC_ALL, $current); 117 setlocale(LC_ALL, $current);
diff --git a/tests/languages/en/UtilsEnTest.php b/tests/languages/en/UtilsEnTest.php
index d8680b2b..a74063ae 100644
--- a/tests/languages/en/UtilsEnTest.php
+++ b/tests/languages/en/UtilsEnTest.php
@@ -81,12 +81,12 @@ class UtilsEnTest extends UtilsTest
81 } 81 }
82 82
83 /** 83 /**
84 * Test autoLocale with multiples value, the second one is valid 84 * Test autoLocale with multiples value, the second one is available
85 */ 85 */
86 public function testAutoLocaleMultipleSecondValid() 86 public function testAutoLocaleMultipleSecondAvailable()
87 { 87 {
88 $current = setlocale(LC_ALL, 0); 88 $current = setlocale(LC_ALL, 0);
89 $header = 'pt_BR,fr-fr'; 89 $header = 'mag_IN,fr-fr';
90 autoLocale($header); 90 autoLocale($header);
91 $this->assertEquals('fr_FR.utf8', setlocale(LC_ALL, 0)); 91 $this->assertEquals('fr_FR.utf8', setlocale(LC_ALL, 0));
92 92
@@ -106,12 +106,12 @@ class UtilsEnTest extends UtilsTest
106 } 106 }
107 107
108 /** 108 /**
109 * Test autoLocale with an invalid value: defaults to en_US. 109 * Test autoLocale with an unavailable value: defaults to en_US.
110 */ 110 */
111 public function testAutoLocaleInvalid() 111 public function testAutoLocaleUnavailable()
112 { 112 {
113 $current = setlocale(LC_ALL, 0); 113 $current = setlocale(LC_ALL, 0);
114 autoLocale('pt_BR'); 114 autoLocale('mag_IN');
115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0)); 115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0));
116 116
117 setlocale(LC_ALL, $current); 117 setlocale(LC_ALL, $current);
diff --git a/tests/languages/fr/UtilsFrTest.php b/tests/languages/fr/UtilsFrTest.php
index 0d50a878..3dbb126f 100644
--- a/tests/languages/fr/UtilsFrTest.php
+++ b/tests/languages/fr/UtilsFrTest.php
@@ -81,12 +81,12 @@ class UtilsFrTest extends UtilsTest
81 } 81 }
82 82
83 /** 83 /**
84 * Test autoLocale with multiples value, the second one is valid 84 * Test autoLocale with multiples value, the second one is available
85 */ 85 */
86 public function testAutoLocaleMultipleSecondValid() 86 public function testAutoLocaleMultipleSecondAvailable()
87 { 87 {
88 $current = setlocale(LC_ALL, 0); 88 $current = setlocale(LC_ALL, 0);
89 $header = 'pt_BR,de-de'; 89 $header = 'mag_IN,de-de';
90 autoLocale($header); 90 autoLocale($header);
91 $this->assertEquals('de_DE.utf8', setlocale(LC_ALL, 0)); 91 $this->assertEquals('de_DE.utf8', setlocale(LC_ALL, 0));
92 92
@@ -106,12 +106,12 @@ class UtilsFrTest extends UtilsTest
106 } 106 }
107 107
108 /** 108 /**
109 * Test autoLocale with an invalid value: defaults to en_US. 109 * Test autoLocale with an unavailable value: defaults to en_US.
110 */ 110 */
111 public function testAutoLocaleInvalid() 111 public function testAutoLocaleUnavailable()
112 { 112 {
113 $current = setlocale(LC_ALL, 0); 113 $current = setlocale(LC_ALL, 0);
114 autoLocale('pt_BR'); 114 autoLocale('mag_IN');
115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0)); 115 $this->assertEquals('en_US.utf8', setlocale(LC_ALL, 0));
116 116
117 setlocale(LC_ALL, $current); 117 setlocale(LC_ALL, $current);
diff --git a/tpl/default/css/shaarli.css b/tpl/default/css/shaarli.css
index e1868c59..ba589723 100644
--- a/tpl/default/css/shaarli.css
+++ b/tpl/default/css/shaarli.css
@@ -539,7 +539,7 @@ body, .pure-g [class*="pure-u"] {
539} 539}
540 540
541.linklist-item-title a:visited .linklist-link { 541.linklist-item-title a:visited .linklist-link {
542 color: #555555; 542 color: #2a4c41;
543} 543}
544 544
545.linklist-item-title a:hover, .linklist-item-title .linklist-link:hover{ 545.linklist-item-title a:hover, .linklist-item-title .linklist-link:hover{
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js
index 4f49affa..1c66ebbd 100644
--- a/tpl/default/js/shaarli.js
+++ b/tpl/default/js/shaarli.js
@@ -401,14 +401,14 @@ window.onload = function () {
401 401
402 var message = 'Are you sure you want to delete '+ links.length +' links?\n'; 402 var message = 'Are you sure you want to delete '+ links.length +' links?\n';
403 message += 'This action is IRREVERSIBLE!\n\nTitles:\n'; 403 message += 'This action is IRREVERSIBLE!\n\nTitles:\n';
404 var ids = ''; 404 var ids = [];
405 links.forEach(function(item) { 405 links.forEach(function(item) {
406 message += ' - '+ item['title'] +'\n'; 406 message += ' - '+ item['title'] +'\n';
407 ids += item['id'] +'+'; 407 ids.push(item['id']);
408 }); 408 });
409 409
410 if (window.confirm(message)) { 410 if (window.confirm(message)) {
411 window.location = '?delete_link&lf_linkdate='+ ids +'&token='+ token.value; 411 window.location = '?delete_link&lf_linkdate='+ ids.join('+') +'&token='+ token.value;
412 } 412 }
413 }); 413 });
414 } 414 }
@@ -607,10 +607,11 @@ function htmlEntities(str)
607function activateFirefoxSocial(node) { 607function activateFirefoxSocial(node) {
608 var loc = location.href; 608 var loc = location.href;
609 var baseURL = loc.substring(0, loc.lastIndexOf("/") + 1); 609 var baseURL = loc.substring(0, loc.lastIndexOf("/") + 1);
610 var title = document.title;
610 611
611 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable. 612 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable.
612 var data = { 613 var data = {
613 name: "{$shaarlititle}", 614 name: title,
614 description: "The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community.", 615 description: "The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community.",
615 author: "Shaarli", 616 author: "Shaarli",
616 version: "1.0.0", 617 version: "1.0.0",
diff --git a/tpl/default/loginform.html b/tpl/default/loginform.html
index eb6d8378..5777a218 100644
--- a/tpl/default/loginform.html
+++ b/tpl/default/loginform.html
@@ -30,7 +30,8 @@
30 </div> 30 </div>
31 <div class="remember-me"> 31 <div class="remember-me">
32 <input type="checkbox" name="longlastingsession" id="longlastingsessionform" 32 <input type="checkbox" name="longlastingsession" id="longlastingsessionform"
33 checked="checked" tabindex="22"> 33 {if="$remember_user_default"}checked="checked"{/if}
34 tabindex="22">
34 <label for="longlastingsessionform">{'Remember me'|t}</label> 35 <label for="longlastingsessionform">{'Remember me'|t}</label>
35 </div> 36 </div>
36 <div> 37 <div>
diff --git a/tpl/vintage/loginform.html b/tpl/vintage/loginform.html
index 84176385..1becd44f 100644
--- a/tpl/vintage/loginform.html
+++ b/tpl/vintage/loginform.html
@@ -24,7 +24,9 @@
24 </label> 24 </label>
25 <input type="submit" value="Login" class="bigbutton" tabindex="4"> 25 <input type="submit" value="Login" class="bigbutton" tabindex="4">
26 <label for="longlastingsession"> 26 <label for="longlastingsession">
27 <input type="checkbox" name="longlastingsession" id="longlastingsession" tabindex="3"> 27 <input type="checkbox" name="longlastingsession"
28 id="longlastingsession" tabindex="3"
29 {if="$remember_user_default"}checked="checked"{/if}>
28 Stay signed in (Do not check on public computers)</label> 30 Stay signed in (Do not check on public computers)</label>
29 <input type="hidden" name="token" value="{$token}"> 31 <input type="hidden" name="token" value="{$token}">
30 {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if} 32 {if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if}