diff options
-rw-r--r-- | .editorconfig | 23 | ||||
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | .github/mailmap | 2 | ||||
-rw-r--r-- | AUTHORS | 9 | ||||
-rw-r--r-- | CHANGELOG.md | 38 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | application/ApplicationUtils.php | 15 | ||||
-rw-r--r-- | application/History.php | 16 | ||||
-rw-r--r-- | application/NetscapeBookmarkUtils.php | 16 | ||||
-rw-r--r-- | application/PageBuilder.php | 9 | ||||
-rw-r--r-- | application/Updater.php | 4 | ||||
-rw-r--r-- | data/.htaccess | 12 | ||||
-rw-r--r-- | doc/md/Upgrade-and-migration.md | 10 | ||||
-rw-r--r-- | doc/md/docker/docker-101.md | 78 | ||||
-rw-r--r-- | doc/md/index.md | 11 | ||||
-rw-r--r-- | index.php | 6 | ||||
-rw-r--r-- | tests/NetscapeBookmarkUtils/BookmarkImportTest.php | 81 | ||||
-rw-r--r-- | tpl/default/includes.html | 14 | ||||
-rw-r--r-- | tpl/default/js/shaarli.js | 6 | ||||
-rw-r--r-- | tpl/default/page.footer.html | 6 | ||||
-rw-r--r-- | tpl/default/tag.cloud.html | 2 |
21 files changed, 283 insertions, 78 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4a6589a2 --- /dev/null +++ b/.editorconfig | |||
@@ -0,0 +1,23 @@ | |||
1 | # EditorConfig: http://EditorConfig.org | ||
2 | |||
3 | root = true | ||
4 | |||
5 | [*] | ||
6 | charset = utf-8 | ||
7 | end_of_line = lf | ||
8 | insert_final_newline = true | ||
9 | trim_trailing_whitespace = true | ||
10 | indent_style = space | ||
11 | indent_size = 4 | ||
12 | |||
13 | [*.{htaccess,html,xml}] | ||
14 | indent_size = 2 | ||
15 | |||
16 | [*.php] | ||
17 | max_line_length = 100 | ||
18 | |||
19 | [Dockerfile] | ||
20 | max_line_length = 80 | ||
21 | |||
22 | [Makefile] | ||
23 | indent_style = tab | ||
diff --git a/.gitattributes b/.gitattributes index dd0e573c..93900602 100644 --- a/.gitattributes +++ b/.gitattributes | |||
@@ -24,6 +24,7 @@ Dockerfile text | |||
24 | *.min.js binary | 24 | *.min.js binary |
25 | 25 | ||
26 | # Exclude from Git archives | 26 | # Exclude from Git archives |
27 | .editorconfig export-ignore | ||
27 | .gitattributes export-ignore | 28 | .gitattributes export-ignore |
28 | .github export-ignore | 29 | .github export-ignore |
29 | .gitignore export-ignore | 30 | .gitignore export-ignore |
diff --git a/.github/mailmap b/.github/mailmap index 41d91e47..bbdb7908 100644 --- a/.github/mailmap +++ b/.github/mailmap | |||
@@ -11,3 +11,5 @@ Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurho | |||
11 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> | 11 | VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com> |
12 | VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net> | 12 | VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net> |
13 | VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org> | 13 | VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org> |
14 | Willi Eggeling <thewilli@gmail.com> <mail@wje-online.de> | ||
15 | Willi Eggeling <thewilli@gmail.com> <thewilli@users.noreply.github.com> | ||
@@ -1,11 +1,13 @@ | |||
1 | 518 ArthurHoaro <arthur@hoa.ro> | 1 | 537 ArthurHoaro <arthur@hoa.ro> |
2 | 231 VirtualTam <virtualtam@flibidi.net> | 2 | 252 VirtualTam <virtualtam@flibidi.net> |
3 | 147 nodiscc <nodiscc@gmail.com> | 3 | 148 nodiscc <nodiscc@gmail.com> |
4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> | 4 | 56 Sébastien Sauvage <sebsauvage@sebsauvage.net> |
5 | 15 Florian Eula <eula.florian@gmail.com> | 5 | 15 Florian Eula <eula.florian@gmail.com> |
6 | 13 Emilien Klein <emilien@klein.st> | 6 | 13 Emilien Klein <emilien@klein.st> |
7 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> | 7 | 12 Nicolas Danelon <hi@nicolasmd.com.ar> |
8 | 9 Willi Eggeling <thewilli@gmail.com> | ||
8 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> | 9 | 8 Christophe HENRY <christophe.henry@sbgodin.fr> |
10 | 6 B. van Berkum <dev@dotmpe.com> | ||
9 | 5 Lucas Cimon <lucas.cimon@gmail.com> | 11 | 5 Lucas Cimon <lucas.cimon@gmail.com> |
10 | 4 Alexandre Alapetite <alexandre@alapetite.fr> | 12 | 4 Alexandre Alapetite <alexandre@alapetite.fr> |
11 | 4 David Sferruzza <david.sferruzza@gmail.com> | 13 | 4 David Sferruzza <david.sferruzza@gmail.com> |
@@ -37,6 +39,7 @@ | |||
37 | 1 Kevin Canévet <kevin@streamroot.io> | 39 | 1 Kevin Canévet <kevin@streamroot.io> |
38 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> | 40 | 1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org> |
39 | 1 Lionel Martin <renarddesmers@gmail.com> | 41 | 1 Lionel Martin <renarddesmers@gmail.com> |
42 | 1 Mark Gerarts <mark.gerarts@gmail.com> | ||
40 | 1 Marsup <marsup@gmail.com> | 43 | 1 Marsup <marsup@gmail.com> |
41 | 1 Sbgodin <Sbgodin@users.noreply.github.com> | 44 | 1 Sbgodin <Sbgodin@users.noreply.github.com> |
42 | 1 TsT <tst2005@gmail.com> | 45 | 1 TsT <tst2005@gmail.com> |
diff --git a/CHANGELOG.md b/CHANGELOG.md index 60262d56..120c5d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file. | |||
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) |
5 | and this project adheres to [Semantic Versioning](http://semver.org/). | 5 | and this project adheres to [Semantic Versioning](http://semver.org/). |
6 | 6 | ||
7 | ## [v0.9.2](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) - 2017-10-07 | ||
8 | |||
9 | **Major security issue fixed. Please update.** | ||
10 | |||
11 | ### Added | ||
12 | - Tag search now supports wildcards `*` | ||
13 | - New setting `privacy.force_login` which can be used with `privacy.hide_public_links` to redirect anonymous users to the login page. | ||
14 | - New setting `general.default_note_title` used to override default `Note:` title prefix for notes. | ||
15 | - Add a version hash for asset loading to prevent browser's cache issue | ||
16 | |||
17 | ### Changed | ||
18 | - The "Remember me" checkbox is unchecked by default | ||
19 | - The default value of the "Remember me" checkbox can be configured under `data/config.json.php` | ||
20 | |||
21 | ### Removed | ||
22 | - Remove obsolete PHP magic quote support | ||
23 | |||
24 | ### Fixed | ||
25 | - Generates a permalink URL if the URL is set to blank | ||
26 | - Replace links to the old GitHub wiki with ReadTheDocs URIs | ||
27 | - Use single quotes in the note bookmarklet | ||
28 | - Daily page if there is no link | ||
29 | - Bulk link deletion with a single link | ||
30 | - HTTPS detection behind a reverse proxy | ||
31 | - Travis tests environment and localization | ||
32 | - Improve template paths robustness (trailing slash) | ||
33 | - Robustness: safer gzinflate/zlib usage | ||
34 | - Description links parsing with parenthesis (without Markdown) | ||
35 | - Templates: | ||
36 | - Sort the tag cloud alphabetically | ||
37 | - Firefox social title | ||
38 | - Improved visited link color | ||
39 | - Fix jumpy textarea with long content in post edit | ||
40 | |||
41 | ### Security | ||
42 | |||
43 | - Vulnerability introduced in v0.9.1 fixed. | ||
44 | |||
7 | ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 | 45 | ## [v0.9.1](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) - 2017-08-23 |
8 | 46 | ||
9 | The documentation has been migrated to ReadTheDocs: | 47 | The documentation has been migrated to ReadTheDocs: |
@@ -9,7 +9,7 @@ _It is designed to be personal (single-user), fast and handy._ | |||
9 | [![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) | 9 | [![](https://img.shields.io/badge/stable-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) |
10 | [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) | 10 | [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) |
11 | • | 11 | • |
12 | [![](https://img.shields.io/badge/latest-v0.9.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.1) | 12 | [![](https://img.shields.io/badge/latest-v0.9.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.2) |
13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) | 13 | [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) |
14 | • | 14 | • |
15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) | 15 | [![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli) |
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 123cc0b3..5643f4a0 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php | |||
@@ -221,4 +221,19 @@ class ApplicationUtils | |||
221 | 221 | ||
222 | return $errors; | 222 | return $errors; |
223 | } | 223 | } |
224 | |||
225 | /** | ||
226 | * Returns a salted hash representing the current Shaarli version. | ||
227 | * | ||
228 | * Useful for assets browser cache. | ||
229 | * | ||
230 | * @param string $currentVersion of Shaarli | ||
231 | * @param string $salt User personal salt, also used for the authentication | ||
232 | * | ||
233 | * @return string version hash | ||
234 | */ | ||
235 | public static function getVersionHash($currentVersion, $salt) | ||
236 | { | ||
237 | return hash_hmac('sha256', $currentVersion, $salt); | ||
238 | } | ||
224 | } | 239 | } |
diff --git a/application/History.php b/application/History.php index 116b9264..5e3b1b72 100644 --- a/application/History.php +++ b/application/History.php | |||
@@ -16,6 +16,7 @@ | |||
16 | * - UPDATED: link updated | 16 | * - UPDATED: link updated |
17 | * - DELETED: link deleted | 17 | * - DELETED: link deleted |
18 | * - SETTINGS: the settings have been updated through the UI. | 18 | * - SETTINGS: the settings have been updated through the UI. |
19 | * - IMPORT: bulk links import | ||
19 | * | 20 | * |
20 | * Note: new events are put at the beginning of the file and history array. | 21 | * Note: new events are put at the beginning of the file and history array. |
21 | */ | 22 | */ |
@@ -42,6 +43,11 @@ class History | |||
42 | const SETTINGS = 'SETTINGS'; | 43 | const SETTINGS = 'SETTINGS'; |
43 | 44 | ||
44 | /** | 45 | /** |
46 | * @var string Action key: a bulk import has been processed. | ||
47 | */ | ||
48 | const IMPORT = 'IMPORT'; | ||
49 | |||
50 | /** | ||
45 | * @var string History file path. | 51 | * @var string History file path. |
46 | */ | 52 | */ |
47 | protected $historyFilePath; | 53 | protected $historyFilePath; |
@@ -122,6 +128,16 @@ class History | |||
122 | } | 128 | } |
123 | 129 | ||
124 | /** | 130 | /** |
131 | * Add Event: bulk import. | ||
132 | * | ||
133 | * Note: we don't store links add/update one by one since it can have a huge impact on performances. | ||
134 | */ | ||
135 | public function importLinks() | ||
136 | { | ||
137 | $this->addEvent(self::IMPORT); | ||
138 | } | ||
139 | |||
140 | /** | ||
125 | * Save a new event and write it in the history file. | 141 | * Save a new event and write it in the history file. |
126 | * | 142 | * |
127 | * @param string $status Event key, should be defined as constant. | 143 | * @param string $status Event key, should be defined as constant. |
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php index 2a10ff22..31796367 100644 --- a/application/NetscapeBookmarkUtils.php +++ b/application/NetscapeBookmarkUtils.php | |||
@@ -66,6 +66,7 @@ class NetscapeBookmarkUtils | |||
66 | * @param int $importCount how many links were imported | 66 | * @param int $importCount how many links were imported |
67 | * @param int $overwriteCount how many links were overwritten | 67 | * @param int $overwriteCount how many links were overwritten |
68 | * @param int $skipCount how many links were skipped | 68 | * @param int $skipCount how many links were skipped |
69 | * @param int $duration how many seconds did the import take | ||
69 | * | 70 | * |
70 | * @return string Summary of the bookmark import status | 71 | * @return string Summary of the bookmark import status |
71 | */ | 72 | */ |
@@ -74,14 +75,16 @@ class NetscapeBookmarkUtils | |||
74 | $filesize, | 75 | $filesize, |
75 | $importCount=0, | 76 | $importCount=0, |
76 | $overwriteCount=0, | 77 | $overwriteCount=0, |
77 | $skipCount=0 | 78 | $skipCount=0, |
79 | $duration=0 | ||
78 | ) | 80 | ) |
79 | { | 81 | { |
80 | $status = 'File '.$filename.' ('.$filesize.' bytes) '; | 82 | $status = 'File '.$filename.' ('.$filesize.' bytes) '; |
81 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { | 83 | if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { |
82 | $status .= 'has an unknown file format. Nothing was imported.'; | 84 | $status .= 'has an unknown file format. Nothing was imported.'; |
83 | } else { | 85 | } else { |
84 | $status .= 'was successfully processed: '.$importCount.' links imported, '; | 86 | $status .= 'was successfully processed in '. $duration .' seconds: '; |
87 | $status .= $importCount.' links imported, '; | ||
85 | $status .= $overwriteCount.' links overwritten, '; | 88 | $status .= $overwriteCount.' links overwritten, '; |
86 | $status .= $skipCount.' links skipped.'; | 89 | $status .= $skipCount.' links skipped.'; |
87 | } | 90 | } |
@@ -101,6 +104,7 @@ class NetscapeBookmarkUtils | |||
101 | */ | 104 | */ |
102 | public static function import($post, $files, $linkDb, $conf, $history) | 105 | public static function import($post, $files, $linkDb, $conf, $history) |
103 | { | 106 | { |
107 | $start = time(); | ||
104 | $filename = $files['filetoupload']['name']; | 108 | $filename = $files['filetoupload']['name']; |
105 | $filesize = $files['filetoupload']['size']; | 109 | $filesize = $files['filetoupload']['size']; |
106 | $data = file_get_contents($files['filetoupload']['tmp_name']); | 110 | $data = file_get_contents($files['filetoupload']['tmp_name']); |
@@ -184,7 +188,6 @@ class NetscapeBookmarkUtils | |||
184 | $linkDb[$existingLink['id']] = $newLink; | 188 | $linkDb[$existingLink['id']] = $newLink; |
185 | $importCount++; | 189 | $importCount++; |
186 | $overwriteCount++; | 190 | $overwriteCount++; |
187 | $history->updateLink($newLink); | ||
188 | continue; | 191 | continue; |
189 | } | 192 | } |
190 | 193 | ||
@@ -196,16 +199,19 @@ class NetscapeBookmarkUtils | |||
196 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); | 199 | $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); |
197 | $linkDb[$newLink['id']] = $newLink; | 200 | $linkDb[$newLink['id']] = $newLink; |
198 | $importCount++; | 201 | $importCount++; |
199 | $history->addLink($newLink); | ||
200 | } | 202 | } |
201 | 203 | ||
202 | $linkDb->save($conf->get('resource.page_cache')); | 204 | $linkDb->save($conf->get('resource.page_cache')); |
205 | $history->importLinks(); | ||
206 | |||
207 | $duration = time() - $start; | ||
203 | return self::importStatus( | 208 | return self::importStatus( |
204 | $filename, | 209 | $filename, |
205 | $filesize, | 210 | $filesize, |
206 | $importCount, | 211 | $importCount, |
207 | $overwriteCount, | 212 | $overwriteCount, |
208 | $skipCount | 213 | $skipCount, |
214 | $duration | ||
209 | ); | 215 | ); |
210 | } | 216 | } |
211 | } | 217 | } |
diff --git a/application/PageBuilder.php b/application/PageBuilder.php index 7a42400d..291860ad 100644 --- a/application/PageBuilder.php +++ b/application/PageBuilder.php | |||
@@ -49,7 +49,7 @@ class PageBuilder | |||
49 | 49 | ||
50 | try { | 50 | try { |
51 | $version = ApplicationUtils::checkUpdate( | 51 | $version = ApplicationUtils::checkUpdate( |
52 | shaarli_version, | 52 | SHAARLI_VERSION, |
53 | $this->conf->get('resource.update_check'), | 53 | $this->conf->get('resource.update_check'), |
54 | $this->conf->get('updates.check_updates_interval'), | 54 | $this->conf->get('updates.check_updates_interval'), |
55 | $this->conf->get('updates.check_updates'), | 55 | $this->conf->get('updates.check_updates'), |
@@ -75,7 +75,11 @@ class PageBuilder | |||
75 | } | 75 | } |
76 | $this->tpl->assign('searchcrits', $searchcrits); | 76 | $this->tpl->assign('searchcrits', $searchcrits); |
77 | $this->tpl->assign('source', index_url($_SERVER)); | 77 | $this->tpl->assign('source', index_url($_SERVER)); |
78 | $this->tpl->assign('version', shaarli_version); | 78 | $this->tpl->assign('version', SHAARLI_VERSION); |
79 | $this->tpl->assign( | ||
80 | 'version_hash', | ||
81 | ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt')) | ||
82 | ); | ||
79 | $this->tpl->assign('scripturl', index_url($_SERVER)); | 83 | $this->tpl->assign('scripturl', index_url($_SERVER)); |
80 | $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? | 84 | $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? |
81 | $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); | 85 | $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); |
@@ -89,6 +93,7 @@ class PageBuilder | |||
89 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); | 93 | $this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss'); |
90 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); | 94 | $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); |
91 | $this->tpl->assign('token', getToken($this->conf)); | 95 | $this->tpl->assign('token', getToken($this->conf)); |
96 | |||
92 | if ($this->linkDB !== null) { | 97 | if ($this->linkDB !== null) { |
93 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); | 98 | $this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); |
94 | } | 99 | } |
diff --git a/application/Updater.php b/application/Updater.php index 40a15906..72b2def0 100644 --- a/application/Updater.php +++ b/application/Updater.php | |||
@@ -398,7 +398,7 @@ class Updater | |||
398 | */ | 398 | */ |
399 | public function updateMethodCheckUpdateRemoteBranch() | 399 | public function updateMethodCheckUpdateRemoteBranch() |
400 | { | 400 | { |
401 | if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { | 401 | if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') { |
402 | return true; | 402 | return true; |
403 | } | 403 | } |
404 | 404 | ||
@@ -413,7 +413,7 @@ class Updater | |||
413 | $latestMajor = $matches[1]; | 413 | $latestMajor = $matches[1]; |
414 | 414 | ||
415 | // Get current major version digit | 415 | // Get current major version digit |
416 | preg_match('/(\d+)\.\d+$/', shaarli_version, $matches); | 416 | preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches); |
417 | $currentMajor = $matches[1]; | 417 | $currentMajor = $matches[1]; |
418 | 418 | ||
419 | if ($currentMajor === $latestMajor) { | 419 | if ($currentMajor === $latestMajor) { |
diff --git a/data/.htaccess b/data/.htaccess index f601c1ee..1d49da37 100644 --- a/data/.htaccess +++ b/data/.htaccess | |||
@@ -1,10 +1,16 @@ | |||
1 | <IfModule version_module> | 1 | <IfModule version_module> |
2 | <IfVersion >= 2.4> | 2 | <IfVersion >= 2.4> |
3 | Require all denied | 3 | Require all denied |
4 | <Files "user.css"> | ||
5 | Require all granted | ||
6 | </Files> | ||
4 | </IfVersion> | 7 | </IfVersion> |
5 | <IfVersion < 2.4> | 8 | <IfVersion < 2.4> |
6 | Allow from none | 9 | Allow from none |
7 | Deny from all | 10 | Deny from all |
11 | <Files "user.css"> | ||
12 | Allow from all | ||
13 | </Files> | ||
8 | </IfVersion> | 14 | </IfVersion> |
9 | </IfModule> | 15 | </IfModule> |
10 | 16 | ||
diff --git a/doc/md/Upgrade-and-migration.md b/doc/md/Upgrade-and-migration.md index b3a08764..7033cd41 100644 --- a/doc/md/Upgrade-and-migration.md +++ b/doc/md/Upgrade-and-migration.md | |||
@@ -14,7 +14,7 @@ Shaarli stores all user data under the `data` directory: | |||
14 | - `data/ipbans.php` - banned IP addresses | 14 | - `data/ipbans.php` - banned IP addresses |
15 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run | 15 | - `data/updates.txt` - contains all automatic update to the configuration and datastore files already run |
16 | 16 | ||
17 | See [Shaarli configuration](Shaarli configuration) for more information about Shaarli resources. | 17 | See [Shaarli configuration](Shaarli-configuration) for more information about Shaarli resources. |
18 | 18 | ||
19 | It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: | 19 | It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: |
20 | 20 | ||
@@ -27,7 +27,7 @@ As all user data is kept under `data`, this is the only directory you need to wo | |||
27 | 27 | ||
28 | - backup the `data` directory | 28 | - backup the `data` directory |
29 | - install or update Shaarli: | 29 | - install or update Shaarli: |
30 | - fresh installation - see [Download and installation](Download and installation) | 30 | - fresh installation - see [Download and installation](Download-and-installation) |
31 | - update - see the following sections | 31 | - update - see the following sections |
32 | - check or restore the `data` directory | 32 | - check or restore the `data` directory |
33 | 33 | ||
@@ -35,11 +35,11 @@ As all user data is kept under `data`, this is the only directory you need to wo | |||
35 | 35 | ||
36 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. | 36 | All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page. |
37 | 37 | ||
38 | 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. | 38 | 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. |
39 | 39 | ||
40 | 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! | 40 | 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! |
41 | 41 | ||
42 | 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). | 42 | 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). |
43 | 43 | ||
44 | ## Upgrading with Git | 44 | ## Upgrading with Git |
45 | 45 | ||
@@ -173,7 +173,7 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to | |||
173 | 173 | ||
174 | #### Step 3: configuration | 174 | #### Step 3: configuration |
175 | 175 | ||
176 | 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). | 176 | 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). |
177 | 177 | ||
178 | ## Troubleshooting | 178 | ## Troubleshooting |
179 | 179 | ||
diff --git a/doc/md/docker/docker-101.md b/doc/md/docker/docker-101.md index b02dd149..a9c00b85 100644 --- a/doc/md/docker/docker-101.md +++ b/doc/md/docker/docker-101.md | |||
@@ -60,3 +60,81 @@ wheezy: Pulling from debian | |||
60 | Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe | 60 | Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe |
61 | Status: Downloaded newer image for debian:wheezy | 61 | Status: Downloaded newer image for debian:wheezy |
62 | ``` | 62 | ``` |
63 | |||
64 | 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. | ||
65 | |||
66 | ### Start a container | ||
67 | 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. | ||
68 | |||
69 | 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``. | ||
70 | |||
71 | Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter: | ||
72 | ```bash | ||
73 | $ docker ps -a | ||
74 | ``` | ||
75 | |||
76 | 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. | ||
77 | |||
78 | ### Access a running container | ||
79 | A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container: | ||
80 | ```bash | ||
81 | $ docker exec -ti <container-name-or-id> bash | ||
82 | ``` | ||
83 | 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. | ||
84 | |||
85 | Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``. | ||
86 | |||
87 | You can view the console output of the main container process too: | ||
88 | ```bash | ||
89 | $ docker logs -f <container-name-or-id> | ||
90 | ``` | ||
91 | |||
92 | ### Docker disk use | ||
93 | Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space. | ||
94 | |||
95 | 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: | ||
96 | |||
97 | ```bash | ||
98 | $ docker rmi -f $(docker images -aq) # remove or mark all images for disposal | ||
99 | $ docker volume rm $(docker volume ls -q) # remove all volumes | ||
100 | ``` | ||
101 | |||
102 | ### Systemd config | ||
103 | 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. | ||
104 | |||
105 | ```bash | ||
106 | systemctl enable /etc/systemd/system/docker.shaarli.service | ||
107 | systemctl start docker.shaarli | ||
108 | systemctl status docker.* | ||
109 | journalctl -f # inspect system log if needed | ||
110 | ``` | ||
111 | |||
112 | 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: | ||
113 | ``` | ||
114 | [Unit] | ||
115 | Description=Shaarli Bookmark Manager Container | ||
116 | After=docker.service | ||
117 | Requires=docker.service | ||
118 | |||
119 | |||
120 | [Service] | ||
121 | Restart=always | ||
122 | |||
123 | # Put any environment you want in an included file, like $host- or $domainname in this example | ||
124 | EnvironmentFile=/etc/sysconfig/box-environment | ||
125 | |||
126 | # It's just an example.. | ||
127 | ExecStart=/usr/bin/docker run \ | ||
128 | -p 28010:80 \ | ||
129 | --name ${hostname}-shaarli \ | ||
130 | --hostname shaarli.${domainname} \ | ||
131 | -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \ | ||
132 | -v /etc/localtime:/etc/localtime:ro \ | ||
133 | shaarli/shaarli:latest | ||
134 | |||
135 | ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli | ||
136 | |||
137 | |||
138 | [Install] | ||
139 | WantedBy=multi-user.target | ||
140 | ``` | ||
diff --git a/doc/md/index.md b/doc/md/index.md index 24ada6c7..2b7d0f00 100644 --- a/doc/md/index.md +++ b/doc/md/index.md | |||
@@ -22,6 +22,17 @@ It runs the latest development version of Shaarli and is updated/reset daily. | |||
22 | 22 | ||
23 | Login: `demo`; Password: `demo` | 23 | Login: `demo`; Password: `demo` |
24 | 24 | ||
25 | 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): | ||
26 | ``` | ||
27 | MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P) | ||
28 | docker run -ti --rm \ | ||
29 | -p 8000:80 \ | ||
30 | -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \ | ||
31 | shaarli/shaarli | ||
32 | ``` | ||
33 | |||
34 | A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101). | ||
35 | To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation. | ||
25 | 36 | ||
26 | ## Features | 37 | ## Features |
27 | 38 | ||
@@ -88,7 +88,7 @@ try { | |||
88 | exit; | 88 | exit; |
89 | } | 89 | } |
90 | 90 | ||
91 | define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); | 91 | define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE)); |
92 | 92 | ||
93 | // Force cookie path (but do not change lifetime) | 93 | // Force cookie path (but do not change lifetime) |
94 | $cookie = session_get_cookie_params(); | 94 | $cookie = session_get_cookie_params(); |
@@ -840,7 +840,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
840 | } | 840 | } |
841 | 841 | ||
842 | $data = array( | 842 | $data = array( |
843 | 'search_tags' => implode(' ', $filteringTags), | 843 | 'search_tags' => implode(' ', escape($filteringTags)), |
844 | 'tags' => $tagList, | 844 | 'tags' => $tagList, |
845 | ); | 845 | ); |
846 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); | 846 | $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); |
@@ -870,7 +870,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history) | |||
870 | } | 870 | } |
871 | 871 | ||
872 | $data = [ | 872 | $data = [ |
873 | 'search_tags' => implode(' ', $filteringTags), | 873 | 'search_tags' => implode(' ', escape($filteringTags)), |
874 | 'tags' => $tags, | 874 | 'tags' => $tags, |
875 | ]; | 875 | ]; |
876 | $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); | 876 | $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); |
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php index 5fc1d1e8..4961aa2c 100644 --- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php +++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php | |||
@@ -132,8 +132,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
132 | public function testImportInternetExplorerEncoding() | 132 | public function testImportInternetExplorerEncoding() |
133 | { | 133 | { |
134 | $files = file2array('internet_explorer_encoding.htm'); | 134 | $files = file2array('internet_explorer_encoding.htm'); |
135 | $this->assertEquals( | 135 | $this->assertStringMatchesFormat( |
136 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' | 136 | 'File internet_explorer_encoding.htm (356 bytes) was successfully processed in %d seconds:' |
137 | .' 1 links imported, 0 links overwritten, 0 links skipped.', | 137 | .' 1 links imported, 0 links overwritten, 0 links skipped.', |
138 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 138 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
139 | ); | 139 | ); |
@@ -161,8 +161,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
161 | public function testImportNested() | 161 | public function testImportNested() |
162 | { | 162 | { |
163 | $files = file2array('netscape_nested.htm'); | 163 | $files = file2array('netscape_nested.htm'); |
164 | $this->assertEquals( | 164 | $this->assertStringMatchesFormat( |
165 | 'File netscape_nested.htm (1337 bytes) was successfully processed:' | 165 | 'File netscape_nested.htm (1337 bytes) was successfully processed in %d seconds:' |
166 | .' 8 links imported, 0 links overwritten, 0 links skipped.', | 166 | .' 8 links imported, 0 links overwritten, 0 links skipped.', |
167 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 167 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
168 | ); | 168 | ); |
@@ -283,8 +283,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
283 | public function testImportDefaultPrivacyNoPost() | 283 | public function testImportDefaultPrivacyNoPost() |
284 | { | 284 | { |
285 | $files = file2array('netscape_basic.htm'); | 285 | $files = file2array('netscape_basic.htm'); |
286 | $this->assertEquals( | 286 | $this->assertStringMatchesFormat( |
287 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 287 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
288 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 288 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
289 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) | 289 | NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history) |
290 | ); | 290 | ); |
@@ -328,8 +328,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
328 | { | 328 | { |
329 | $post = array('privacy' => 'default'); | 329 | $post = array('privacy' => 'default'); |
330 | $files = file2array('netscape_basic.htm'); | 330 | $files = file2array('netscape_basic.htm'); |
331 | $this->assertEquals( | 331 | $this->assertStringMatchesFormat( |
332 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 332 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
333 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 333 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
334 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 334 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
335 | ); | 335 | ); |
@@ -372,8 +372,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
372 | { | 372 | { |
373 | $post = array('privacy' => 'public'); | 373 | $post = array('privacy' => 'public'); |
374 | $files = file2array('netscape_basic.htm'); | 374 | $files = file2array('netscape_basic.htm'); |
375 | $this->assertEquals( | 375 | $this->assertStringMatchesFormat( |
376 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 376 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
377 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 377 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
378 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 378 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
379 | ); | 379 | ); |
@@ -396,8 +396,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
396 | { | 396 | { |
397 | $post = array('privacy' => 'private'); | 397 | $post = array('privacy' => 'private'); |
398 | $files = file2array('netscape_basic.htm'); | 398 | $files = file2array('netscape_basic.htm'); |
399 | $this->assertEquals( | 399 | $this->assertStringMatchesFormat( |
400 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 400 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
401 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 401 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
402 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 402 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
403 | ); | 403 | ); |
@@ -422,8 +422,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
422 | 422 | ||
423 | // import links as private | 423 | // import links as private |
424 | $post = array('privacy' => 'private'); | 424 | $post = array('privacy' => 'private'); |
425 | $this->assertEquals( | 425 | $this->assertStringMatchesFormat( |
426 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 426 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
427 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 427 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
428 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 428 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
429 | ); | 429 | ); |
@@ -442,8 +442,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
442 | 'privacy' => 'public', | 442 | 'privacy' => 'public', |
443 | 'overwrite' => 'true' | 443 | 'overwrite' => 'true' |
444 | ); | 444 | ); |
445 | $this->assertEquals( | 445 | $this->assertStringMatchesFormat( |
446 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 446 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
447 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 447 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
448 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 448 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
449 | ); | 449 | ); |
@@ -468,8 +468,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
468 | 468 | ||
469 | // import links as public | 469 | // import links as public |
470 | $post = array('privacy' => 'public'); | 470 | $post = array('privacy' => 'public'); |
471 | $this->assertEquals( | 471 | $this->assertStringMatchesFormat( |
472 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 472 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
473 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 473 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
474 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 474 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
475 | ); | 475 | ); |
@@ -489,8 +489,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
489 | 'privacy' => 'private', | 489 | 'privacy' => 'private', |
490 | 'overwrite' => 'true' | 490 | 'overwrite' => 'true' |
491 | ); | 491 | ); |
492 | $this->assertEquals( | 492 | $this->assertStringMatchesFormat( |
493 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 493 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
494 | .' 2 links imported, 2 links overwritten, 0 links skipped.', | 494 | .' 2 links imported, 2 links overwritten, 0 links skipped.', |
495 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 495 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
496 | ); | 496 | ); |
@@ -513,8 +513,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
513 | { | 513 | { |
514 | $post = array('privacy' => 'public'); | 514 | $post = array('privacy' => 'public'); |
515 | $files = file2array('netscape_basic.htm'); | 515 | $files = file2array('netscape_basic.htm'); |
516 | $this->assertEquals( | 516 | $this->assertStringMatchesFormat( |
517 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 517 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
518 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 518 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
519 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 519 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
520 | ); | 520 | ); |
@@ -523,8 +523,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
523 | 523 | ||
524 | // re-import as private, DO NOT enable overwriting | 524 | // re-import as private, DO NOT enable overwriting |
525 | $post = array('privacy' => 'private'); | 525 | $post = array('privacy' => 'private'); |
526 | $this->assertEquals( | 526 | $this->assertStringMatchesFormat( |
527 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 527 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
528 | .' 0 links imported, 0 links overwritten, 2 links skipped.', | 528 | .' 0 links imported, 0 links overwritten, 2 links skipped.', |
529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 529 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
530 | ); | 530 | ); |
@@ -542,8 +542,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
542 | 'default_tags' => 'tag1,tag2 tag3' | 542 | 'default_tags' => 'tag1,tag2 tag3' |
543 | ); | 543 | ); |
544 | $files = file2array('netscape_basic.htm'); | 544 | $files = file2array('netscape_basic.htm'); |
545 | $this->assertEquals( | 545 | $this->assertStringMatchesFormat( |
546 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 546 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
547 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 547 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
548 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 548 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
549 | ); | 549 | ); |
@@ -569,8 +569,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
569 | 'default_tags' => 'tag1&,tag2 "tag3"' | 569 | 'default_tags' => 'tag1&,tag2 "tag3"' |
570 | ); | 570 | ); |
571 | $files = file2array('netscape_basic.htm'); | 571 | $files = file2array('netscape_basic.htm'); |
572 | $this->assertEquals( | 572 | $this->assertStringMatchesFormat( |
573 | 'File netscape_basic.htm (482 bytes) was successfully processed:' | 573 | 'File netscape_basic.htm (482 bytes) was successfully processed in %d seconds:' |
574 | .' 2 links imported, 0 links overwritten, 0 links skipped.', | 574 | .' 2 links imported, 0 links overwritten, 0 links skipped.', |
575 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) | 575 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history) |
576 | ); | 576 | ); |
@@ -594,8 +594,8 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
594 | public function testImportSameDate() | 594 | public function testImportSameDate() |
595 | { | 595 | { |
596 | $files = file2array('same_date.htm'); | 596 | $files = file2array('same_date.htm'); |
597 | $this->assertEquals( | 597 | $this->assertStringMatchesFormat( |
598 | 'File same_date.htm (453 bytes) was successfully processed:' | 598 | 'File same_date.htm (453 bytes) was successfully processed in %d seconds:' |
599 | .' 3 links imported, 0 links overwritten, 0 links skipped.', | 599 | .' 3 links imported, 0 links overwritten, 0 links skipped.', |
600 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) | 600 | NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history) |
601 | ); | 601 | ); |
@@ -622,24 +622,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase | |||
622 | 'overwrite' => 'true', | 622 | 'overwrite' => 'true', |
623 | ]; | 623 | ]; |
624 | $files = file2array('netscape_basic.htm'); | 624 | $files = file2array('netscape_basic.htm'); |
625 | $nbLinks = 2; | ||
626 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 625 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); |
627 | $history = $this->history->getHistory(); | 626 | $history = $this->history->getHistory(); |
628 | $this->assertEquals($nbLinks, count($history)); | 627 | $this->assertEquals(1, count($history)); |
629 | foreach ($history as $value) { | 628 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
630 | $this->assertEquals(History::CREATED, $value['event']); | 629 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
631 | $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']); | ||
632 | $this->assertTrue(is_int($value['id'])); | ||
633 | } | ||
634 | 630 | ||
635 | // re-import as private, enable overwriting | 631 | // re-import as private, enable overwriting |
636 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); | 632 | NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history); |
637 | $history = $this->history->getHistory(); | 633 | $history = $this->history->getHistory(); |
638 | $this->assertEquals($nbLinks * 2, count($history)); | 634 | $this->assertEquals(2, count($history)); |
639 | for ($i = 0 ; $i < $nbLinks ; $i++) { | 635 | $this->assertEquals(History::IMPORT, $history[0]['event']); |
640 | $this->assertEquals(History::UPDATED, $history[$i]['event']); | 636 | $this->assertTrue(new DateTime('-5 seconds') < $history[0]['datetime']); |
641 | $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']); | 637 | $this->assertEquals(History::IMPORT, $history[1]['event']); |
642 | $this->assertTrue(is_int($history[$i]['id'])); | 638 | $this->assertTrue(new DateTime('-5 seconds') < $history[1]['datetime']); |
643 | } | ||
644 | } | 639 | } |
645 | } | 640 | } |
diff --git a/tpl/default/includes.html b/tpl/default/includes.html index 0350ef66..80c08333 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html | |||
@@ -5,16 +5,16 @@ | |||
5 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> | 5 | <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> |
6 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> | 6 | <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> |
7 | <link href="img/favicon.png" rel="shortcut icon" type="image/png" /> | 7 | <link href="img/favicon.png" rel="shortcut icon" type="image/png" /> |
8 | <link type="text/css" rel="stylesheet" href="css/pure.min.css" /> | 8 | <link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" /> |
9 | <link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css"> | 9 | <link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}"> |
10 | <link type="text/css" rel="stylesheet" href="css/pure-extras.css"> | 10 | <link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}"> |
11 | <link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" /> | 11 | <link type="text/css" rel="stylesheet" href="css/font-awesome.min.css?v={$version_hash}" /> |
12 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" /> | 12 | <link type="text/css" rel="stylesheet" href="inc/awesomplete.css?v={$version_hash}#" /> |
13 | <link type="text/css" rel="stylesheet" href="css/shaarli.css" /> | 13 | <link type="text/css" rel="stylesheet" href="css/shaarli.css?v={$version_hash}" /> |
14 | {if="is_file('data/user.css')"} | 14 | {if="is_file('data/user.css')"} |
15 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> | 15 | <link type="text/css" rel="stylesheet" href="data/user.css#" /> |
16 | {/if} | 16 | {/if} |
17 | {loop="$plugins_includes.css_files"} | 17 | {loop="$plugins_includes.css_files"} |
18 | <link type="text/css" rel="stylesheet" href="{$value}#"/> | 18 | <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/> |
19 | {/loop} | 19 | {/loop} |
20 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> \ No newline at end of file | 20 | <link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/> \ No newline at end of file |
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js index 1c66ebbd..55656f80 100644 --- a/tpl/default/js/shaarli.js +++ b/tpl/default/js/shaarli.js | |||
@@ -275,8 +275,14 @@ window.onload = function () { | |||
275 | }; | 275 | }; |
276 | function init () { | 276 | function init () { |
277 | function resize () { | 277 | function resize () { |
278 | /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */ | ||
279 | var scrollTop = window.pageYOffset || | ||
280 | (document.documentElement || document.body.parentNode || document.body).scrollTop; | ||
281 | |||
278 | description.style.height = 'auto'; | 282 | description.style.height = 'auto'; |
279 | description.style.height = description.scrollHeight+10+'px'; | 283 | description.style.height = description.scrollHeight+10+'px'; |
284 | |||
285 | window.scrollTo(0, scrollTop); | ||
280 | } | 286 | } |
281 | /* 0-timeout to get the already changed text */ | 287 | /* 0-timeout to get the already changed text */ |
282 | function delayedResize () { | 288 | function delayedResize () { |
diff --git a/tpl/default/page.footer.html b/tpl/default/page.footer.html index 94f771a2..54b16e8a 100644 --- a/tpl/default/page.footer.html +++ b/tpl/default/page.footer.html | |||
@@ -27,6 +27,6 @@ | |||
27 | <script src="{$value}#"></script> | 27 | <script src="{$value}#"></script> |
28 | {/loop} | 28 | {/loop} |
29 | 29 | ||
30 | <script src="js/shaarli.js"></script> | 30 | <script src="js/shaarli.js?v={$version_hash}"></script> |
31 | <script src="inc/awesomplete.js#"></script> | 31 | <script src="inc/awesomplete.js?v={$version_hash}#"></script> |
32 | <script src="inc/awesomplete-multiple-tags.js#"></script> | 32 | <script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script> |
diff --git a/tpl/default/tag.cloud.html b/tpl/default/tag.cloud.html index 96b357a3..68335c70 100644 --- a/tpl/default/tag.cloud.html +++ b/tpl/default/tag.cloud.html | |||
@@ -26,7 +26,7 @@ | |||
26 | <input type="hidden" name="do" value="tagcloud"> | 26 | <input type="hidden" name="do" value="tagcloud"> |
27 | <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}" | 27 | <input type="text" name="searchtags" placeholder="{'Filter by tag'|t}" |
28 | {if="!empty($search_tags)"} | 28 | {if="!empty($search_tags)"} |
29 | value="{$search_tags}" | 29 | value="{$search_tags}" |
30 | {/if} | 30 | {/if} |
31 | autocomplete="off" data-multiple data-autofirst data-minChars="1" | 31 | autocomplete="off" data-multiple data-autofirst data-minChars="1" |
32 | data-list="{loop="$tags"}{$key}, {/loop}" | 32 | data-list="{loop="$tags"}{$key}, {/loop}" |