]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #846 from virtualtam/docker/alpine
authorVirtualTam <virtualtam+github@flibidi.net>
Sat, 21 Oct 2017 16:00:08 +0000 (18:00 +0200)
committerGitHub <noreply@github.com>
Sat, 21 Oct 2017 16:00:08 +0000 (18:00 +0200)
Docker: switch to Alpine Linux

21 files changed:
.editorconfig [new file with mode: 0644]
.gitattributes
.github/mailmap
AUTHORS
CHANGELOG.md
README.md
application/ApplicationUtils.php
application/History.php
application/NetscapeBookmarkUtils.php
application/PageBuilder.php
application/Updater.php
data/.htaccess
doc/md/Upgrade-and-migration.md
doc/md/docker/docker-101.md
doc/md/index.md
index.php
tests/NetscapeBookmarkUtils/BookmarkImportTest.php
tpl/default/includes.html
tpl/default/js/shaarli.js
tpl/default/page.footer.html
tpl/default/tag.cloud.html

diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..4a6589a
--- /dev/null
@@ -0,0 +1,23 @@
+# EditorConfig: http://EditorConfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 4
+
+[*.{htaccess,html,xml}]
+indent_size = 2
+
+[*.php]
+max_line_length = 100
+
+[Dockerfile]
+max_line_length = 80
+
+[Makefile]
+indent_style = tab
index dd0e573cffb0d6a6aa35bd3a701ea87522963387..939006027f24a5ef9643ffcaaf4620a1b9822a16 100644 (file)
@@ -24,6 +24,7 @@ Dockerfile      text
 *.min.js        binary
 
 # Exclude from Git archives
+.editorconfig   export-ignore
 .gitattributes  export-ignore
 .github         export-ignore
 .gitignore      export-ignore
index 41d91e4758eda026135d1a8d330955558b6b6dd3..bbdb7908fe9f91cddb04ac090fbd1226a1b7cb95 100644 (file)
@@ -11,3 +11,5 @@ Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurho
 VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
 VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net>
 VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org>
+Willi Eggeling <thewilli@gmail.com> <mail@wje-online.de>
+Willi Eggeling <thewilli@gmail.com> <thewilli@users.noreply.github.com>
diff --git a/AUTHORS b/AUTHORS
index 2181ec9d9869282b33dc6a3d3dcdddb8993c850d..105561c1ef3c303a28063a68dc1bd82a2b95adec 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,13 @@
-   518 ArthurHoaro <arthur@hoa.ro>
-   231 VirtualTam <virtualtam@flibidi.net>
-   147 nodiscc <nodiscc@gmail.com>
+   537 ArthurHoaro <arthur@hoa.ro>
+   252 VirtualTam <virtualtam@flibidi.net>
+   148 nodiscc <nodiscc@gmail.com>
     56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
     15 Florian Eula <eula.florian@gmail.com>
     13 Emilien Klein <emilien@klein.st>
     12 Nicolas Danelon <hi@nicolasmd.com.ar>
+     9 Willi Eggeling <thewilli@gmail.com>
      8 Christophe HENRY <christophe.henry@sbgodin.fr>
+     6 B. van Berkum <dev@dotmpe.com>
      5 Lucas Cimon <lucas.cimon@gmail.com>
      4 Alexandre Alapetite <alexandre@alapetite.fr>
      4 David Sferruzza <david.sferruzza@gmail.com>
@@ -37,6 +39,7 @@
      1 Kevin Canévet <kevin@streamroot.io>
      1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
      1 Lionel Martin <renarddesmers@gmail.com>
+     1 Mark Gerarts <mark.gerarts@gmail.com>
      1 Marsup <marsup@gmail.com>
      1 Sbgodin <Sbgodin@users.noreply.github.com>
      1 TsT <tst2005@gmail.com>
index 60262d564d3983084a96f823a241e64f49d80a8c..120c5d2250ed490ae2feb466a450b9b3ccc5499f 100644 (file)
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07
+
+**Major security issue fixed. Please update.**
+
+### Added
+- Tag search now supports wildcards `*`
+- New setting `privacy.force_login` which can be used with `privacy.hide_public_links` to redirect anonymous users to the login page.
+- New setting `general.default_note_title` used to override default `Note:` title prefix for notes.
+- Add a version hash for asset loading to prevent browser's cache issue
+
+### Changed
+- The "Remember me" checkbox is unchecked by default
+- The default value of the "Remember me" checkbox can be configured under `data/config.json.php`
+
+### Removed
+- Remove obsolete PHP magic quote support
+
+### Fixed
+- Generates a permalink URL if the URL is set to blank
+- Replace links to the old GitHub wiki with ReadTheDocs URIs
+- Use single quotes in the note bookmarklet
+- Daily page if there is no link
+- Bulk link deletion with a single link
+- HTTPS detection behind a reverse proxy
+- Travis tests environment and localization
+- Improve template paths robustness (trailing slash)
+- Robustness: safer gzinflate/zlib usage
+- Description links parsing with parenthesis (without Markdown)
+- Templates:
+    - Sort the tag cloud alphabetically
+    - Firefox social title
+    - Improved visited link color
+    - Fix jumpy textarea with long content in post edit
+
+### Security
+
+- Vulnerability introduced in v0.9.1 fixed.
+
 ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23
 
 The documentation has been migrated to ReadTheDocs:
index 100ff46bc94d90200b10b3b96e3b0c1dd0e06453..c10500274477551047071a1e76b25aee689e06b9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ _It is designed to be personal (single-user), fast and handy._
 [![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
 [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
 &bull;
-[![](https://img.shields.io/badge/latest-v0.9.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1)
+[![](https://img.shields.io/badge/latest-v0.9.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2)
 [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
 &bull;
 [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
index 123cc0b3e567ef531e4123dff6d31149d5f69ff5..5643f4a09706f2bb5b867153ee09c3c4974d77c3 100644 (file)
@@ -221,4 +221,19 @@ class ApplicationUtils
 
         return $errors;
     }
+
+    /**
+     * Returns a salted hash representing the current Shaarli version.
+     *
+     * Useful for assets browser cache.
+     *
+     * @param string $currentVersion of Shaarli
+     * @param string $salt           User personal salt, also used for the authentication
+     *
+     * @return string version hash
+     */
+    public static function getVersionHash($currentVersion, $salt)
+    {
+        return hash_hmac('sha256', $currentVersion, $salt);
+    }
 }
index 116b9264019c2a667e19f2d1e47aee0d5ab0b362..5e3b1b72d475ce97fd05692bec751e661b13e182 100644 (file)
@@ -16,6 +16,7 @@
  *   - UPDATED: link updated
  *   - DELETED: link deleted
  *   - SETTINGS: the settings have been updated through the UI.
+ *   - IMPORT: bulk links import
  *
  * Note: new events are put at the beginning of the file and history array.
  */
@@ -41,6 +42,11 @@ class History
      */
     const SETTINGS = 'SETTINGS';
 
+    /**
+     * @var string Action key: a bulk import has been processed.
+     */
+    const IMPORT = 'IMPORT';
+
     /**
      * @var string History file path.
      */
@@ -121,6 +127,16 @@ class History
         $this->addEvent(self::SETTINGS);
     }
 
+    /**
+     * Add Event: bulk import.
+     *
+     * Note: we don't store links add/update one by one since it can have a huge impact on performances.
+     */
+    public function importLinks()
+    {
+        $this->addEvent(self::IMPORT);
+    }
+
     /**
      * Save a new event and write it in the history file.
      *
index 2a10ff22149f8c89f30458d9e5c907e19847438a..3179636799622178bf7132fc2ede3ed6fe6521fd 100644 (file)
@@ -66,6 +66,7 @@ class NetscapeBookmarkUtils
      * @param int    $importCount    how many links were imported
      * @param int    $overwriteCount how many links were overwritten
      * @param int    $skipCount      how many links were skipped
+     * @param int    $duration       how many seconds did the import take
      *
      * @return string Summary of the bookmark import status
      */
@@ -74,14 +75,16 @@ class NetscapeBookmarkUtils
         $filesize,
         $importCount=0,
         $overwriteCount=0,
-        $skipCount=0
+        $skipCount=0,
+        $duration=0
     )
     {
         $status = 'File '.$filename.' ('.$filesize.' bytes) ';
         if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
             $status .= 'has an unknown file format. Nothing was imported.';
         } else {
-            $status .= 'was successfully processed: '.$importCount.' links imported, ';
+            $status .= 'was successfully processed in '. $duration .' seconds: ';
+            $status .= $importCount.' links imported, ';
             $status .= $overwriteCount.' links overwritten, ';
             $status .= $skipCount.' links skipped.';
         }
@@ -101,6 +104,7 @@ class NetscapeBookmarkUtils
      */
     public static function import($post, $files, $linkDb, $conf, $history)
     {
+        $start = time();
         $filename = $files['filetoupload']['name'];
         $filesize = $files['filetoupload']['size'];
         $data = file_get_contents($files['filetoupload']['tmp_name']);
@@ -184,7 +188,6 @@ class NetscapeBookmarkUtils
                 $linkDb[$existingLink['id']] = $newLink;
                 $importCount++;
                 $overwriteCount++;
-                $history->updateLink($newLink);
                 continue;
             }
 
@@ -196,16 +199,19 @@ class NetscapeBookmarkUtils
             $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
             $linkDb[$newLink['id']] = $newLink;
             $importCount++;
-            $history->addLink($newLink);
         }
 
         $linkDb->save($conf->get('resource.page_cache'));
+        $history->importLinks();
+
+        $duration = time() - $start;
         return self::importStatus(
             $filename,
             $filesize,
             $importCount,
             $overwriteCount,
-            $skipCount
+            $skipCount,
+            $duration
         );
     }
 }
index 7a42400d88f6baa2444932d179985f5ad89cd14a..291860adeb61912af4f8671c8544849224804713 100644 (file)
@@ -49,7 +49,7 @@ class PageBuilder
 
         try {
             $version = ApplicationUtils::checkUpdate(
-                shaarli_version,
+                SHAARLI_VERSION,
                 $this->conf->get('resource.update_check'),
                 $this->conf->get('updates.check_updates_interval'),
                 $this->conf->get('updates.check_updates'),
@@ -75,7 +75,11 @@ class PageBuilder
         }
         $this->tpl->assign('searchcrits', $searchcrits);
         $this->tpl->assign('source', index_url($_SERVER));
-        $this->tpl->assign('version', shaarli_version);
+        $this->tpl->assign('version', SHAARLI_VERSION);
+        $this->tpl->assign(
+            'version_hash',
+            ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
+        );
         $this->tpl->assign('scripturl', index_url($_SERVER));
         $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
         $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
@@ -89,6 +93,7 @@ class PageBuilder
         $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
         $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
         $this->tpl->assign('token', getToken($this->conf));
+
         if ($this->linkDB !== null) {
             $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
         }
index 40a15906b6bac9b53fb54faff0232b6514dd76ec..72b2def019dea484d28b6de040097d614dc6e867 100644 (file)
@@ -398,7 +398,7 @@ class Updater
      */
     public function updateMethodCheckUpdateRemoteBranch()
     {
-        if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
+        if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
             return true;
         }
 
@@ -413,7 +413,7 @@ class Updater
         $latestMajor = $matches[1];
 
         // Get current major version digit
-        preg_match('/(\d+)\.\d+$/', shaarli_version, $matches);
+        preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
         $currentMajor = $matches[1];
 
         if ($currentMajor === $latestMajor) {
index f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a..1d49da37a072dea22d567d4326563a7a07bf4534 100644 (file)
@@ -1,10 +1,16 @@
 <IfModule version_module>
   <IfVersion >= 2.4>
-     Require all denied
+    Require all denied
+    <Files "user.css">
+      Require all granted
+    </Files>
   </IfVersion>
   <IfVersion < 2.4>
-     Allow from none
-     Deny from all
+    Allow from none
+    Deny from all
+    <Files "user.css">
+      Allow from all
+    </Files>
   </IfVersion>
 </IfModule>
 
index b3a087644e9b7be11ca1624c5cfaba74e00534a3..7033cd41b4b995b90610051109f0038c4525b414 100644 (file)
@@ -14,7 +14,7 @@ Shaarli stores all user data under the `data` directory:
 - `data/ipbans.php` - banned IP addresses
 - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
 
-See [Shaarli configuration](Shaarli configuration) for more information about Shaarli resources.
+See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources.
 
 It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
 
@@ -27,7 +27,7 @@ As all user data is kept under `data`, this is the only directory you need to wo
 
 - backup the `data` directory
 - install or update Shaarli:
-    - fresh installation - see [Download and installation](Download and installation)
+    - fresh installation - see [Download and installation](Download-and-installation)
     - update - see the following sections
 - check or restore the `data` directory
 
@@ -35,11 +35,11 @@ As all user data is kept under `data`, this is the only directory you need to wo
 
 All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.
 
-We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download and installation) for `git` complete instructions.
+We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download-and-installation) for `git` complete instructions.
 
 Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
 
-After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli configuration) for more details).
+After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration) for more details).
 
 ## Upgrading with Git
 
@@ -173,7 +173,7 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to
 
 #### Step 3: configuration
 
-After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli configuration) for more details).
+After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration) for more details).
 
 ## Troubleshooting
 
index b02dd149a8be61974cb4469ecb0327217d0c3fb0..a9c00b85cbda8c246855934ec7b74dcc856ae26c 100644 (file)
@@ -60,3 +60,81 @@ wheezy: Pulling from debian
 Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
 Status: Downloaded newer image for debian:wheezy
 ```
+
+Docker re-uses layers already downloaded. In other words if you have images based on Alpine or some Ubuntu version for example, those can share disk space.
+
+### Start a container
+A container is an instance created from an image, that can be run and that keeps running until its main process exits. Or until the user stops the container. 
+
+The simplest way to start a container from image is ``docker run``. It also pulls the image for you if it is not locally available. For more advanced use, refer to ``docker create``.
+
+Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter:
+```bash
+$ docker ps -a
+```
+
+Some containers may be designed or configured to be restarted, others are not. Also remember both network ports and volumes of a container are created on start, and not editable later.
+
+### Access a running container
+A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container:
+```bash
+$ docker exec -ti <container-name-or-id> bash
+```
+Note the names and ID's of containers are listed in ``docker ps``. You can even type only one or two letters of the ID, given they are unique.
+
+Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``.
+
+You can view the console output of the main container process too:
+```bash
+$ docker logs -f <container-name-or-id>
+```
+
+### Docker disk use
+Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space.
+
+If you care only about trying out docker and not about what is running or saved, the following commands should help you out quickly if you run low on disk space:
+
+```bash
+$ docker rmi -f $(docker images -aq) # remove or mark all images for disposal
+$ docker volume rm $(docker volume ls -q) # remove all volumes
+```
+
+### Systemd config
+Systemd is the process manager of choice on Debian-based distributions. Once you have a ``docker`` service installed, you can use the following steps to set up Shaarli to run on system start.
+
+```bash
+systemctl enable /etc/systemd/system/docker.shaarli.service
+systemctl start docker.shaarli
+systemctl status docker.*
+journalctl -f # inspect system log if needed
+```
+
+You will need sudo or a root terminal to perform some or all of the steps above. Here are the contents for the service file:
+```
+[Unit]
+Description=Shaarli Bookmark Manager Container
+After=docker.service
+Requires=docker.service
+
+
+[Service]
+Restart=always
+
+# Put any environment you want in an included file, like $host- or $domainname in this example
+EnvironmentFile=/etc/sysconfig/box-environment
+
+# It's just an example..
+ExecStart=/usr/bin/docker run \
+  -p 28010:80 \
+  --name ${hostname}-shaarli \
+  --hostname shaarli.${domainname} \
+  -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \
+  -v /etc/localtime:/etc/localtime:ro \
+  shaarli/shaarli:latest
+
+ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli
+
+
+[Install]
+WantedBy=multi-user.target
+```
index 24ada6c7cfafd5bcee221db47660ac6087d3a17b..2b7d0f0070de616c3de1f48d0250ef62b451b67f 100644 (file)
@@ -22,6 +22,17 @@ It runs the latest development version of Shaarli and is updated/reset daily.
 
 Login: `demo`; Password: `demo`
 
+Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
+```
+MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
+docker run -ti --rm \
+         -p 8000:80 \
+         -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
+         shaarli/shaarli
+```
+
+A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101).
+To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation.
 
 ## Features
 
index 43aab303c67f84c51fcf274cd478407af7fa7260..4068a828f10293ee40a8e245e5052e908cbe8bf3 100644 (file)
--- a/index.php
+++ b/index.php
@@ -88,7 +88,7 @@ try {
     exit;
 }
 
-define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
+define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
 
 // Force cookie path (but do not change lifetime)
 $cookie = session_get_cookie_params();
@@ -840,7 +840,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
         }
 
         $data = array(
-            'search_tags' => implode(' ', $filteringTags),
+            'search_tags' => implode(' ', escape($filteringTags)),
             'tags' => $tagList,
         );
         $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
@@ -870,7 +870,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
         }
 
         $data = [
-            'search_tags' => implode(' ', $filteringTags),
+            'search_tags' => implode(' ', escape($filteringTags)),
             'tags' => $tags,
         ];
         $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]);
index 5fc1d1e830da9204ea62c0a17b8e9b4058ebcf46..4961aa2c4f73166d069144776cb574db9a537f67 100644 (file)
@@ -132,8 +132,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     public function testImportInternetExplorerEncoding()
     {
         $files = file2array('internet_explorer_encoding.htm');
-        $this->assertEquals(
-            'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:'
             .' 1 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -161,8 +161,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     public function testImportNested()
     {
         $files = file2array('netscape_nested.htm');
-        $this->assertEquals(
-            'File netscape_nested.htm (1337 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:'
             .' 8 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -283,8 +283,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     public function testImportDefaultPrivacyNoPost()
     {
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -328,8 +328,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     {
         $post = array('privacy' => 'default');
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -372,8 +372,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     {
         $post = array('privacy' => 'public');
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -396,8 +396,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     {
         $post = array('privacy' => 'private');
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -422,8 +422,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         // import links as private
         $post = array('privacy' => 'private');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -442,8 +442,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             'privacy' => 'public',
             'overwrite' => 'true'
         );
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 2 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -468,8 +468,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         // import links as public
         $post = array('privacy' => 'public');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -489,8 +489,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             'privacy' => 'private',
             'overwrite' => 'true'
         );
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 2 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -513,8 +513,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     {
         $post = array('privacy' => 'public');
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -523,8 +523,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         // re-import as private, DO NOT enable overwriting
         $post = array('privacy' => 'private');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 0 links imported, 0 links overwritten, 2 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -542,8 +542,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             'default_tags' => 'tag1,tag2 tag3'
         );
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -569,8 +569,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             'default_tags' => 'tag1&,tag2 "tag3"'
         );
         $files = file2array('netscape_basic.htm');
-        $this->assertEquals(
-            'File netscape_basic.htm (482 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:'
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -594,8 +594,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
     public function testImportSameDate()
     {
         $files = file2array('same_date.htm');
-        $this->assertEquals(
-            'File same_date.htm (453 bytes) was successfully processed:'
+        $this->assertStringMatchesFormat(
+            'File same_date.htm (453 bytes) was successfully processed in %d seconds:'
             .' 3 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
         );
@@ -622,24 +622,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             'overwrite' => 'true',
         ];
         $files = file2array('netscape_basic.htm');
-        $nbLinks = 2;
         NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
         $history = $this->history->getHistory();
-        $this->assertEquals($nbLinks, count($history));
-        foreach ($history as $value) {
-            $this->assertEquals(History::CREATED, $value['event']);
-            $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']);
-            $this->assertTrue(is_int($value['id']));
-        }
+        $this->assertEquals(1, count($history));
+        $this->assertEquals(History::IMPORT, $history[0]['event']);
+        $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
 
         // re-import as private, enable overwriting
         NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
         $history = $this->history->getHistory();
-        $this->assertEquals($nbLinks * 2, count($history));
-        for ($i = 0 ; $i < $nbLinks ; $i++) {
-            $this->assertEquals(History::UPDATED, $history[$i]['event']);
-            $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']);
-            $this->assertTrue(is_int($history[$i]['id']));
-        }
+        $this->assertEquals(2, count($history));
+        $this->assertEquals(History::IMPORT, $history[0]['event']);
+        $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']);
+        $this->assertEquals(History::IMPORT, $history[1]['event']);
+        $this->assertTrue(new DateTime('-5 seconds') < $history[1]['datetime']);
     }
 }
index 0350ef6681371e7a24263e29c8e29be8d2b5cef7..80c083331d10a31dced6a4188cfa3f858fbf5397 100644 (file)
@@ -5,16 +5,16 @@
 <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
 <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
 <link href="img/favicon.png" rel="shortcut icon" type="image/png" />
-<link type="text/css" rel="stylesheet" href="css/pure.min.css" />
-<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css">
-<link type="text/css" rel="stylesheet" href="css/pure-extras.css">
-<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" />
-<link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
-<link type="text/css" rel="stylesheet" href="css/shaarli.css" />
+<link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" />
+<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}">
+<link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}">
+<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css?v={$version_hash}" />
+<link type="text/css" rel="stylesheet" href="inc/awesomplete.css?v={$version_hash}#" />
+<link type="text/css" rel="stylesheet" href="css/shaarli.css?v={$version_hash}" />
 {if="is_file('data/user.css')"}
   <link type="text/css" rel="stylesheet" href="data/user.css#" />
 {/if}
 {loop="$plugins_includes.css_files"}
-  <link type="text/css" rel="stylesheet" href="{$value}#"/>
+  <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/>
 {/loop}
 <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/>
\ No newline at end of file
index 1c66ebbdd708f033c5ae26e747e3f313137edee7..55656f80ea39032dd62b321cf6e707aa3e786868 100644 (file)
@@ -275,8 +275,14 @@ window.onload = function () {
     };
     function init () {
         function resize () {
+            /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
+            var scrollTop  = window.pageYOffset ||
+                (document.documentElement || document.body.parentNode || document.body).scrollTop;
+
             description.style.height = 'auto';
             description.style.height = description.scrollHeight+10+'px';
+
+            window.scrollTo(0, scrollTop);
         }
         /* 0-timeout to get the already changed text */
         function delayedResize () {
index 94f771a250c40583cac13bdae5f8b08a65976eaa..54b16e8a3454dabcdd248dd21ae567ed6cac7814 100644 (file)
@@ -27,6 +27,6 @@
        <script src="{$value}#"></script>
 {/loop}
 
-<script src="js/shaarli.js"></script>
-<script src="inc/awesomplete.js#"></script>
-<script src="inc/awesomplete-multiple-tags.js#"></script>
+<script src="js/shaarli.js?v={$version_hash}"></script>
+<script src="inc/awesomplete.js?v={$version_hash}#"></script>
+<script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script>
index 96b357a3e1ec282885c4314388a0a67adb4131a5..68335c709ba90b5be6b79e80e2cabd9c03b05b9f 100644 (file)
@@ -26,7 +26,7 @@
           <input type="hidden" name="do" value="tagcloud">
           <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}"
                  {if="!empty($search_tags)"}
-                 value="{$search_tags}"
+                    value="{$search_tags}"
                  {/if}
           autocomplete="off" data-multiple data-autofirst data-minChars="1"
           data-list="{loop="$tags"}{$key}, {/loop}"