aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml1
-rw-r--r--CHANGELOG.md39
-rw-r--r--COPYING4
-rw-r--r--application/.htaccess15
-rw-r--r--application/FeedBuilder.php4
-rw-r--r--application/LinkDB.php184
-rw-r--r--application/LinkFilter.php37
-rw-r--r--application/LinkUtils.php13
-rw-r--r--application/NetscapeBookmarkUtils.php139
-rw-r--r--application/Updater.php45
-rw-r--r--application/Utils.php6
-rw-r--r--cache/.htaccess15
-rw-r--r--data/.htaccess15
-rw-r--r--doc/Community-&-Related-software.html8
-rw-r--r--doc/Community-&-Related-software.md8
-rw-r--r--doc/Download-and-Installation.html17
-rw-r--r--doc/Download-and-Installation.md21
-rw-r--r--doc/Release-Shaarli.html39
-rw-r--r--doc/Release-Shaarli.md42
-rw-r--r--doc/Server-configuration.html33
-rw-r--r--doc/Server-configuration.md44
-rw-r--r--doc/Theming.html7
-rw-r--r--doc/Theming.md9
-rw-r--r--docker/.htaccess15
-rw-r--r--docker/development/Dockerfile2
-rw-r--r--docker/development/nginx.conf7
-rw-r--r--docker/production/Dockerfile2
-rw-r--r--docker/production/nginx.conf7
-rw-r--r--docker/production/stable/Dockerfile2
-rw-r--r--docker/production/stable/nginx.conf7
-rw-r--r--inc/shaarli.css9
-rw-r--r--index.php120
-rw-r--r--pagecache/.htaccess15
-rw-r--r--plugins/addlink_toolbar/addlink_toolbar.css4
-rw-r--r--plugins/addlink_toolbar/addlink_toolbar.php10
-rw-r--r--plugins/archiveorg/archiveorg.html2
-rw-r--r--plugins/demo_plugin/demo_plugin.php26
-rw-r--r--plugins/isso/isso.php4
-rw-r--r--plugins/markdown/README.md38
-rw-r--r--plugins/markdown/markdown.meta5
-rw-r--r--plugins/markdown/markdown.php27
-rw-r--r--plugins/playvideos/playvideos.php8
-rw-r--r--plugins/qrcode/qrcode.html2
-rw-r--r--plugins/readityourself/readityourself.html2
-rw-r--r--plugins/wallabag/wallabag.html2
-rw-r--r--shaarli_version.php2
-rw-r--r--tests/.htaccess15
-rw-r--r--tests/FeedBuilderTest.php31
-rw-r--r--tests/LinkDBTest.php39
-rw-r--r--tests/LinkFilterTest.php6
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkExportTest.php6
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkImportTest.php146
-rw-r--r--tests/Updater/UpdaterTest.php98
-rw-r--r--tests/plugins/PluginIssoTest.php25
-rw-r--r--tests/plugins/PluginMarkdownTest.php42
-rw-r--r--tests/utils/ReferenceLinkDB.php61
-rw-r--r--tmp/.htaccess15
-rw-r--r--tpl/daily.html4
-rw-r--r--tpl/editlink.html35
-rw-r--r--tpl/feed.atom.html4
-rw-r--r--tpl/includes.html3
-rw-r--r--tpl/linklist.html15
-rw-r--r--tpl/linklist.paging.html6
-rw-r--r--tpl/loginform.html4
-rw-r--r--tpl/page.header.html6
-rw-r--r--tpl/pluginsadmin.html4
-rw-r--r--tpl/tools.html6
68 files changed, 1278 insertions, 358 deletions
diff --git a/.gitignore b/.gitignore
index 095aaded..9121905d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,7 @@ composer.lock
17vendor/ 17vendor/
18 18
19# Release archives 19# Release archives
20*.tar 20*.tar.gz
21*.zip 21*.zip
22 22
23# Development and test resources 23# Development and test resources
diff --git a/.travis.yml b/.travis.yml
index 9ffb3d00..6ff1b20f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ cache:
4 directories: 4 directories:
5 - $HOME/.composer/cache 5 - $HOME/.composer/cache
6php: 6php:
7 - 7.1
7 - 7.0 8 - 7.0
8 - 5.6 9 - 5.6
9 - 5.5 10 - 5.5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d42d6a75..21d5436c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
5and this project adheres to [Semantic Versioning](http://semver.org/). 5and this project adheres to [Semantic Versioning](http://semver.org/).
6 6
7 7
8## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - UNPUBLISHED 8## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED
9
10### Added
11
12### Changed
13
14### Fixed
15
16
17## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12
18
19> Note: this version will create an automatic backup of your database if anything goes wrong.
20
9### Added 21### Added
10- Add CHANGELOG.md to track the whole project's history 22- Add CHANGELOG.md to track the whole project's history
11- Enable Composer cache for Travis builds 23- Enable Composer cache for Travis builds
@@ -13,20 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
13- Plugins: 25- Plugins:
14 - Add an [Isso](https://posativ.org/isso/) plugin to enable user comments on permalinks 26 - Add an [Isso](https://posativ.org/isso/) plugin to enable user comments on permalinks
15 - Allow defining init functions, e.g. for performing checks and error processing 27 - Allow defining init functions, e.g. for performing checks and error processing
16 28 - Add a Piwik plugin for analytics.
17### Changed 29 - Markdown: add warning notice regarding HTML rendering
18- Cleanup `{loop}` declarations in templates 30- Meta tag to *not* send the referrer to external resources.
31
32### Changed
33- Link ID complete refactoring:
34 - Links now have a numeric ID instead of dates
35 - Short URLs are now created once and can't change over time (previous URL are kept)
36- Templates:
37 - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin`
38 - Cleanup `{loop}` declarations in templates
39 - Tools: hide Firefox Social button when not in HTTPS
40 - Firefox Social: show Shaarli's title when shaaring using Firefox Social
19- Release archives now have the same structure as GitHub-generated archives: 41- Release archives now have the same structure as GitHub-generated archives:
20 - archives contain a `Shaarli` directory, itself containing sources + dependencies 42 - archives contain a `Shaarli` directory, itself containing sources + dependencies
21 - the tarball is now gzipped 43 - the tarball is now gzipped
44- Plugins:
45 - Markdown: Parsedown library is now imported through Composer
22- Minor code cleanup: PHPDoc, spelling, unused variables, etc. 46- Minor code cleanup: PHPDoc, spelling, unused variables, etc.
47- Docker: explicitly set the maximum file upload size to 10 MiB
23 48
24### Fixed 49### Fixed
25- Fix the server `<self>` value in Atom/RSS feeds 50- Fix the server `<self>` value in Atom/RSS feeds
26- Plugins: 51- Plugins:
27 - Tools: only display parameter description when it exists 52 - Tools: only display parameter description when it exists
28 - archive.org: do not propose archival of private notes 53 - archive.org: do not propose archival of private notes
54 - Markdown:
55 - render links properly in code blocks
56 - bug regarding the `nomarkdown` tag
57 - W3C compliance
29- Use absolute URL for hashtags in RSS and ATOM feeds 58- Use absolute URL for hashtags in RSS and ATOM feeds
59- Docker: specify the location of the favicon
60- ATOM feed: remove new line between content tag and data
30 61
31### Security 62### Security
32- Allow whitelisting trusted IPs, else continue banning clients upon login failure 63- Allow whitelisting trusted IPs, else continue banning clients upon login failure
diff --git a/COPYING b/COPYING
index 5a825402..d8241e50 100644
--- a/COPYING
+++ b/COPYING
@@ -72,6 +72,10 @@ Files: plugins/wallabag/wallabag.png
72License: MIT License (http://opensource.org/licenses/MIT) 72License: MIT License (http://opensource.org/licenses/MIT)
73Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag 73Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag
74 74
75Files: plugins/markdown/Parsedown.php
76License: MIT License (http://opensource.org/licenses/MIT)
77Copyright: (C) 2015 Emanuil Rusev - https://github.com/erusev/parsedown
78
75Files: tpl/default/img/sad_star.png 79Files: tpl/default/img/sad_star.png
76License: MIT License (http://opensource.org/licenses/MIT) 80License: MIT License (http://opensource.org/licenses/MIT)
77Copyright: (C) 2015 kalvn - https://github.com/kalvn/Shaarli-Material 81Copyright: (C) 2015 kalvn - https://github.com/kalvn/Shaarli-Material
diff --git a/application/.htaccess b/application/.htaccess
index b584d98c..f601c1ee 100644
--- a/application/.htaccess
+++ b/application/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
index 4036a7cc..b0aa5764 100644
--- a/application/FeedBuilder.php
+++ b/application/FeedBuilder.php
@@ -156,12 +156,12 @@ class FeedBuilder
156 $link['description'] = format_description($link['description'], '', $pageaddr); 156 $link['description'] = format_description($link['description'], '', $pageaddr);
157 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink; 157 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink;
158 158
159 $pubDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 159 $pubDate = $link['created'];
160 $link['pub_iso_date'] = $this->getIsoDate($pubDate); 160 $link['pub_iso_date'] = $this->getIsoDate($pubDate);
161 161
162 // atom:entry elements MUST contain exactly one atom:updated element. 162 // atom:entry elements MUST contain exactly one atom:updated element.
163 if (!empty($link['updated'])) { 163 if (!empty($link['updated'])) {
164 $upDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']); 164 $upDate = $link['updated'];
165 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); 165 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
166 } else { 166 } else {
167 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; 167 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);;
diff --git a/application/LinkDB.php b/application/LinkDB.php
index c8b162b6..1e13286a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -6,15 +6,15 @@
6 * 6 *
7 * Example: 7 * Example:
8 * $myLinks = new LinkDB(); 8 * $myLinks = new LinkDB();
9 * echo $myLinks['20110826_161819']['title']; 9 * echo $myLinks[350]['title'];
10 * foreach ($myLinks as $link) 10 * foreach ($myLinks as $link)
11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; 11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
12 * 12 *
13 * Available keys: 13 * Available keys:
14 * - id: primary key, incremental integer identifier (persistent)
14 * - description: description of the entry 15 * - description: description of the entry
15 * - linkdate: creation date of this entry, format: YYYYMMDD_HHMMSS 16 * - created: creation date of this entry, DateTime object.
16 * (e.g.'20110914_192317') 17 * - updated: last modification date of this entry, DateTime object.
17 * - updated: last modification date of this entry, format: YYYYMMDD_HHMMSS
18 * - private: Is this link private? 0=no, other value=yes 18 * - private: Is this link private? 0=no, other value=yes
19 * - tags: tags attached to this entry (separated by spaces) 19 * - tags: tags attached to this entry (separated by spaces)
20 * - title Title of the link 20 * - title Title of the link
@@ -22,11 +22,25 @@
22 * Can be absolute or relative. 22 * Can be absolute or relative.
23 * Relative URLs are permalinks (e.g.'?m-ukcw') 23 * Relative URLs are permalinks (e.g.'?m-ukcw')
24 * - real_url Absolute processed URL. 24 * - real_url Absolute processed URL.
25 * - shorturl Permalink smallhash
25 * 26 *
26 * Implements 3 interfaces: 27 * Implements 3 interfaces:
27 * - ArrayAccess: behaves like an associative array; 28 * - ArrayAccess: behaves like an associative array;
28 * - Countable: there is a count() method; 29 * - Countable: there is a count() method;
29 * - Iterator: usable in foreach () loops. 30 * - Iterator: usable in foreach () loops.
31 *
32 * ID mechanism:
33 * ArrayAccess is implemented in a way that will allow to access a link
34 * with the unique identifier ID directly with $link[ID].
35 * Note that it's not the real key of the link array attribute.
36 * This mechanism is in place to have persistent link IDs,
37 * even though the internal array is reordered by date.
38 * Example:
39 * - DB: link #1 (2010-01-01) link #2 (2016-01-01)
40 * - Order: #2 #1
41 * - Import links containing: link #3 (2013-01-01)
42 * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
43 * - Real order: #2 #3 #1
30 */ 44 */
31class LinkDB implements Iterator, Countable, ArrayAccess 45class LinkDB implements Iterator, Countable, ArrayAccess
32{ 46{
@@ -47,11 +61,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess
47 // - value: associative array (keys: title, description...) 61 // - value: associative array (keys: title, description...)
48 private $links; 62 private $links;
49 63
50 // List of all recorded URLs (key=url, value=linkdate) 64 // List of all recorded URLs (key=url, value=link offset)
51 // for fast reserve search (url-->linkdate) 65 // for fast reserve search (url-->link offset)
52 private $urls; 66 private $urls;
53 67
54 // List of linkdate keys (for the Iterator interface implementation) 68 /**
69 * @var array List of all links IDS mapped with their array offset.
70 * Map: id->offset.
71 */
72 protected $ids;
73
74 // List of offset keys (for the Iterator interface implementation)
55 private $keys; 75 private $keys;
56 76
57 // Position in the $this->keys array (for the Iterator interface) 77 // Position in the $this->keys array (for the Iterator interface)
@@ -121,14 +141,26 @@ class LinkDB implements Iterator, Countable, ArrayAccess
121 if (!$this->loggedIn) { 141 if (!$this->loggedIn) {
122 die('You are not authorized to add a link.'); 142 die('You are not authorized to add a link.');
123 } 143 }
124 if (empty($value['linkdate']) || empty($value['url'])) { 144 if (!isset($value['id']) || empty($value['url'])) {
125 die('Internal Error: A link should always have a linkdate and URL.'); 145 die('Internal Error: A link should always have an id and URL.');
126 } 146 }
127 if (empty($offset)) { 147 if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
128 die('You must specify a key.'); 148 die('You must specify an integer as a key.');
149 }
150 if (! empty($offset) && $offset !== $value['id']) {
151 die('Array offset and link ID must be equal.');
152 }
153
154 // If the link exists, we reuse the real offset, otherwise new entry
155 $existing = $this->getLinkOffset($offset);
156 if ($existing !== null) {
157 $offset = $existing;
158 } else {
159 $offset = count($this->links);
129 } 160 }
130 $this->links[$offset] = $value; 161 $this->links[$offset] = $value;
131 $this->urls[$value['url']]=$offset; 162 $this->urls[$value['url']] = $offset;
163 $this->ids[$value['id']] = $offset;
132 } 164 }
133 165
134 /** 166 /**
@@ -136,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
136 */ 168 */
137 public function offsetExists($offset) 169 public function offsetExists($offset)
138 { 170 {
139 return array_key_exists($offset, $this->links); 171 return array_key_exists($this->getLinkOffset($offset), $this->links);
140 } 172 }
141 173
142 /** 174 /**
@@ -148,9 +180,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
148 // TODO: raise an exception 180 // TODO: raise an exception
149 die('You are not authorized to delete a link.'); 181 die('You are not authorized to delete a link.');
150 } 182 }
151 $url = $this->links[$offset]['url']; 183 $realOffset = $this->getLinkOffset($offset);
184 $url = $this->links[$realOffset]['url'];
152 unset($this->urls[$url]); 185 unset($this->urls[$url]);
153 unset($this->links[$offset]); 186 unset($this->ids[$realOffset]);
187 unset($this->links[$realOffset]);
154 } 188 }
155 189
156 /** 190 /**
@@ -158,7 +192,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
158 */ 192 */
159 public function offsetGet($offset) 193 public function offsetGet($offset)
160 { 194 {
161 return isset($this->links[$offset]) ? $this->links[$offset] : null; 195 $realOffset = $this->getLinkOffset($offset);
196 return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
162 } 197 }
163 198
164 /** 199 /**
@@ -166,7 +201,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
166 */ 201 */
167 public function current() 202 public function current()
168 { 203 {
169 return $this->links[$this->keys[$this->position]]; 204 return $this[$this->keys[$this->position]];
170 } 205 }
171 206
172 /** 207 /**
@@ -192,8 +227,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
192 */ 227 */
193 public function rewind() 228 public function rewind()
194 { 229 {
195 $this->keys = array_keys($this->links); 230 $this->keys = array_keys($this->ids);
196 rsort($this->keys);
197 $this->position = 0; 231 $this->position = 0;
198 } 232 }
199 233
@@ -219,6 +253,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
219 // Create a dummy database for example 253 // Create a dummy database for example
220 $this->links = array(); 254 $this->links = array();
221 $link = array( 255 $link = array(
256 'id' => 1,
222 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 257 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
223 'url'=>'https://github.com/shaarli/Shaarli/wiki', 258 'url'=>'https://github.com/shaarli/Shaarli/wiki',
224 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 259 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@@ -227,20 +262,23 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
227 262
228You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 263You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
229 'private'=>0, 264 'private'=>0,
230 'linkdate'=> date('Ymd_His'), 265 'created'=> new DateTime(),
231 'tags'=>'opensource software' 266 'tags'=>'opensource software'
232 ); 267 );
233 $this->links[$link['linkdate']] = $link; 268 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
269 $this->links[1] = $link;
234 270
235 $link = array( 271 $link = array(
272 'id' => 0,
236 'title'=>'My secret stuff... - Pastebin.com', 273 'title'=>'My secret stuff... - Pastebin.com',
237 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 274 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
238 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', 275 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
239 'private'=>1, 276 'private'=>1,
240 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 277 'created'=> new DateTime('1 minute ago'),
241 'tags'=>'secretstuff' 278 'tags'=>'secretstuff',
242 ); 279 );
243 $this->links[$link['linkdate']] = $link; 280 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
281 $this->links[0] = $link;
244 282
245 // Write database to disk 283 // Write database to disk
246 $this->write(); 284 $this->write();
@@ -251,7 +289,6 @@ You use the community supported version of the original Shaarli project, by Seba
251 */ 289 */
252 private function read() 290 private function read()
253 { 291 {
254
255 // Public links are hidden and user not logged in => nothing to show 292 // Public links are hidden and user not logged in => nothing to show
256 if ($this->hidePublicLinks && !$this->loggedIn) { 293 if ($this->hidePublicLinks && !$this->loggedIn) {
257 $this->links = array(); 294 $this->links = array();
@@ -269,23 +306,13 @@ You use the community supported version of the original Shaarli project, by Seba
269 strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); 306 strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
270 } 307 }
271 308
272 // If user is not logged in, filter private links. 309 $toremove = array();
273 if (!$this->loggedIn) { 310 foreach ($this->links as $key => &$link) {
274 $toremove = array(); 311 if (! $this->loggedIn && $link['private'] != 0) {
275 foreach ($this->links as $link) { 312 // Transition for not upgraded databases.
276 if ($link['private'] != 0) { 313 $toremove[] = $key;
277 $toremove[] = $link['linkdate']; 314 continue;
278 }
279 }
280 foreach ($toremove as $linkdate) {
281 unset($this->links[$linkdate]);
282 } 315 }
283 }
284
285 $this->urls = array();
286 foreach ($this->links as &$link) {
287 // Keep the list of the mapping URLs-->linkdate up-to-date.
288 $this->urls[$link['url']] = $link['linkdate'];
289 316
290 // Sanitize data fields. 317 // Sanitize data fields.
291 sanitizeLink($link); 318 sanitizeLink($link);
@@ -307,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba
307 else { 334 else {
308 $link['real_url'] = $link['url']; 335 $link['real_url'] = $link['url'];
309 } 336 }
337
338 // To be able to load links before running the update, and prepare the update
339 if (! isset($link['created'])) {
340 $link['id'] = $link['linkdate'];
341 $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
342 if (! empty($link['updated'])) {
343 $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
344 }
345 $link['shorturl'] = smallHash($link['linkdate']);
346 }
347 }
348
349 // If user is not logged in, filter private links.
350 foreach ($toremove as $offset) {
351 unset($this->links[$offset]);
310 } 352 }
353
354 $this->reorder();
311 } 355 }
312 356
313 /** 357 /**
@@ -430,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba
430 $request = ''; 474 $request = '';
431 } 475 }
432 476
433 $linkFilter = new LinkFilter($this->links); 477 $linkFilter = new LinkFilter($this);
434 return $linkFilter->filter($type, $request, $casesensitive, $privateonly); 478 return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
435 } 479 }
436 480
@@ -467,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba
467 public function days() 511 public function days()
468 { 512 {
469 $linkDays = array(); 513 $linkDays = array();
470 foreach (array_keys($this->links) as $day) { 514 foreach ($this->links as $link) {
471 $linkDays[substr($day, 0, 8)] = 0; 515 $linkDays[$link['created']->format('Ymd')] = 0;
472 } 516 }
473 $linkDays = array_keys($linkDays); 517 $linkDays = array_keys($linkDays);
474 sort($linkDays); 518 sort($linkDays);
475 519
476 return $linkDays; 520 return $linkDays;
477 } 521 }
522
523 /**
524 * Reorder links by creation date (newest first).
525 *
526 * Also update the urls and ids mapping arrays.
527 *
528 * @param string $order ASC|DESC
529 */
530 public function reorder($order = 'DESC')
531 {
532 $order = $order === 'ASC' ? -1 : 1;
533 // Reorder array by dates.
534 usort($this->links, function($a, $b) use ($order) {
535 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
536 });
537
538 $this->urls = array();
539 $this->ids = array();
540 foreach ($this->links as $key => $link) {
541 $this->urls[$link['url']] = $key;
542 $this->ids[$link['id']] = $key;
543 }
544 }
545
546 /**
547 * Return the next key for link creation.
548 * E.g. If the last ID is 597, the next will be 598.
549 *
550 * @return int next ID.
551 */
552 public function getNextId()
553 {
554 if (!empty($this->ids)) {
555 return max(array_keys($this->ids)) + 1;
556 }
557 return 0;
558 }
559
560 /**
561 * Returns a link offset in links array from its unique ID.
562 *
563 * @param int $id Persistent ID of a link.
564 *
565 * @return int Real offset in local array, or null if doesn't exist.
566 */
567 protected function getLinkOffset($id)
568 {
569 if (isset($this->ids[$id])) {
570 return $this->ids[$id];
571 }
572 return null;
573 }
478} 574}
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index d4fe28df..daa6d9cc 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -33,12 +33,12 @@ class LinkFilter
33 public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; 33 public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
34 34
35 /** 35 /**
36 * @var array all available links. 36 * @var LinkDB all available links.
37 */ 37 */
38 private $links; 38 private $links;
39 39
40 /** 40 /**
41 * @param array $links initialization. 41 * @param LinkDB $links initialization.
42 */ 42 */
43 public function __construct($links) 43 public function __construct($links)
44 { 44 {
@@ -94,18 +94,16 @@ class LinkFilter
94 private function noFilter($privateonly = false) 94 private function noFilter($privateonly = false)
95 { 95 {
96 if (! $privateonly) { 96 if (! $privateonly) {
97 krsort($this->links);
98 return $this->links; 97 return $this->links;
99 } 98 }
100 99
101 $out = array(); 100 $out = array();
102 foreach ($this->links as $value) { 101 foreach ($this->links as $key => $value) {
103 if ($value['private']) { 102 if ($value['private']) {
104 $out[$value['linkdate']] = $value; 103 $out[$key] = $value;
105 } 104 }
106 } 105 }
107 106
108 krsort($out);
109 return $out; 107 return $out;
110 } 108 }
111 109
@@ -121,10 +119,10 @@ class LinkFilter
121 private function filterSmallHash($smallHash) 119 private function filterSmallHash($smallHash)
122 { 120 {
123 $filtered = array(); 121 $filtered = array();
124 foreach ($this->links as $l) { 122 foreach ($this->links as $key => $l) {
125 if ($smallHash == smallHash($l['linkdate'])) { 123 if ($smallHash == $l['shorturl']) {
126 // Yes, this is ugly and slow 124 // Yes, this is ugly and slow
127 $filtered[$l['linkdate']] = $l; 125 $filtered[$key] = $l;
128 return $filtered; 126 return $filtered;
129 } 127 }
130 } 128 }
@@ -188,7 +186,7 @@ class LinkFilter
188 $keys = array('title', 'description', 'url', 'tags'); 186 $keys = array('title', 'description', 'url', 'tags');
189 187
190 // Iterate over every stored link. 188 // Iterate over every stored link.
191 foreach ($this->links as $link) { 189 foreach ($this->links as $id => $link) {
192 190
193 // ignore non private links when 'privatonly' is on. 191 // ignore non private links when 'privatonly' is on.
194 if (! $link['private'] && $privateonly === true) { 192 if (! $link['private'] && $privateonly === true) {
@@ -222,11 +220,10 @@ class LinkFilter
222 } 220 }
223 221
224 if ($found) { 222 if ($found) {
225 $filtered[$link['linkdate']] = $link; 223 $filtered[$id] = $link;
226 } 224 }
227 } 225 }
228 226
229 krsort($filtered);
230 return $filtered; 227 return $filtered;
231 } 228 }
232 229
@@ -256,7 +253,7 @@ class LinkFilter
256 return $filtered; 253 return $filtered;
257 } 254 }
258 255
259 foreach ($this->links as $link) { 256 foreach ($this->links as $key => $link) {
260 // ignore non private links when 'privatonly' is on. 257 // ignore non private links when 'privatonly' is on.
261 if (! $link['private'] && $privateonly === true) { 258 if (! $link['private'] && $privateonly === true) {
262 continue; 259 continue;
@@ -278,10 +275,9 @@ class LinkFilter
278 } 275 }
279 276
280 if ($found) { 277 if ($found) {
281 $filtered[$link['linkdate']] = $link; 278 $filtered[$key] = $link;
282 } 279 }
283 } 280 }
284 krsort($filtered);
285 return $filtered; 281 return $filtered;
286 } 282 }
287 283
@@ -304,13 +300,14 @@ class LinkFilter
304 } 300 }
305 301
306 $filtered = array(); 302 $filtered = array();
307 foreach ($this->links as $l) { 303 foreach ($this->links as $key => $l) {
308 if (startsWith($l['linkdate'], $day)) { 304 if ($l['created']->format('Ymd') == $day) {
309 $filtered[$l['linkdate']] = $l; 305 $filtered[$key] = $l;
310 } 306 }
311 } 307 }
312 ksort($filtered); 308
313 return $filtered; 309 // sort by date ASC
310 return array_reverse($filtered, true);
314 } 311 }
315 312
316 /** 313 /**
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index 9d9ae3cb..cf58f808 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -169,3 +169,16 @@ function space2nbsp($text)
169function format_description($description, $redirector = '', $indexUrl = '') { 169function format_description($description, $redirector = '', $indexUrl = '') {
170 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl))); 170 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
171} 171}
172
173/**
174 * Generate a small hash for a link.
175 *
176 * @param DateTime $date Link creation date.
177 * @param int $id Link ID.
178 *
179 * @return string the small hash generated from link data.
180 */
181function link_small_hash($date, $id)
182{
183 return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
184}
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index dd21f05b..f21ee359 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -38,7 +38,7 @@ class NetscapeBookmarkUtils
38 if ($link['private'] == 0 && $selection == 'private') { 38 if ($link['private'] == 0 && $selection == 'private') {
39 continue; 39 continue;
40 } 40 }
41 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 41 $date = $link['created'];
42 $link['timestamp'] = $date->getTimestamp(); 42 $link['timestamp'] = $date->getTimestamp();
43 $link['taglist'] = str_replace(' ', ',', $link['tags']); 43 $link['taglist'] = str_replace(' ', ',', $link['tags']);
44 44
@@ -147,6 +147,143 @@ class NetscapeBookmarkUtils
147 'url' => $bkm['uri'], 147 'url' => $bkm['uri'],
148 'description' => $bkm['note'], 148 'description' => $bkm['note'],
149 'private' => $private, 149 'private' => $private,
150 'tags' => $bkm['tags']
151 );
152
153 $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
154
155 if ($existingLink !== false) {
156 if ($overwrite === false) {
157 // Do not overwrite an existing link
158 $skipCount++;
159 continue;
160 }
161
162 // Overwrite an existing link, keep its date
163 $newLink['id'] = $existingLink['id'];
164 $newLink['created'] = $existingLink['created'];
165 $newLink['updated'] = new DateTime();
166 $linkDb[$existingLink['id']] = $newLink;
167 $importCount++;
168 $overwriteCount++;
169 continue;
170 }
171
172 // Add a new link - @ used for UNIX timestamps
173 $newLinkDate = new DateTime('@'.strval($bkm['time']));
174 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
175 $newLink['created'] = $newLinkDate;
176 $newLink['id'] = $linkDb->getNextId();
177 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
178 $linkDb[$newLink['id']] = $newLink;
179 $importCount++;
180 }
181
182 $linkDb->save($pagecache);
183 return self::importStatus(
184 $filename,
185 $filesize,
186 $importCount,
187 $overwriteCount,
188 $skipCount
189 );
190 }
191
192 /**
193 * Generates an import status summary
194 *
195 * @param string $filename name of the file to import
196 * @param int $filesize size of the file to import
197 * @param int $importCount how many links were imported
198 * @param int $overwriteCount how many links were overwritten
199 * @param int $skipCount how many links were skipped
200 *
201 * @return string Summary of the bookmark import status
202 */
203 private static function importStatus(
204 $filename,
205 $filesize,
206 $importCount=0,
207 $overwriteCount=0,
208 $skipCount=0
209 )
210 {
211 $status = 'File '.$filename.' ('.$filesize.' bytes) ';
212 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
213 $status .= 'has an unknown file format. Nothing was imported.';
214 } else {
215 $status .= 'was successfully processed: '.$importCount.' links imported, ';
216 $status .= $overwriteCount.' links overwritten, ';
217 $status .= $skipCount.' links skipped.';
218 }
219 return $status;
220 }
221
222 /**
223 * Imports Web bookmarks from an uploaded Netscape bookmark dump
224 *
225 * @param array $post Server $_POST parameters
226 * @param array $files Server $_FILES parameters
227 * @param LinkDB $linkDb Loaded LinkDB instance
228 * @param string $pagecache Page cache
229 *
230 * @return string Summary of the bookmark import status
231 */
232 public static function import($post, $files, $linkDb, $pagecache)
233 {
234 $filename = $files['filetoupload']['name'];
235 $filesize = $files['filetoupload']['size'];
236 $data = file_get_contents($files['filetoupload']['tmp_name']);
237
238 if (strpos($data, '<!DOCTYPE NETSCAPE-Bookmark-file-1>') === false) {
239 return self::importStatus($filename, $filesize);
240 }
241
242 // Overwrite existing links?
243 $overwrite = ! empty($post['overwrite']);
244
245 // Add tags to all imported links?
246 if (empty($post['default_tags'])) {
247 $defaultTags = array();
248 } else {
249 $defaultTags = preg_split(
250 '/[\s,]+/',
251 escape($post['default_tags'])
252 );
253 }
254
255 // links are imported as public by default
256 $defaultPrivacy = 0;
257
258 $parser = new NetscapeBookmarkParser(
259 true, // nested tag support
260 $defaultTags, // additional user-specified tags
261 strval(1 - $defaultPrivacy) // defaultPub = 1 - defaultPrivacy
262 );
263 $bookmarks = $parser->parseString($data);
264
265 $importCount = 0;
266 $overwriteCount = 0;
267 $skipCount = 0;
268
269 foreach ($bookmarks as $bkm) {
270 $private = $defaultPrivacy;
271 if (empty($post['privacy']) || $post['privacy'] == 'default') {
272 // use value from the imported file
273 $private = $bkm['pub'] == '1' ? 0 : 1;
274 } else if ($post['privacy'] == 'private') {
275 // all imported links are private
276 $private = 1;
277 } else if ($post['privacy'] == 'public') {
278 // all imported links are public
279 $private = 0;
280 }
281
282 $newLink = array(
283 'title' => $bkm['title'],
284 'url' => $bkm['uri'],
285 'description' => $bkm['note'],
286 'private' => $private,
150 'linkdate'=> '', 287 'linkdate'=> '',
151 'tags' => $bkm['tags'] 288 'tags' => $bkm['tags']
152 ); 289 );
diff --git a/application/Updater.php b/application/Updater.php
index 36eddd4f..f0d02814 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -138,10 +138,10 @@ class Updater
138 public function updateMethodRenameDashTags() 138 public function updateMethodRenameDashTags()
139 { 139 {
140 $linklist = $this->linkDB->filterSearch(); 140 $linklist = $this->linkDB->filterSearch();
141 foreach ($linklist as $link) { 141 foreach ($linklist as $key => $link) {
142 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); 142 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); 143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
144 $this->linkDB[$link['linkdate']] = $link; 144 $this->linkDB[$key] = $link;
145 } 145 }
146 $this->linkDB->save($this->conf->get('resource.page_cache')); 146 $this->linkDB->save($this->conf->get('resource.page_cache'));
147 return true; 147 return true;
@@ -215,6 +215,47 @@ class Updater
215 } 215 }
216 return true; 216 return true;
217 } 217 }
218
219 /**
220 * Update the database to use the new ID system, which replaces linkdate primary keys.
221 * Also, creation and update dates are now DateTime objects (done by LinkDB).
222 *
223 * Since this update is very sensitve (changing the whole database), the datastore will be
224 * automatically backed up into the file datastore.<datetime>.php.
225 *
226 * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
227 * which will be saved by this method.
228 *
229 * @return bool true if the update is successful, false otherwise.
230 */
231 public function updateMethodDatastoreIds()
232 {
233 // up to date database
234 if (isset($this->linkDB[0])) {
235 return true;
236 }
237
238 $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php';
239 copy($this->conf->get('resource.datastore'), $save);
240
241 $links = array();
242 foreach ($this->linkDB as $offset => $value) {
243 $links[] = $value;
244 unset($this->linkDB[$offset]);
245 }
246 $links = array_reverse($links);
247 $cpt = 0;
248 foreach ($links as $l) {
249 unset($l['linkdate']);
250 $l['id'] = $cpt;
251 $this->linkDB[$cpt++] = $l;
252 }
253
254 $this->linkDB->save($this->conf->get('resource.page_cache'));
255 $this->linkDB->reorder();
256
257 return true;
258 }
218} 259}
219 260
220/** 261/**
diff --git a/application/Utils.php b/application/Utils.php
index 0166ee2a..0a5b476e 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message)
31 * - are NOT cryptographically secure (they CAN be forged) 31 * - are NOT cryptographically secure (they CAN be forged)
32 * 32 *
33 * In Shaarli, they are used as a tinyurl-like link to individual entries, 33 * In Shaarli, they are used as a tinyurl-like link to individual entries,
34 * e.g. smallHash('20111006_131924') --> yZH23w 34 * built once with the combination of the date and item ID.
35 * e.g. smallHash('20111006_131924' . 142) --> eaWxtQ
36 *
37 * @warning before v0.8.1, smallhashes were built only with the date,
38 * and their value has been preserved.
35 * 39 *
36 * @param string $text Create a hash from this text. 40 * @param string $text Create a hash from this text.
37 * 41 *
diff --git a/cache/.htaccess b/cache/.htaccess
index b584d98c..f601c1ee 100644
--- a/cache/.htaccess
+++ b/cache/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/data/.htaccess b/data/.htaccess
index b584d98c..f601c1ee 100644
--- a/data/.htaccess
+++ b/data/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/doc/Community-&-Related-software.html b/doc/Community-&-Related-software.html
index accbacdc..cbc73d54 100644
--- a/doc/Community-&-Related-software.html
+++ b/doc/Community-&-Related-software.html
@@ -81,10 +81,11 @@
81<ul> 81<ul>
82<li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li> 82<li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li>
83<li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li> 83<li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li>
84<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li> 84<li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li>
85<li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li> 85<li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li>
86<li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li> 86<li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li>
87<li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li> 87<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li>
88<li><a href="https://github.com/ArthurHoaro/shaarli2twitter">shaarli2twitter</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a> - Automatically tweet your shared links from Shaarli<a href=".html"></a></li>
88</ul> 89</ul>
89<h3 id="themes">Themes</h3> 90<h3 id="themes">Themes</h3>
90<p>See <a href="Theming.html">Theming</a> for the list of community-contributed themes, and an installation guide.</p> 91<p>See <a href="Theming.html">Theming</a> for the list of community-contributed themes, and an installation guide.</p>
@@ -95,7 +96,7 @@
95<li><a href="https://github.com/DMeloni/shaarlo">Shaarlo</a> - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: <a href="http://shaarli.fr/">shaarli.fr</a>)<a href=".html"></a></li> 96<li><a href="https://github.com/DMeloni/shaarlo">Shaarlo</a> - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: <a href="http://shaarli.fr/">shaarli.fr</a>)<a href=".html"></a></li>
96<li><a href="https://github.com/BoboTiG/shaarlimages">Shaarlimages</a> - An image-oriented aggregator for Shaarlis<a href=".html"></a></li> 97<li><a href="https://github.com/BoboTiG/shaarlimages">Shaarlimages</a> - An image-oriented aggregator for Shaarlis<a href=".html"></a></li>
97<li><a href="https://github.com/mknexen/shaarli-api">mknexen/shaarli-api</a> - A REST API for Shaarli<a href=".html"></a></li> 98<li><a href="https://github.com/mknexen/shaarli-api">mknexen/shaarli-api</a> - A REST API for Shaarli<a href=".html"></a></li>
98<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. An <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">another version</a>, can be used for others shaarli (but use most ressources).<a href=".html"></a></li> 99<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">Another version</a>, can be used for other shaarli instances (but is more resource consuming).<a href=".html"></a></li>
99</ul> 100</ul>
100<h3 id="mobile-apps">Mobile Apps</h3> 101<h3 id="mobile-apps">Mobile Apps</h3>
101<ul> 102<ul>
@@ -107,6 +108,7 @@
107<ul> 108<ul>
108<li><a href="https://github.com/jcsaaddupuy/tt-rss-shaarli">tt-rss-shaarli</a> - <a href="http://tt-rss.org/">TinyTiny RSS</a> plugin that adds support for sharing articles with Shaarli<a href=".html"></a></li> 109<li><a href="https://github.com/jcsaaddupuy/tt-rss-shaarli">tt-rss-shaarli</a> - <a href="http://tt-rss.org/">TinyTiny RSS</a> plugin that adds support for sharing articles with Shaarli<a href=".html"></a></li>
109<li><a href="https://github.com/ahmet2mir/octopress-shaarli">octopress-shaarli</a> - Octopress plugin to retrieve Shaarli links on the sidebar<a href=".html"></a></li> 110<li><a href="https://github.com/ahmet2mir/octopress-shaarli">octopress-shaarli</a> - Octopress plugin to retrieve Shaarli links on the sidebar<a href=".html"></a></li>
111<li><a href="https://github.com/q2apro/scuttle-to-shaarli">Scuttle to Shaarli</a> - Import bookmarks from Scuttle<a href=".html"></a></li>
110</ul> 112</ul>
111<h2 id="alternatives-to-shaarli">Alternatives to Shaarli</h2> 113<h2 id="alternatives-to-shaarli">Alternatives to Shaarli</h2>
112<ul> 114<ul>
diff --git a/doc/Community-&-Related-software.md b/doc/Community-&-Related-software.md
index 3945d005..291bf643 100644
--- a/doc/Community-&-Related-software.md
+++ b/doc/Community-&-Related-software.md
@@ -20,10 +20,11 @@ _TODO: contact repos owners to see if they'd like to standardize their work with
20 20
21 * [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html) 21 * [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html)
22 * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) 22 * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html)
23 * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) 23 * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html)
24 * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html) 24 * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html)
25 * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) 25 * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html)
26 * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html) 26 * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
27 * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html)
27 28
28 29
29### Themes 30### Themes
@@ -35,7 +36,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
35- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html) 36- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html)
36- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html) 37- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html)
37- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html) 38- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html)
38- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. An [another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for others shaarli (but use most ressources).[](.html) 39- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).[](.html)
39 40
40### Mobile Apps 41### Mobile Apps
41- [Shaarli💫](http://app.mro.name/Shaarli💫) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html) 42- [Shaarli💫](http://app.mro.name/Shaarli💫) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html)
@@ -45,6 +46,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
45## Integration with other platforms 46## Integration with other platforms
46- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html) 47- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html)
47- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html) 48- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html)
49- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle[](.html)
48 50
49## Alternatives to Shaarli 51## Alternatives to Shaarli
50- [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html) 52- [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html)
diff --git a/doc/Download-and-Installation.html b/doc/Download-and-Installation.html
index 17c7b69e..b9cac360 100644
--- a/doc/Download-and-Installation.html
+++ b/doc/Download-and-Installation.html
@@ -105,13 +105,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
105<p>Several releases are available:</p> 105<p>Several releases are available:</p>
106<hr /> 106<hr />
107<h2 id="latest-release-recommended">Latest release (recommended)</h2> 107<h2 id="latest-release-recommended">Latest release (recommended)</h2>
108<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
109<p>The current latest released version is <code>v0.7.0</code>.</p>
110<h3 id="download-as-an-archive">Download as an archive</h3> 108<h3 id="download-as-an-archive">Download as an archive</h3>
111<p>As a .zip archive:</p> 109<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
112<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/archive/v0.7.0.zip 110<p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p>
113$ <span class="fu">unzip</span> Shaarli-v0.7.0.zip 111<p>The current latest released version is <code>v0.8.0</code></p>
114$ <span class="fu">mv</span> Shaarli-v0.7.0 /path/to/shaarli/</code></pre></div> 112<p>Or in command lines:</p>
113<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
114$ <span class="fu">unzip</span> shaarli-v0.8.0-full.zip
115$ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div>
115<table style="width:46%;"> 116<table style="width:46%;">
116<colgroup> 117<colgroup>
117<col style="width: 8%" /> 118<col style="width: 8%" />
@@ -126,6 +127,10 @@ $ <span class="fu">mv</span> Shaarli-v0.7.0 /path/to/shaarli/</code></pre></div>
126<tbody> 127<tbody>
127</tbody> 128</tbody>
128</table> 129</table>
130<h3 id="using-git">Using git</h3>
131<pre><code>mkdir -p /path/to/shaarli &amp;&amp; cd /path/to/shaarli/
132git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
133composer update --no-dev</code></pre>
129<hr /> 134<hr />
130<h2 id="stable-version">Stable version</h2> 135<h2 id="stable-version">Stable version</h2>
131<p>The stable version has been experienced by Shaarli users, and will receive security updates.</p> 136<p>The stable version has been experienced by Shaarli users, and will receive security updates.</p>
diff --git a/doc/Download-and-Installation.md b/doc/Download-and-Installation.md
index 77af25eb..32df8984 100644
--- a/doc/Download-and-Installation.md
+++ b/doc/Download-and-Installation.md
@@ -8,26 +8,31 @@ Several releases are available:
8-------------------------------------------------------- 8--------------------------------------------------------
9 9
10## Latest release (recommended) 10## Latest release (recommended)
11 11### Download as an archive
12Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) 12Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html)
13 13
14The current latest released version is `v0.7.0`. 14**Download our *shaarli-full* archive** to include dependencies.
15 15
16### Download as an archive 16The current latest released version is `v0.8.0`
17 17
18As a .zip archive: 18Or in command lines:
19 19
20```bash 20```bash
21$ wget https://github.com/shaarli/Shaarli/archive/v0.7.0.zip 21$ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
22$ unzip Shaarli-v0.7.0.zip 22$ unzip shaarli-v0.8.0-full.zip
23$ mv Shaarli-v0.7.0 /path/to/shaarli/ 23$ mv Shaarli /path/to/shaarli/
24``` 24```
25 25
26
27| ! |In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|[](.html) 26| ! |In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|[](.html)
28|-----|--------------------------| 27|-----|--------------------------|
29 28
29### Using git
30 30
31```
32mkdir -p /path/to/shaarli && cd /path/to/shaarli/
33git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
34composer update --no-dev
35```
31 36
32-------------------------------------------------------- 37--------------------------------------------------------
33 38
diff --git a/doc/Release-Shaarli.html b/doc/Release-Shaarli.html
index cdefd3d6..0d9fa3e1 100644
--- a/doc/Release-Shaarli.html
+++ b/doc/Release-Shaarli.html
@@ -115,9 +115,35 @@ releases</a>.</p>
115<li><code>origin</code> pointing to your GitHub fork</li> 115<li><code>origin</code> pointing to your GitHub fork</li>
116<li><code>upstream</code> pointing to the main Shaarli repository</li> 116<li><code>upstream</code> pointing to the main Shaarli repository</li>
117</ul></li> 117</ul></li>
118<li>maintainer permissions on the main Shaarli repository (to push the signed tag)</li> 118<li>maintainer permissions on the main Shaarli repository, to:
119<ul>
120<li>push the signed tag</li>
121<li>create a new release</li>
122</ul></li>
119<li><a href="https://getcomposer.org/">Composer</a> and <a href="http://pandoc.org/">Pandoc</a> need to be installed<a href=".html"></a></li> 123<li><a href="https://getcomposer.org/">Composer</a> and <a href="http://pandoc.org/">Pandoc</a> need to be installed<a href=".html"></a></li>
120</ul> 124</ul>
125<h2 id="github-release-draft-and-changelog.md">GitHub release draft and <code>CHANGELOG.md</code></h2>
126<p>See <a href="http://keepachangelog.com/en/0.3.0/" class="uri">http://keepachangelog.com/en/0.3.0/</a> for changelog formatting.</p>
127<h3 id="github-release-draft">GitHub release draft</h3>
128<p>GitHub allows drafting the release note for the upcoming release, from the <a href="https://github.com/shaarli/Shaarli/releases">Releases</a> page. This way, the release note can be drafted while contributions are merged to <code>master</code>.<a href=".html"></a></p>
129<h3 id="changelog.md"><code>CHANGELOG.md</code></h3>
130<p>This file should contain the same information as the release note draft for the upcoming version.</p>
131<p>Update it to:</p>
132<ul>
133<li>add new entries (additions, fixes, etc.)</li>
134<li>mark the current version as released by setting its date and link</li>
135<li>add a new section for the future unreleased version</li>
136</ul>
137<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
138
139$ <span class="fu">nano</span> CHANGELOG.md
140
141[<span class="ex">...</span>][](.html)
142<span class="co">## vA.B.C - UNRELEASED</span>
143<span class="ex">TBA</span>
144
145<span class="co">## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)</span>
146[<span class="ex">...</span>][](.html)</code></pre></div>
121<h2 id="increment-the-version-code-create-and-push-a-signed-tag">Increment the version code, create and push a signed tag</h2> 147<h2 id="increment-the-version-code-create-and-push-a-signed-tag">Increment the version code, create and push a signed tag</h2>
122<h3 id="bump-shaarlis-version">Bump Shaarli's version</h3> 148<h3 id="bump-shaarlis-version">Bump Shaarli's version</h3>
123<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli 149<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
@@ -165,7 +191,16 @@ $ <span class="fu">git</span> show-ref tags/v0.5.0
165$ <span class="fu">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 191$ <span class="fu">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
166<span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F 192<span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
167<span class="ex">gpg</span>: Good signature from <span class="st">&quot;VirtualTam &lt;virtualtam@flibidi.net&gt;&quot;</span> [ultimate][](.html)</code></pre></div> 193<span class="ex">gpg</span>: Good signature from <span class="st">&quot;VirtualTam &lt;virtualtam@flibidi.net&gt;&quot;</span> [ultimate][](.html)</code></pre></div>
168<h2 id="generate-and-upload-all-in-one-release-archives">Generate and upload all-in-one release archives</h2> 194<h2 id="publish-the-github-release">Publish the GitHub release</h2>
195<h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3>
196<p>From the previously drafted release:</p>
197<ul>
198<li>edit the release notes (if needed)</li>
199<li>specify the appropriate Git tag</li>
200<li>publish the release</li>
201<li>profit!</li>
202</ul>
203<h3 id="generate-and-upload-all-in-one-release-archives">Generate and upload all-in-one release archives</h3>
169<p>Users with a shared hosting may have:</p> 204<p>Users with a shared hosting may have:</p>
170<ul> 205<ul>
171<li>no SSH access</li> 206<li>no SSH access</li>
diff --git a/doc/Release-Shaarli.md b/doc/Release-Shaarli.md
index 5cbcd79a..556a96ee 100644
--- a/doc/Release-Shaarli.md
+++ b/doc/Release-Shaarli.md
@@ -10,9 +10,39 @@ This guide assumes that you have:
10- a local clone of your Shaarli fork, with the following remotes: 10- a local clone of your Shaarli fork, with the following remotes:
11 - `origin` pointing to your GitHub fork 11 - `origin` pointing to your GitHub fork
12 - `upstream` pointing to the main Shaarli repository 12 - `upstream` pointing to the main Shaarli repository
13- maintainer permissions on the main Shaarli repository (to push the signed tag) 13- maintainer permissions on the main Shaarli repository, to:
14 - push the signed tag
15 - create a new release
14- [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html) 16- [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html)
15 17
18## GitHub release draft and `CHANGELOG.md`
19See http://keepachangelog.com/en/0.3.0/ for changelog formatting.
20
21### GitHub release draft
22GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`.[](.html)
23
24### `CHANGELOG.md`
25This file should contain the same information as the release note draft for the upcoming version.
26
27Update it to:
28- add new entries (additions, fixes, etc.)
29- mark the current version as released by setting its date and link
30- add a new section for the future unreleased version
31
32```bash
33$ cd /path/to/shaarli
34
35$ nano CHANGELOG.md
36
37[...][](.html)
38## vA.B.C - UNRELEASED
39TBA
40
41## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)
42[...][](.html)
43```
44
45
16## Increment the version code, create and push a signed tag 46## Increment the version code, create and push a signed tag
17### Bump Shaarli's version 47### Bump Shaarli's version
18```bash 48```bash
@@ -72,7 +102,15 @@ gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
72gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html) 102gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html)
73``` 103```
74 104
75## Generate and upload all-in-one release archives 105## Publish the GitHub release
106### Create a GitHub release from a Git tag
107From the previously drafted release:
108- edit the release notes (if needed)
109- specify the appropriate Git tag
110- publish the release
111- profit!
112
113### Generate and upload all-in-one release archives
76Users with a shared hosting may have: 114Users with a shared hosting may have:
77- no SSH access 115- no SSH access
78- no possibility to install PHP packages or server extensions 116- no possibility to install PHP packages or server extensions
diff --git a/doc/Server-configuration.html b/doc/Server-configuration.html
index 068900b8..2f1c25b5 100644
--- a/doc/Server-configuration.html
+++ b/doc/Server-configuration.html
@@ -193,6 +193,9 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
193 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span> 193 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span>
194 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span> 194 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
195<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div> 195<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
196<h3 id="htaccess">.htaccess</h3>
197<p>Shaarli use <code>.htaccess</code> Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive <code>AllowOverride All</code> in your virtual host configuration for them to work.</p>
198<p><strong>Warning</strong>: If you use Apache 2.2 or lower, you need <a href="https://httpd.apache.org/docs/current/mod/mod_version.html">mod_version</a> to be installed and enabled.<a href=".html"></a></p>
196<h2 id="lighthttpd">LightHttpd</h2> 199<h2 id="lighthttpd">LightHttpd</h2>
197<h2 id="nginx">Nginx</h2> 200<h2 id="nginx">Nginx</h2>
198<h3 id="foreword">Foreword</h3> 201<h3 id="foreword">Foreword</h3>
@@ -233,7 +236,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
233<li>files may be located in a user's home directory</li> 236<li>files may be located in a user's home directory</li>
234<li>in this case, make sure both Nginx and PHP-FPM are running as the local user/group!</li> 237<li>in this case, make sure both Nginx and PHP-FPM are running as the local user/group!</li>
235</ul> 238</ul>
236<p>For all following examples, a development configuration will be used:</p> 239<p>For all following configuration examples, this user/group pair will be used:</p>
237<ul> 240<ul>
238<li><code>user:group = john:users</code>,</li> 241<li><code>user:group = john:users</code>,</li>
239</ul> 242</ul>
@@ -251,6 +254,24 @@ user john users;
251http { 254http {
252 [...][](.html) 255 [...][](.html)
253}</code></pre> 256}</code></pre>
257<h3 id="optional-increase-the-maximum-file-upload-size">(Optional) Increase the maximum file upload size</h3>
258<p>Some bookmark dumps generated by web browsers can be <em>huge</em> due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.</p>
259<p>To increase upload size, you will need to modify both nginx and PHP configuration:</p>
260<pre class="nginx"><code># /etc/nginx/nginx.conf
261
262http {
263 [...][](.html)
264
265 client_max_body_size 10m;
266
267 [...][](.html)
268}</code></pre>
269<div class="sourceCode"><pre class="sourceCode ini"><code class="sourceCode ini"><span class="co"># /etc/php5/fpm/php.ini</span>
270
271<span class="kw">[...][]</span><span class="dt">(.html)</span>
272<span class="dt">post_max_size </span><span class="ot">=</span><span class="st"> 10M</span>
273<span class="kw">[...][]</span><span class="dt">(.html)</span>
274<span class="dt">upload_max_filesize </span><span class="ot">=</span><span class="st"> 10M</span></code></pre></div>
254<h3 id="minimal-1">Minimal</h3> 275<h3 id="minimal-1">Minimal</h3>
255<p><em>WARNING: Use for development only!</em></p> 276<p><em>WARNING: Use for development only!</em></p>
256<pre class="nginx"><code>user john users; 277<pre class="nginx"><code>user john users;
@@ -350,6 +371,11 @@ http {
350 error_log /var/log/nginx/shaarli.error.log; 371 error_log /var/log/nginx/shaarli.error.log;
351 } 372 }
352 373
374 location = /shaarli/favicon.ico {
375 # serve the Shaarli favicon from its custom location
376 alias /var/www/shaarli/images/favicon.ico;
377 }
378
353 include deny.conf; 379 include deny.conf;
354 include static_assets.conf; 380 include static_assets.conf;
355 include php.conf; 381 include php.conf;
@@ -403,6 +429,11 @@ http {
403 error_log /var/log/nginx/shaarli.error.log; 429 error_log /var/log/nginx/shaarli.error.log;
404 } 430 }
405 431
432 location = /shaarli/favicon.ico {
433 # serve the Shaarli favicon from its custom location
434 alias /var/www/shaarli/images/favicon.ico;
435 }
436
406 include deny.conf; 437 include deny.conf;
407 include static_assets.conf; 438 include static_assets.conf;
408 include php.conf; 439 include php.conf;
diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md
index 1ab57a0a..df10feb2 100644
--- a/doc/Server-configuration.md
+++ b/doc/Server-configuration.md
@@ -102,6 +102,12 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache)
102</VirtualHost> 102</VirtualHost>
103``` 103```
104 104
105### .htaccess
106
107Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work.
108
109**Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html)
110
105## LightHttpd 111## LightHttpd
106 112
107## Nginx 113## Nginx
@@ -136,7 +142,7 @@ On a development server:
136- files may be located in a user's home directory 142- files may be located in a user's home directory
137- in this case, make sure both Nginx and PHP-FPM are running as the local user/group! 143- in this case, make sure both Nginx and PHP-FPM are running as the local user/group!
138 144
139For all following examples, a development configuration will be used: 145For all following configuration examples, this user/group pair will be used:
140- `user:group = john:users`, 146- `user:group = john:users`,
141 147
142which corresponds to the following service configuration: 148which corresponds to the following service configuration:
@@ -160,6 +166,32 @@ http {
160} 166}
161``` 167```
162 168
169### (Optional) Increase the maximum file upload size
170Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
171
172To increase upload size, you will need to modify both nginx and PHP configuration:
173
174```nginx
175# /etc/nginx/nginx.conf
176
177http {
178 [...][](.html)
179
180 client_max_body_size 10m;
181
182 [...][](.html)
183}
184```
185
186```ini
187# /etc/php5/fpm/php.ini
188
189[...][](.html)
190post_max_size = 10M
191[...][](.html)
192upload_max_filesize = 10M
193```
194
163### Minimal 195### Minimal
164_WARNING: Use for development only!_ 196_WARNING: Use for development only!_
165 197
@@ -271,6 +303,11 @@ http {
271 error_log /var/log/nginx/shaarli.error.log; 303 error_log /var/log/nginx/shaarli.error.log;
272 } 304 }
273 305
306 location = /shaarli/favicon.ico {
307 # serve the Shaarli favicon from its custom location
308 alias /var/www/shaarli/images/favicon.ico;
309 }
310
274 include deny.conf; 311 include deny.conf;
275 include static_assets.conf; 312 include static_assets.conf;
276 include php.conf; 313 include php.conf;
@@ -328,6 +365,11 @@ http {
328 error_log /var/log/nginx/shaarli.error.log; 365 error_log /var/log/nginx/shaarli.error.log;
329 } 366 }
330 367
368 location = /shaarli/favicon.ico {
369 # serve the Shaarli favicon from its custom location
370 alias /var/www/shaarli/images/favicon.ico;
371 }
372
331 include deny.conf; 373 include deny.conf;
332 include static_assets.conf; 374 include static_assets.conf;
333 include php.conf; 375 include php.conf;
diff --git a/doc/Theming.html b/doc/Theming.html
index 13e6acf0..7cbf7aef 100644
--- a/doc/Theming.html
+++ b/doc/Theming.html
@@ -119,19 +119,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
119<ul> 119<ul>
120<li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li> 120<li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li>
121</ul></li> 121</ul></li>
122<li><p>Edit <code>data/config.php</code> to have Shaarli use this template, e.g.</p> 122<li><p>Edit <code>data/config.json.php</code> to have Shaarli use this template, in <code>&quot;resource&quot;</code> e.g.</p>
123<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span> = <span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;](</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;</span>.html<span class="ot">)</span></code></pre></div></li> 123<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="er">&quot;raintpl_tpl&quot;:</span> <span class="er">&quot;tpl\/my-template\/&quot;,</span></code></pre></div></li>
124</ul> 124</ul>
125<h2 id="community-themes-templates">Community themes &amp; templates</h2> 125<h2 id="community-themes-templates">Community themes &amp; templates</h2>
126<ul> 126<ul>
127<li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li> 127<li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li>
128<li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li> 128<li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li>
129<li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme.<a href=".html"></a></li>
129<li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li> 130<li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li>
130<li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li> 131<li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li>
131<li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li> 132<li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li>
133<li><a href="https://github.com/ManufacturaInd/shaarli-2004licious-theme">ManufacturaInd/shaarli-2004licious-theme</a> - A template/theme as a humble homage to the early looks of the del.icio.us site.<a href=".html"></a></li>
132<li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li> 134<li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li>
133<li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for SHaarli.<a href=".html"></a></li> 135<li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for SHaarli.<a href=".html"></a></li>
134<li><a href="https://github.com/Vinm/Blue-theme-for-Shaarli">Vinm/Blue-theme-for Shaarli</a> - A template/theme for Shaarli (<a href="https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2">unmaintained</a>, compatibility unknown)<a href=".html"></a></li>
135<li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li> 136<li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li>
136</ul> 137</ul>
137<h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3> 138<h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3>
diff --git a/doc/Theming.md b/doc/Theming.md
index 7fb8d927..a21899c2 100644
--- a/doc/Theming.md
+++ b/doc/Theming.md
@@ -16,20 +16,21 @@ _WARNING - This feature is currently being worked on and will be improved in the
16- Find it's git clone URL or download the zip archive for the template. 16- Find it's git clone URL or download the zip archive for the template.
17- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive. 17- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive.
18 - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files. 18 - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files.
19- Edit `data/config.php` to have Shaarli use this template, e.g. 19- Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g.
20```php 20```json
21$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html) 21"raintpl_tpl": "tpl\/my-template\/",
22``` 22```
23 23
24## Community themes & templates 24## Community themes & templates
25- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) 25- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html)
26- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) 26- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html)
27- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html)
27- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) 28- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html)
28- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) 29- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html)
29- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html) 30- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html)
31- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site.[](.html)
30- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) 32- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html)
31- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html) 33- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html)
32- [Vinm/Blue-theme-for Shaarli](https://github.com/Vinm/Blue-theme-for-Shaarli) - A template/theme for Shaarli ([unmaintained](https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2), compatibility unknown)[](.html)
33- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) 34- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html)
34 35
35### Example installation: AlbinoMouse template 36### Example installation: AlbinoMouse template
diff --git a/docker/.htaccess b/docker/.htaccess
index b584d98c..f601c1ee 100644
--- a/docker/.htaccess
+++ b/docker/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile
index 0c19b085..d9ef8da7 100644
--- a/docker/development/Dockerfile
+++ b/docker/development/Dockerfile
@@ -15,6 +15,8 @@ RUN apt-get update \
15 nano \ 15 nano \
16 && apt-get clean 16 && apt-get clean
17 17
18RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
19RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
18COPY nginx.conf /etc/nginx/nginx.conf 20COPY nginx.conf /etc/nginx/nginx.conf
19COPY supervised.conf /etc/supervisor/conf.d/supervised.conf 21COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
20 22
diff --git a/docker/development/nginx.conf b/docker/development/nginx.conf
index cda09b56..ac0c6c61 100644
--- a/docker/development/nginx.conf
+++ b/docker/development/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -49,6 +51,11 @@ http {
49 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 51 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
50 } 52 }
51 53
54 location = /favicon.ico {
55 # serve the Shaarli favicon from its custom location
56 alias /var/www/shaarli/images/favicon.ico;
57 }
58
52 location ~ (index)\.php$ { 59 location ~ (index)\.php$ {
53 # filter and proxy PHP requests to PHP-FPM 60 # filter and proxy PHP requests to PHP-FPM
54 fastcgi_pass unix:/var/run/php5-fpm.sock; 61 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile
index d93ed262..d0509115 100644
--- a/docker/production/Dockerfile
+++ b/docker/production/Dockerfile
@@ -14,6 +14,8 @@ RUN apt-get update \
14 supervisor \ 14 supervisor \
15 && apt-get clean 15 && apt-get clean
16 16
17RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
18RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
17COPY nginx.conf /etc/nginx/nginx.conf 19COPY nginx.conf /etc/nginx/nginx.conf
18COPY supervised.conf /etc/supervisor/conf.d/supervised.conf 20COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
19 21
diff --git a/docker/production/nginx.conf b/docker/production/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/nginx.conf
+++ b/docker/production/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -41,6 +43,11 @@ http {
41 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 43 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
42 } 44 }
43 45
46 location = /favicon.ico {
47 # serve the Shaarli favicon from its custom location
48 alias /var/www/shaarli/images/favicon.ico;
49 }
50
44 location ~ (index)\.php$ { 51 location ~ (index)\.php$ {
45 # filter and proxy PHP requests to PHP-FPM 52 # filter and proxy PHP requests to PHP-FPM
46 fastcgi_pass unix:/var/run/php5-fpm.sock; 53 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/stable/Dockerfile b/docker/production/stable/Dockerfile
index a509fda6..fc9588b0 100644
--- a/docker/production/stable/Dockerfile
+++ b/docker/production/stable/Dockerfile
@@ -14,6 +14,8 @@ RUN apt-get update \
14 supervisor \ 14 supervisor \
15 && apt-get clean 15 && apt-get clean
16 16
17RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
18RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
17COPY nginx.conf /etc/nginx/nginx.conf 19COPY nginx.conf /etc/nginx/nginx.conf
18COPY supervised.conf /etc/supervisor/conf.d/supervised.conf 20COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
19 21
diff --git a/docker/production/stable/nginx.conf b/docker/production/stable/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/stable/nginx.conf
+++ b/docker/production/stable/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -41,6 +43,11 @@ http {
41 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 43 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
42 } 44 }
43 45
46 location = /favicon.ico {
47 # serve the Shaarli favicon from its custom location
48 alias /var/www/shaarli/images/favicon.ico;
49 }
50
44 location ~ (index)\.php$ { 51 location ~ (index)\.php$ {
45 # filter and proxy PHP requests to PHP-FPM 52 # filter and proxy PHP requests to PHP-FPM
46 fastcgi_pass unix:/var/run/php5-fpm.sock; 53 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/inc/shaarli.css b/inc/shaarli.css
index 5808320c..a24d4b7c 100644
--- a/inc/shaarli.css
+++ b/inc/shaarli.css
@@ -37,6 +37,10 @@ em {
37 font-style: italic; 37 font-style: italic;
38} 38}
39 39
40strong {
41 font-weight: bold;
42}
43
40/* Buttons */ 44/* Buttons */
41.bigbutton { 45.bigbutton {
42 background-color: #c0c0c0; 46 background-color: #c0c0c0;
@@ -1168,8 +1172,13 @@ ul.errors {
1168} 1172}
1169 1173
1170#pluginsadmin a { 1174#pluginsadmin a {
1175 color: #486D08;
1176}
1177
1178#pluginsadmin a.arrow {
1171 color: black; 1179 color: black;
1172} 1180}
1181
1173/* 404 page */ 1182/* 404 page */
1174.error-container { 1183.error-container {
1175 1184
diff --git a/index.php b/index.php
index c4c0d15a..a0a3a8c7 100644
--- a/index.php
+++ b/index.php
@@ -1,6 +1,6 @@
1<?php 1<?php
2/** 2/**
3 * Shaarli v0.8.0 - Shaare your links... 3 * Shaarli v0.8.1 - Shaare your links...
4 * 4 *
5 * The personal, minimalist, super-fast, database free, bookmarking service. 5 * The personal, minimalist, super-fast, database free, bookmarking service.
6 * 6 *
@@ -25,7 +25,7 @@ if (date_default_timezone_get() == '') {
25/* 25/*
26 * PHP configuration 26 * PHP configuration
27 */ 27 */
28define('shaarli_version', '0.8.0'); 28define('shaarli_version', '0.8.1');
29 29
30// http://server.com/x/shaarli --> /shaarli/ 30// http://server.com/x/shaarli --> /shaarli/
31define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); 31define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@ -564,24 +564,19 @@ function showDailyRSS($conf) {
564 ); 564 );
565 565
566 /* Some Shaarlies may have very few links, so we need to look 566 /* Some Shaarlies may have very few links, so we need to look
567 back in time (rsort()) until we have enough days ($nb_of_days). 567 back in time until we have enough days ($nb_of_days).
568 */ 568 */
569 $linkdates = array();
570 foreach ($LINKSDB as $linkdate => $value) {
571 $linkdates[] = $linkdate;
572 }
573 rsort($linkdates);
574 $nb_of_days = 7; // We take 7 days. 569 $nb_of_days = 7; // We take 7 days.
575 $today = date('Ymd'); 570 $today = date('Ymd');
576 $days = array(); 571 $days = array();
577 572
578 foreach ($linkdates as $linkdate) { 573 foreach ($LINKSDB as $link) {
579 $day = substr($linkdate, 0, 8); // Extract day (without time) 574 $day = $link['created']->format('Ymd'); // Extract day (without time)
580 if (strcmp($day,$today) < 0) { 575 if (strcmp($day, $today) < 0) {
581 if (empty($days[$day])) { 576 if (empty($days[$day])) {
582 $days[$day] = array(); 577 $days[$day] = array();
583 } 578 }
584 $days[$day][] = $linkdate; 579 $days[$day][] = $link;
585 } 580 }
586 581
587 if (count($days) > $nb_of_days) { 582 if (count($days) > $nb_of_days) {
@@ -601,24 +596,18 @@ function showDailyRSS($conf) {
601 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; 596 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
602 597
603 // For each day. 598 // For each day.
604 foreach ($days as $day => $linkdates) { 599 foreach ($days as $day => $links) {
605 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 600 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
606 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. 601 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
607 602
608 // Build the HTML body of this RSS entry.
609 $links = array();
610
611 // We pre-format some fields for proper output. 603 // We pre-format some fields for proper output.
612 foreach ($linkdates as $linkdate) { 604 foreach ($links as &$link) {
613 $l = $LINKSDB[$linkdate]; 605 $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
614 $l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url')); 606 $link['thumbnail'] = thumbnail($conf, $link['url']);
615 $l['thumbnail'] = thumbnail($conf, $l['url']); 607 $link['timestamp'] = $link['created']->getTimestamp();
616 $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']); 608 if (startsWith($link['url'], '?')) {
617 $l['timestamp'] = $l_date->getTimestamp(); 609 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
618 if (startsWith($l['url'], '?')) {
619 $l['url'] = index_url($_SERVER) . $l['url']; // make permalink URL absolute
620 } 610 }
621 $links[$linkdate] = $l;
622 } 611 }
623 612
624 // Then build the HTML for this day: 613 // Then build the HTML for this day:
@@ -680,8 +669,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
680 $linksToDisplay[$key]['taglist']=$taglist; 669 $linksToDisplay[$key]['taglist']=$taglist;
681 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url')); 670 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
682 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']); 671 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
683 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 672 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
684 $linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
685 } 673 }
686 674
687 /* We need to spread the articles on 3 columns. 675 /* We need to spread the articles on 3 columns.
@@ -831,7 +819,7 @@ function renderPage($conf, $pluginManager)
831 // Get only links which have a thumbnail. 819 // Get only links which have a thumbnail.
832 foreach($links as $link) 820 foreach($links as $link)
833 { 821 {
834 $permalink='?'.escape(smallHash($link['linkdate'])); 822 $permalink='?'.$link['shorturl'];
835 $thumb=lazyThumbnail($conf, $link['url'],$permalink); 823 $thumb=lazyThumbnail($conf, $link['url'],$permalink);
836 if ($thumb!='') // Only output links which have a thumbnail. 824 if ($thumb!='') // Only output links which have a thumbnail.
837 { 825 {
@@ -1078,6 +1066,7 @@ function renderPage($conf, $pluginManager)
1078 { 1066 {
1079 $data = array( 1067 $data = array(
1080 'pageabsaddr' => index_url($_SERVER), 1068 'pageabsaddr' => index_url($_SERVER),
1069 'sslenabled' => !empty($_SERVER['HTTPS'])
1081 ); 1070 );
1082 $pluginManager->executeHooks('render_tools', $data); 1071 $pluginManager->executeHooks('render_tools', $data);
1083 1072
@@ -1244,13 +1233,30 @@ function renderPage($conf, $pluginManager)
1244 // -------- User clicked the "Save" button when editing a link: Save link to database. 1233 // -------- User clicked the "Save" button when editing a link: Save link to database.
1245 if (isset($_POST['save_edit'])) 1234 if (isset($_POST['save_edit']))
1246 { 1235 {
1247 $linkdate = $_POST['lf_linkdate'];
1248 $updated = isset($LINKSDB[$linkdate]) ? strval(date('Ymd_His')) : false;
1249
1250 // Go away! 1236 // Go away!
1251 if (! tokenOk($_POST['token'])) { 1237 if (! tokenOk($_POST['token'])) {
1252 die('Wrong token.'); 1238 die('Wrong token.');
1253 } 1239 }
1240
1241 // lf_id should only be present if the link exists.
1242 $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
1243 // Linkdate is kept here to:
1244 // - use the same permalink for notes as they're displayed when creating them
1245 // - let users hack creation date of their posts
1246 // See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link
1247 $linkdate = escape($_POST['lf_linkdate']);
1248 if (isset($LINKSDB[$id])) {
1249 // Edit
1250 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1251 $updated = new DateTime();
1252 $shortUrl = $LINKSDB[$id]['shorturl'];
1253 } else {
1254 // New link
1255 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1256 $updated = null;
1257 $shortUrl = link_small_hash($created, $id);
1258 }
1259
1254 // Remove multiple spaces. 1260 // Remove multiple spaces.
1255 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); 1261 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
1256 // Remove first '-' char in tags. 1262 // Remove first '-' char in tags.
@@ -1267,14 +1273,17 @@ function renderPage($conf, $pluginManager)
1267 } 1273 }
1268 1274
1269 $link = array( 1275 $link = array(
1276 'id' => $id,
1270 'title' => trim($_POST['lf_title']), 1277 'title' => trim($_POST['lf_title']),
1271 'url' => $url, 1278 'url' => $url,
1272 'description' => $_POST['lf_description'], 1279 'description' => $_POST['lf_description'],
1273 'private' => (isset($_POST['lf_private']) ? 1 : 0), 1280 'private' => (isset($_POST['lf_private']) ? 1 : 0),
1274 'linkdate' => $linkdate, 1281 'created' => $created,
1275 'updated' => $updated, 1282 'updated' => $updated,
1276 'tags' => str_replace(',', ' ', $tags) 1283 'tags' => str_replace(',', ' ', $tags),
1284 'shorturl' => $shortUrl,
1277 ); 1285 );
1286
1278 // If title is empty, use the URL as title. 1287 // If title is empty, use the URL as title.
1279 if ($link['title'] == '') { 1288 if ($link['title'] == '') {
1280 $link['title'] = $link['url']; 1289 $link['title'] = $link['url'];
@@ -1282,7 +1291,7 @@ function renderPage($conf, $pluginManager)
1282 1291
1283 $pluginManager->executeHooks('save_link', $link); 1292 $pluginManager->executeHooks('save_link', $link);
1284 1293
1285 $LINKSDB[$linkdate] = $link; 1294 $LINKSDB[$id] = $link;
1286 $LINKSDB->save($conf->get('resource.page_cache')); 1295 $LINKSDB->save($conf->get('resource.page_cache'));
1287 pubsubhub($conf); 1296 pubsubhub($conf);
1288 1297
@@ -1295,7 +1304,7 @@ function renderPage($conf, $pluginManager)
1295 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; 1304 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
1296 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1305 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1297 // Scroll to the link which has been edited. 1306 // Scroll to the link which has been edited.
1298 $location .= '#' . smallHash($_POST['lf_linkdate']); 1307 $location .= '#' . $link['shorturl'];
1299 // After saving the link, redirect to the page the user was on. 1308 // After saving the link, redirect to the page the user was on.
1300 header('Location: '. $location); 1309 header('Location: '. $location);
1301 exit; 1310 exit;
@@ -1306,27 +1315,31 @@ function renderPage($conf, $pluginManager)
1306 { 1315 {
1307 // If we are called from the bookmarklet, we must close the popup: 1316 // If we are called from the bookmarklet, we must close the popup:
1308 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1317 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
1318 $link = $LINKSDB[(int) escape($_POST['lf_id'])];
1309 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1319 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1310 $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. 1320 // Scroll to the link which has been edited.
1321 $returnurl .= '#'. $link['shorturl'];
1311 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1322 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1312 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. 1323 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
1313 exit; 1324 exit;
1314 } 1325 }
1315 1326
1316 // -------- User clicked the "Delete" button when editing a link: Delete link from database. 1327 // -------- User clicked the "Delete" button when editing a link: Delete link from database.
1317 if ($targetPage == Router::$PAGE_DELETELINK) 1328 if (isset($_POST['delete_link']))
1318 { 1329 {
1319 if (!tokenOk($_GET['token'])) die('Wrong token.'); 1330 if (!tokenOk($_POST['token'])) die('Wrong token.');
1331
1320 // We do not need to ask for confirmation: 1332 // We do not need to ask for confirmation:
1321 // - confirmation is handled by JavaScript 1333 // - confirmation is handled by JavaScript
1322 // - we are protected from XSRF by the token. 1334 // - we are protected from XSRF by the token.
1323 $linkdate = $_GET['delete_link'];
1324 $link = $LINKSDB[$linkdate];
1325
1326 $pluginManager->executeHooks('delete_link', $link);
1327 1335
1328 unset($LINKSDB[$linkdate]); 1336 // FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
1329 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk 1337 $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
1338
1339 $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
1340
1341 unset($LINKSDB[$id]);
1342 $LINKSDB->save('resource.page_cache'); // save to disk
1330 1343
1331 // If we are called from the bookmarklet, we must close the popup: 1344 // If we are called from the bookmarklet, we must close the popup:
1332 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1345 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1364,8 +1377,10 @@ function renderPage($conf, $pluginManager)
1364 // -------- User clicked the "EDIT" button on a link: Display link edit form. 1377 // -------- User clicked the "EDIT" button on a link: Display link edit form.
1365 if (isset($_GET['edit_link'])) 1378 if (isset($_GET['edit_link']))
1366 { 1379 {
1367 $link = $LINKSDB[$_GET['edit_link']]; // Read database 1380 $id = (int) escape($_GET['edit_link']);
1381 $link = $LINKSDB[$id]; // Read database
1368 if (!$link) { header('Location: ?'); exit; } // Link not found in database. 1382 if (!$link) { header('Location: ?'); exit; } // Link not found in database.
1383 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1369 $data = array( 1384 $data = array(
1370 'link' => $link, 1385 'link' => $link,
1371 'link_is_new' => false, 1386 'link_is_new' => false,
@@ -1389,10 +1404,10 @@ function renderPage($conf, $pluginManager)
1389 $link_is_new = false; 1404 $link_is_new = false;
1390 // Check if URL is not already in database (in this case, we will edit the existing link) 1405 // Check if URL is not already in database (in this case, we will edit the existing link)
1391 $link = $LINKSDB->getLinkFromUrl($url); 1406 $link = $LINKSDB->getLinkFromUrl($url);
1392 if (!$link) 1407 if (! $link)
1393 { 1408 {
1394 $link_is_new = true; 1409 $link_is_new = true;
1395 $linkdate = strval(date('Ymd_His')); 1410 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
1396 // Get title if it was provided in URL (by the bookmarklet). 1411 // Get title if it was provided in URL (by the bookmarklet).
1397 $title = empty($_GET['title']) ? '' : escape($_GET['title']); 1412 $title = empty($_GET['title']) ? '' : escape($_GET['title']);
1398 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] 1413 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
@@ -1416,7 +1431,7 @@ function renderPage($conf, $pluginManager)
1416 } 1431 }
1417 1432
1418 if ($url == '') { 1433 if ($url == '') {
1419 $url = '?' . smallHash($linkdate); 1434 $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
1420 $title = 'Note: '; 1435 $title = 'Note: ';
1421 } 1436 }
1422 $url = escape($url); 1437 $url = escape($url);
@@ -1430,6 +1445,8 @@ function renderPage($conf, $pluginManager)
1430 'tags' => $tags, 1445 'tags' => $tags,
1431 'private' => $private 1446 'private' => $private
1432 ); 1447 );
1448 } else {
1449 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1433 } 1450 }
1434 1451
1435 $data = array( 1452 $data = array(
@@ -1635,18 +1652,15 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1635 $link['description'] = format_description($link['description'], $conf->get('redirector.url')); 1652 $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
1636 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; 1653 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
1637 $link['class'] = $link['private'] == 0 ? $classLi : 'private'; 1654 $link['class'] = $link['private'] == 0 ? $classLi : 'private';
1638 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 1655 $link['timestamp'] = $link['created']->getTimestamp();
1639 $link['timestamp'] = $date->getTimestamp();
1640 if (! empty($link['updated'])) { 1656 if (! empty($link['updated'])) {
1641 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']); 1657 $link['updated_timestamp'] = $link['updated']->getTimestamp();
1642 $link['updated_timestamp'] = $date->getTimestamp();
1643 } else { 1658 } else {
1644 $link['updated_timestamp'] = ''; 1659 $link['updated_timestamp'] = '';
1645 } 1660 }
1646 $taglist = explode(' ', $link['tags']); 1661 $taglist = explode(' ', $link['tags']);
1647 uasort($taglist, 'strcasecmp'); 1662 uasort($taglist, 'strcasecmp');
1648 $link['taglist'] = $taglist; 1663 $link['taglist'] = $taglist;
1649 $link['shorturl'] = smallHash($link['linkdate']);
1650 // Check for both signs of a note: starting with ? and 7 chars long. 1664 // Check for both signs of a note: starting with ? and 7 chars long.
1651 if ($link['url'][0] === '?' && 1665 if ($link['url'][0] === '?' &&
1652 strlen($link['url']) === 7) { 1666 strlen($link['url']) === 7) {
diff --git a/pagecache/.htaccess b/pagecache/.htaccess
index b584d98c..f601c1ee 100644
--- a/pagecache/.htaccess
+++ b/pagecache/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/plugins/addlink_toolbar/addlink_toolbar.css b/plugins/addlink_toolbar/addlink_toolbar.css
deleted file mode 100644
index b6a612f0..00000000
--- a/plugins/addlink_toolbar/addlink_toolbar.css
+++ /dev/null
@@ -1,4 +0,0 @@
1#addlink_toolbar {
2 display: inline;
3 margin: 0 0 0 25px;
4} \ No newline at end of file
diff --git a/plugins/addlink_toolbar/addlink_toolbar.php b/plugins/addlink_toolbar/addlink_toolbar.php
index c96a891e..bf8a198a 100644
--- a/plugins/addlink_toolbar/addlink_toolbar.php
+++ b/plugins/addlink_toolbar/addlink_toolbar.php
@@ -16,10 +16,12 @@ function hook_addlink_toolbar_render_header($data)
16{ 16{
17 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) { 17 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
18 $form = array( 18 $form = array(
19 'method' => 'GET', 19 'attr' => array(
20 'action' => '', 20 'method' => 'GET',
21 'name' => 'addform', 21 'action' => '',
22 'class' => 'addform', 22 'name' => 'addform',
23 'class' => 'addform',
24 ),
23 'inputs' => array( 25 'inputs' => array(
24 array( 26 array(
25 'type' => 'text', 27 'type' => 'text',
diff --git a/plugins/archiveorg/archiveorg.html b/plugins/archiveorg/archiveorg.html
index 576bd46e..0781fe35 100644
--- a/plugins/archiveorg/archiveorg.html
+++ b/plugins/archiveorg/archiveorg.html
@@ -1 +1 @@
<span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" /></a></span> <span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" alt="archive.org" /></a></span>
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php
index 15482fe0..8fdbf663 100644
--- a/plugins/demo_plugin/demo_plugin.php
+++ b/plugins/demo_plugin/demo_plugin.php
@@ -56,9 +56,11 @@ function hook_demo_plugin_render_header($data)
56 * and a mandatory `html` key, which contains its value. 56 * and a mandatory `html` key, which contains its value.
57 */ 57 */
58 $button = array( 58 $button = array(
59 'href' => '#', 59 'attr' => array (
60 'class' => 'mybutton', 60 'href' => '#',
61 'title' => 'hover me', 61 'class' => 'mybutton',
62 'title' => 'hover me',
63 ),
62 'html' => 'DEMO buttons toolbar', 64 'html' => 'DEMO buttons toolbar',
63 ); 65 );
64 $data['buttons_toolbar'][] = $button; 66 $data['buttons_toolbar'][] = $button;
@@ -87,9 +89,11 @@ function hook_demo_plugin_render_header($data)
87 * </form> 89 * </form>
88 */ 90 */
89 $form = array( 91 $form = array(
90 'method' => 'GET', 92 'attr' => array(
91 'action' => '?', 93 'method' => 'GET',
92 'class' => 'addform', 94 'action' => '?',
95 'class' => 'addform',
96 ),
93 'inputs' => array( 97 'inputs' => array(
94 array( 98 array(
95 'type' => 'text', 99 'type' => 'text',
@@ -102,7 +106,9 @@ function hook_demo_plugin_render_header($data)
102 } 106 }
103 // Another button always displayed 107 // Another button always displayed
104 $button = array( 108 $button = array(
105 'href' => '#', 109 'attr' => array(
110 'href' => '#',
111 ),
106 'html' => 'Demo', 112 'html' => 'Demo',
107 ); 113 );
108 $data['buttons_toolbar'][] = $button; 114 $data['buttons_toolbar'][] = $button;
@@ -197,8 +203,10 @@ function hook_demo_plugin_render_linklist($data)
197 * It's also recommended to add key 'on' or 'off' for theme rendering. 203 * It's also recommended to add key 'on' or 'off' for theme rendering.
198 */ 204 */
199 $action = array( 205 $action = array(
200 'href' => '?up', 206 'attr' => array(
201 'title' => 'Uppercase!', 207 'href' => '?up',
208 'title' => 'Uppercase!',
209 ),
202 'html' => '←', 210 'html' => '←',
203 ); 211 );
204 212
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php
index ffb7cfac..ce16645f 100644
--- a/plugins/isso/isso.php
+++ b/plugins/isso/isso.php
@@ -41,9 +41,9 @@ function hook_isso_render_linklist($data, $conf)
41 // Only display comments for permalinks. 41 // Only display comments for permalinks.
42 if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) { 42 if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
43 $link = reset($data['links']); 43 $link = reset($data['links']);
44 $isso_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html'); 44 $issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
45 45
46 $isso = sprintf($isso_html, $issoUrl, $issoUrl, $link['linkdate'], $link['linkdate']); 46 $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
47 $data['plugin_end_zone'][] = $isso; 47 $data['plugin_end_zone'][] = $isso;
48 48
49 // Hackish way to include this CSS file only when necessary. 49 // Hackish way to include this CSS file only when necessary.
diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md
index 4f021871..aafcf066 100644
--- a/plugins/markdown/README.md
+++ b/plugins/markdown/README.md
@@ -20,26 +20,52 @@ The directory structure should look like:
20 |--- markdown.css 20 |--- markdown.css
21 |--- markdown.meta 21 |--- markdown.meta
22 |--- markdown.php 22 |--- markdown.php
23 |--- Parsedown.php
24 |--- README.md 23 |--- README.md
25``` 24```
26 25
27To enable the plugin, just check it in the plugin administration page. 26To enable the plugin, just check it in the plugin administration page.
28 27
29You can also add `markdown` to your list of enabled plugins in `data/config.php` 28You can also add `markdown` to your list of enabled plugins in `data/config.json.php`
30(`ENABLED_PLUGINS` array). 29(`general.enabled_plugins` list).
31 30
32This should look like: 31This should look like:
33 32
34``` 33```
35$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown') 34"general": {
35 "enabled_plugins": [
36 "markdown",
37 [...]
38 ],
39}
36``` 40```
37 41
42Parsedown parsing library is imported using Composer. If you installed Shaarli using `git`,
43or the `master` branch, run
44
45 composer update --no-dev --prefer-dist
46
38### No Markdown tag 47### No Markdown tag
39 48
40If the tag `.nomarkdown` is set for a shaare, it won't be converted to Markdown syntax. 49If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
41 50
42> Note: it's a private tag (leading dot), so it won't be displayed to visitors. 51> Note: this is a special tag, so it won't be displayed in link list.
52
53### HTML rendering
54
55Markdown support HTML tags. For example:
56
57 > <strong>strong</strong><strike>strike</strike>
58
59Will render as:
60
61> <strong>strong</strong><strike>strike</strike>
62
63If you want to shaare HTML code, it is necessary to use inline code or code blocks.
64
65**If your shaared descriptions containing HTML tags before enabling the markdown plugin,
66enabling it might break your page.**
67
68> Note: HTML tags such as script, iframe, etc. are disabled for security reasons.
43 69
44### Known issue 70### Known issue
45 71
diff --git a/plugins/markdown/markdown.meta b/plugins/markdown/markdown.meta
index e3904ed8..8df2ed0b 100644
--- a/plugins/markdown/markdown.meta
+++ b/plugins/markdown/markdown.meta
@@ -1 +1,4 @@
1description="Render shaare description with Markdown syntax." 1description="Render shaare description with Markdown syntax.<br><strong>Warning</strong>:
2If your shaared descriptions containing HTML tags before enabling the markdown plugin,
3enabling it might break your page.
4See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>."
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index a764b6fa..0cf6e6e2 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -22,7 +22,7 @@ function hook_markdown_render_linklist($data)
22{ 22{
23 foreach ($data['links'] as &$value) { 23 foreach ($data['links'] as &$value) {
24 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { 24 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
25 $value['taglist'] = stripNoMarkdownTag($value['taglist']); 25 $value = stripNoMarkdownTag($value);
26 continue; 26 continue;
27 } 27 }
28 $value['description'] = process_markdown($value['description']); 28 $value['description'] = process_markdown($value['description']);
@@ -41,7 +41,7 @@ function hook_markdown_render_feed($data)
41{ 41{
42 foreach ($data['links'] as &$value) { 42 foreach ($data['links'] as &$value) {
43 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { 43 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
44 $value['tags'] = stripNoMarkdownTag($value['tags']); 44 $value = stripNoMarkdownTag($value);
45 continue; 45 continue;
46 } 46 }
47 $value['description'] = process_markdown($value['description']); 47 $value['description'] = process_markdown($value['description']);
@@ -63,6 +63,7 @@ function hook_markdown_render_daily($data)
63 foreach ($data['cols'] as &$value) { 63 foreach ($data['cols'] as &$value) {
64 foreach ($value as &$value2) { 64 foreach ($value as &$value2) {
65 if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) { 65 if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) {
66 $value2 = stripNoMarkdownTag($value2);
66 continue; 67 continue;
67 } 68 }
68 $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); 69 $value2['formatedDescription'] = process_markdown($value2['formatedDescription']);
@@ -81,20 +82,30 @@ function hook_markdown_render_daily($data)
81 */ 82 */
82function noMarkdownTag($tags) 83function noMarkdownTag($tags)
83{ 84{
84 return strpos($tags, NO_MD_TAG) !== false; 85 return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags);
85} 86}
86 87
87/** 88/**
88 * Remove the no-markdown meta tag so it won't be displayed. 89 * Remove the no-markdown meta tag so it won't be displayed.
89 * 90 *
90 * @param string $tags Tag list. 91 * @param array $link Link data.
91 * 92 *
92 * @return string tag list without no markdown tag. 93 * @return array Updated link without no markdown tag.
93 */ 94 */
94function stripNoMarkdownTag($tags) 95function stripNoMarkdownTag($link)
95{ 96{
96 unset($tags[array_search(NO_MD_TAG, $tags)]); 97 if (! empty($link['taglist'])) {
97 return array_values($tags); 98 $offset = array_search(NO_MD_TAG, $link['taglist']);
99 if ($offset !== false) {
100 unset($link['taglist'][$offset]);
101 }
102 }
103
104 if (!empty($link['tags'])) {
105 str_replace(NO_MD_TAG, '', $link['tags']);
106 }
107
108 return $link;
98} 109}
99 110
100/** 111/**
diff --git a/plugins/playvideos/playvideos.php b/plugins/playvideos/playvideos.php
index 2f4b0a31..64484504 100644
--- a/plugins/playvideos/playvideos.php
+++ b/plugins/playvideos/playvideos.php
@@ -17,9 +17,11 @@ function hook_playvideos_render_header($data)
17{ 17{
18 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { 18 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
19 $playvideo = array( 19 $playvideo = array(
20 'href' => '#', 20 'attr' => array(
21 'title' => 'Video player', 21 'href' => '#',
22 'id' => 'playvideos', 22 'title' => 'Video player',
23 'id' => 'playvideos',
24 ),
23 'html' => '► Play Videos' 25 'html' => '► Play Videos'
24 ); 26 );
25 $data['buttons_toolbar'][] = $playvideo; 27 $data['buttons_toolbar'][] = $playvideo;
diff --git a/plugins/qrcode/qrcode.html b/plugins/qrcode/qrcode.html
index cebc5644..dc214ed1 100644
--- a/plugins/qrcode/qrcode.html
+++ b/plugins/qrcode/qrcode.html
@@ -1,5 +1,5 @@
1<div class="linkqrcode"> 1<div class="linkqrcode">
2 <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s"> 2 <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
3 <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code"> 3 <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code" alt="QRCode">
4 </a> 4 </a>
5</div> 5</div>
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
index e8c5f784..5e200715 100644
--- a/plugins/readityourself/readityourself.html
+++ b/plugins/readityourself/readityourself.html
@@ -1 +1 @@
<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" /></a></span> <span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" alt="readityourself" /></a></span>
diff --git a/plugins/wallabag/wallabag.html b/plugins/wallabag/wallabag.html
index c7b1d044..e861536d 100644
--- a/plugins/wallabag/wallabag.html
+++ b/plugins/wallabag/wallabag.html
@@ -1 +1 @@
<span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" /></a></span> <span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" alt="wallabag" /></a></span>
diff --git a/shaarli_version.php b/shaarli_version.php
index eaab95c6..431387bb 100644
--- a/shaarli_version.php
+++ b/shaarli_version.php
@@ -1 +1 @@
<?php /* 0.8.0 */ ?> <?php /* 0.8.1 */ ?>
diff --git a/tests/.htaccess b/tests/.htaccess
index b584d98c..f601c1ee 100644
--- a/tests/.htaccess
+++ b/tests/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index d7839402..06a44506 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -84,8 +84,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
85 85
86 // Test first link (note link) 86 // Test first link (note link)
87 $link = array_shift($data['links']); 87 $link = reset($data['links']);
88 $this->assertEquals('20150310_114651', $link['linkdate']); 88 $this->assertEquals(41, $link['id']);
89 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 90 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
90 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 91 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
91 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); 92 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
@@ -99,14 +100,14 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
99 $this->assertEquals('sTuff', $link['taglist'][0]); 100 $this->assertEquals('sTuff', $link['taglist'][0]);
100 101
101 // Test URL with external link. 102 // Test URL with external link.
102 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']); 103 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
103 104
104 // Test multitags. 105 // Test multitags.
105 $this->assertEquals(5, count($data['links']['20141125_084734']['taglist'])); 106 $this->assertEquals(5, count($data['links'][6]['taglist']));
106 $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]); 107 $this->assertEquals('css', $data['links'][6]['taglist'][0]);
107 108
108 // Test update date 109 // Test update date
109 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']); 110 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
110 } 111 }
111 112
112 /** 113 /**
@@ -119,9 +120,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
119 $data = $feedBuilder->buildData(); 120 $data = $feedBuilder->buildData();
120 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 121 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
121 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); 122 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
122 $link = array_shift($data['links']); 123 $link = reset($data['links']);
123 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']); 124 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
124 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']); 125 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
125 } 126 }
126 127
127 /** 128 /**
@@ -138,7 +139,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
138 $data = $feedBuilder->buildData(); 139 $data = $feedBuilder->buildData();
139 $this->assertEquals(1, count($data['links'])); 140 $this->assertEquals(1, count($data['links']));
140 $link = array_shift($data['links']); 141 $link = array_shift($data['links']);
141 $this->assertEquals('20150310_114651', $link['linkdate']); 142 $this->assertEquals(41, $link['id']);
143 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
142 } 144 }
143 145
144 /** 146 /**
@@ -154,7 +156,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
154 $data = $feedBuilder->buildData(); 156 $data = $feedBuilder->buildData();
155 $this->assertEquals(1, count($data['links'])); 157 $this->assertEquals(1, count($data['links']));
156 $link = array_shift($data['links']); 158 $link = array_shift($data['links']);
157 $this->assertEquals('20150310_114651', $link['linkdate']); 159 $this->assertEquals(41, $link['id']);
160 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
158 } 161 }
159 162
160 /** 163 /**
@@ -170,15 +173,17 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
170 $this->assertTrue($data['usepermalinks']); 173 $this->assertTrue($data['usepermalinks']);
171 // First link is a permalink 174 // First link is a permalink
172 $link = array_shift($data['links']); 175 $link = array_shift($data['links']);
173 $this->assertEquals('20150310_114651', $link['linkdate']); 176 $this->assertEquals(41, $link['id']);
177 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
174 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 178 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
175 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 179 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
176 $this->assertContains('Direct link', $link['description']); 180 $this->assertContains('Direct link', $link['description']);
177 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 181 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
178 // Second link is a direct link 182 // Second link is a direct link
179 $link = array_shift($data['links']); 183 $link = array_shift($data['links']);
180 $this->assertEquals('20150310_114633', $link['linkdate']); 184 $this->assertEquals(8, $link['id']);
181 $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']); 185 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
186 $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
182 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); 187 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
183 $this->assertContains('Direct link', $link['description']); 188 $this->assertContains('Direct link', $link['description']);
184 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); 189 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 9d79386c..1f62a34a 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -186,14 +186,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
186 $dbSize = sizeof($testDB); 186 $dbSize = sizeof($testDB);
187 187
188 $link = array( 188 $link = array(
189 'id' => 42,
189 'title'=>'an additional link', 190 'title'=>'an additional link',
190 'url'=>'http://dum.my', 191 'url'=>'http://dum.my',
191 'description'=>'One more', 192 'description'=>'One more',
192 'private'=>0, 193 'private'=>0,
193 'linkdate'=>'20150518_190000', 194 'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
194 'tags'=>'unit test' 195 'tags'=>'unit test'
195 ); 196 );
196 $testDB[$link['linkdate']] = $link; 197 $testDB[$link['id']] = $link;
197 $testDB->save('tests'); 198 $testDB->save('tests');
198 199
199 $testDB = new LinkDB(self::$testDatastore, true, false); 200 $testDB = new LinkDB(self::$testDatastore, true, false);
@@ -238,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
238 public function testDays() 239 public function testDays()
239 { 240 {
240 $this->assertEquals( 241 $this->assertEquals(
241 array('20121206', '20130614', '20150310'), 242 array('20100310', '20121206', '20130614', '20150310'),
242 self::$publicLinkDB->days() 243 self::$publicLinkDB->days()
243 ); 244 );
244 245
245 $this->assertEquals( 246 $this->assertEquals(
246 array('20121206', '20130614', '20141125', '20150310'), 247 array('20100310', '20121206', '20130614', '20141125', '20150310'),
247 self::$privateLinkDB->days() 248 self::$privateLinkDB->days()
248 ); 249 );
249 } 250 }
@@ -290,10 +291,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
290 'stallman' => 1, 291 'stallman' => 1,
291 'free' => 1, 292 'free' => 1,
292 '-exclude' => 1, 293 '-exclude' => 1,
294 'hashtag' => 2,
293 // The DB contains a link with `sTuff` and another one with `stuff` tag. 295 // The DB contains a link with `sTuff` and another one with `stuff` tag.
294 // They need to be grouped with the first case found (`sTuff`). 296 // They need to be grouped with the first case found - order by date DESC: `sTuff`.
295 'sTuff' => 2, 297 'sTuff' => 2,
296 'hashtag' => 2, 298 'ut' => 1,
297 ), 299 ),
298 self::$publicLinkDB->allTags() 300 self::$publicLinkDB->allTags()
299 ); 301 );
@@ -321,6 +323,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
321 'tag2' => 1, 323 'tag2' => 1,
322 'tag3' => 1, 324 'tag3' => 1,
323 'tag4' => 1, 325 'tag4' => 1,
326 'ut' => 1,
324 ), 327 ),
325 self::$privateLinkDB->allTags() 328 self::$privateLinkDB->allTags()
326 ); 329 );
@@ -411,6 +414,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
411 1, 414 1,
412 count(self::$publicLinkDB->filterHash($request)) 415 count(self::$publicLinkDB->filterHash($request))
413 ); 416 );
417 $request = smallHash('20150310_114633' . 8);
418 $this->assertEquals(
419 1,
420 count(self::$publicLinkDB->filterHash($request))
421 );
414 } 422 }
415 423
416 /** 424 /**
@@ -433,4 +441,23 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
433 { 441 {
434 self::$publicLinkDB->filterHash(''); 442 self::$publicLinkDB->filterHash('');
435 } 443 }
444
445 /**
446 * Test reorder with asc/desc parameter.
447 */
448 public function testReorderLinksDesc()
449 {
450 self::$privateLinkDB->reorder('ASC');
451 $linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
452 $cpt = 0;
453 foreach (self::$privateLinkDB as $key => $value) {
454 $this->assertEquals($linkIds[$cpt++], $key);
455 }
456 self::$privateLinkDB->reorder('DESC');
457 $linkIds = array_reverse($linkIds);
458 $cpt = 0;
459 foreach (self::$privateLinkDB as $key => $value) {
460 $this->assertEquals($linkIds[$cpt++], $key);
461 }
462 }
436} 463}
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 7d45fc59..21d680a5 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -159,7 +159,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
159 159
160 $this->assertEquals( 160 $this->assertEquals(
161 'MediaGoblin', 161 'MediaGoblin',
162 $links['20130614_184135']['title'] 162 $links[7]['title']
163 ); 163 );
164 } 164 }
165 165
@@ -286,7 +286,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
286 ); 286 );
287 287
288 $this->assertEquals( 288 $this->assertEquals(
289 6, 289 7,
290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution')) 290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
291 ); 291 );
292 } 292 }
@@ -346,7 +346,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
346 ); 346 );
347 347
348 $this->assertEquals( 348 $this->assertEquals(
349 6, 349 7,
350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free')) 350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
351 ); 351 );
352 } 352 }
diff --git a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
index cc54ab9f..6a47bbb9 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
@@ -50,7 +50,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, ''); 50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); 51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links));
52 foreach ($links as $link) { 52 foreach ($links as $link) {
53 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 53 $date = $link['created'];
54 $this->assertEquals( 54 $this->assertEquals(
55 $date->getTimestamp(), 55 $date->getTimestamp(),
56 $link['timestamp'] 56 $link['timestamp']
@@ -70,7 +70,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, ''); 70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); 71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
72 foreach ($links as $link) { 72 foreach ($links as $link) {
73 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 73 $date = $link['created'];
74 $this->assertEquals( 74 $this->assertEquals(
75 $date->getTimestamp(), 75 $date->getTimestamp(),
76 $link['timestamp'] 76 $link['timestamp']
@@ -90,7 +90,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); 90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); 91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
92 foreach ($links as $link) { 92 foreach ($links as $link) {
93 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 93 $date = $link['created'];
94 $this->assertEquals( 94 $this->assertEquals(
95 $date->getTimestamp(), 95 $date->getTimestamp(),
96 $link['timestamp'] 96 $link['timestamp']
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index f0ad500f..0ca07eac 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -43,6 +43,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
43 protected $pagecache = 'tests'; 43 protected $pagecache = 'tests';
44 44
45 /** 45 /**
46 * @var string Save the current timezone.
47 */
48 protected static $defaultTimeZone;
49
50 public static function setUpBeforeClass()
51 {
52 self::$defaultTimeZone = date_default_timezone_get();
53 // Timezone without DST for test consistency
54 date_default_timezone_set('Africa/Nairobi');
55 }
56
57 /**
46 * Resets test data before each test 58 * Resets test data before each test
47 */ 59 */
48 protected function setUp() 60 protected function setUp()
@@ -55,6 +67,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
55 $this->linkDb = new LinkDB(self::$testDatastore, true, false); 67 $this->linkDb = new LinkDB(self::$testDatastore, true, false);
56 } 68 }
57 69
70 public static function tearDownAfterClass()
71 {
72 date_default_timezone_set(self::$defaultTimeZone);
73 }
74
58 /** 75 /**
59 * Attempt to import bookmarks from an empty file 76 * Attempt to import bookmarks from an empty file
60 */ 77 */
@@ -98,18 +115,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
98 115
99 $this->assertEquals( 116 $this->assertEquals(
100 array( 117 array(
101 'linkdate' => '20160618_173944', 118 'id' => 0,
119 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
102 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky', 120 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
103 'url' => 'http://hginit.com/', 121 'url' => 'http://hginit.com/',
104 'description' => '', 122 'description' => '',
105 'private' => 0, 123 'private' => 0,
106 'tags' => '' 124 'tags' => '',
125 'shorturl' => 'La37cg',
107 ), 126 ),
108 $this->linkDb->getLinkFromUrl('http://hginit.com/') 127 $this->linkDb->getLinkFromUrl('http://hginit.com/')
109 ); 128 );
110 } 129 }
111 130
112
113 /** 131 /**
114 * Import bookmarks nested in a folder hierarchy 132 * Import bookmarks nested in a folder hierarchy
115 */ 133 */
@@ -126,89 +144,105 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
126 144
127 $this->assertEquals( 145 $this->assertEquals(
128 array( 146 array(
129 'linkdate' => '20160225_205541', 147 'id' => 0,
148 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
130 'title' => 'Nested 1', 149 'title' => 'Nested 1',
131 'url' => 'http://nest.ed/1', 150 'url' => 'http://nest.ed/1',
132 'description' => '', 151 'description' => '',
133 'private' => 0, 152 'private' => 0,
134 'tags' => 'tag1 tag2' 153 'tags' => 'tag1 tag2',
154 'shorturl' => 'KyDNKA',
135 ), 155 ),
136 $this->linkDb->getLinkFromUrl('http://nest.ed/1') 156 $this->linkDb->getLinkFromUrl('http://nest.ed/1')
137 ); 157 );
138 $this->assertEquals( 158 $this->assertEquals(
139 array( 159 array(
140 'linkdate' => '20160225_205542', 160 'id' => 1,
161 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
141 'title' => 'Nested 1-1', 162 'title' => 'Nested 1-1',
142 'url' => 'http://nest.ed/1-1', 163 'url' => 'http://nest.ed/1-1',
143 'description' => '', 164 'description' => '',
144 'private' => 0, 165 'private' => 0,
145 'tags' => 'folder1 tag1 tag2' 166 'tags' => 'folder1 tag1 tag2',
167 'shorturl' => 'T2LnXg',
146 ), 168 ),
147 $this->linkDb->getLinkFromUrl('http://nest.ed/1-1') 169 $this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
148 ); 170 );
149 $this->assertEquals( 171 $this->assertEquals(
150 array( 172 array(
151 'linkdate' => '20160225_205547', 173 'id' => 2,
174 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
152 'title' => 'Nested 1-2', 175 'title' => 'Nested 1-2',
153 'url' => 'http://nest.ed/1-2', 176 'url' => 'http://nest.ed/1-2',
154 'description' => '', 177 'description' => '',
155 'private' => 0, 178 'private' => 0,
156 'tags' => 'folder1 tag3 tag4' 179 'tags' => 'folder1 tag3 tag4',
180 'shorturl' => '46SZxA',
157 ), 181 ),
158 $this->linkDb->getLinkFromUrl('http://nest.ed/1-2') 182 $this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
159 ); 183 );
160 $this->assertEquals( 184 $this->assertEquals(
161 array( 185 array(
162 'linkdate' => '20160202_172222', 186 'id' => 3,
187 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
163 'title' => 'Nested 2-1', 188 'title' => 'Nested 2-1',
164 'url' => 'http://nest.ed/2-1', 189 'url' => 'http://nest.ed/2-1',
165 'description' => 'First link of the second section', 190 'description' => 'First link of the second section',
166 'private' => 1, 191 'private' => 1,
167 'tags' => 'folder2' 192 'tags' => 'folder2',
193 'shorturl' => '4UHOSw',
168 ), 194 ),
169 $this->linkDb->getLinkFromUrl('http://nest.ed/2-1') 195 $this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
170 ); 196 );
171 $this->assertEquals( 197 $this->assertEquals(
172 array( 198 array(
173 'linkdate' => '20160119_200227', 199 'id' => 4,
200 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
174 'title' => 'Nested 2-2', 201 'title' => 'Nested 2-2',
175 'url' => 'http://nest.ed/2-2', 202 'url' => 'http://nest.ed/2-2',
176 'description' => 'Second link of the second section', 203 'description' => 'Second link of the second section',
177 'private' => 1, 204 'private' => 1,
178 'tags' => 'folder2' 205 'tags' => 'folder2',
206 'shorturl' => 'yfzwbw',
179 ), 207 ),
180 $this->linkDb->getLinkFromUrl('http://nest.ed/2-2') 208 $this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
181 ); 209 );
182 $this->assertEquals( 210 $this->assertEquals(
183 array( 211 array(
184 'linkdate' => '20160202_172223', 212 'id' => 5,
213 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
185 'title' => 'Nested 3-1', 214 'title' => 'Nested 3-1',
186 'url' => 'http://nest.ed/3-1', 215 'url' => 'http://nest.ed/3-1',
187 'description' => '', 216 'description' => '',
188 'private' => 0, 217 'private' => 0,
189 'tags' => 'folder3 folder3-1 tag3' 218 'tags' => 'folder3 folder3-1 tag3',
219 'shorturl' => 'UwxIUQ',
190 ), 220 ),
191 $this->linkDb->getLinkFromUrl('http://nest.ed/3-1') 221 $this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
192 ); 222 );
193 $this->assertEquals( 223 $this->assertEquals(
194 array( 224 array(
195 'linkdate' => '20160119_200228', 225 'id' => 6,
226 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
196 'title' => 'Nested 3-2', 227 'title' => 'Nested 3-2',
197 'url' => 'http://nest.ed/3-2', 228 'url' => 'http://nest.ed/3-2',
198 'description' => '', 229 'description' => '',
199 'private' => 0, 230 'private' => 0,
200 'tags' => 'folder3 folder3-1' 231 'tags' => 'folder3 folder3-1',
232 'shorturl' => 'p8dyZg',
201 ), 233 ),
202 $this->linkDb->getLinkFromUrl('http://nest.ed/3-2') 234 $this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
203 ); 235 );
204 $this->assertEquals( 236 $this->assertEquals(
205 array( 237 array(
206 'linkdate' => '20160229_081541', 238 'id' => 7,
239 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
207 'title' => 'Nested 2', 240 'title' => 'Nested 2',
208 'url' => 'http://nest.ed/2', 241 'url' => 'http://nest.ed/2',
209 'description' => '', 242 'description' => '',
210 'private' => 0, 243 'private' => 0,
211 'tags' => 'tag4' 244 'tags' => 'tag4',
245 'shorturl' => 'Gt3Uug',
212 ), 246 ),
213 $this->linkDb->getLinkFromUrl('http://nest.ed/2') 247 $this->linkDb->getLinkFromUrl('http://nest.ed/2')
214 ); 248 );
@@ -227,28 +261,34 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
227 .' 2 links imported, 0 links overwritten, 0 links skipped.', 261 .' 2 links imported, 0 links overwritten, 0 links skipped.',
228 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache) 262 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
229 ); 263 );
264
230 $this->assertEquals(2, count($this->linkDb)); 265 $this->assertEquals(2, count($this->linkDb));
231 $this->assertEquals(1, count_private($this->linkDb)); 266 $this->assertEquals(1, count_private($this->linkDb));
232 267
233 $this->assertEquals( 268 $this->assertEquals(
234 array( 269 array(
235 'linkdate' => '20001010_105536', 270 'id' => 0,
271 // Old link - UTC+4 (note that TZ in the import file is ignored).
272 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
236 'title' => 'Secret stuff', 273 'title' => 'Secret stuff',
237 'url' => 'https://private.tld', 274 'url' => 'https://private.tld',
238 'description' => "Super-secret stuff you're not supposed to know about", 275 'description' => "Super-secret stuff you're not supposed to know about",
239 'private' => 1, 276 'private' => 1,
240 'tags' => 'private secret' 277 'tags' => 'private secret',
278 'shorturl' => 'EokDtA',
241 ), 279 ),
242 $this->linkDb->getLinkFromUrl('https://private.tld') 280 $this->linkDb->getLinkFromUrl('https://private.tld')
243 ); 281 );
244 $this->assertEquals( 282 $this->assertEquals(
245 array( 283 array(
246 'linkdate' => '20160225_205548', 284 'id' => 1,
285 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
247 'title' => 'Public stuff', 286 'title' => 'Public stuff',
248 'url' => 'http://public.tld', 287 'url' => 'http://public.tld',
249 'description' => '', 288 'description' => '',
250 'private' => 0, 289 'private' => 0,
251 'tags' => 'public hello world' 290 'tags' => 'public hello world',
291 'shorturl' => 'Er9ddA',
252 ), 292 ),
253 $this->linkDb->getLinkFromUrl('http://public.tld') 293 $this->linkDb->getLinkFromUrl('http://public.tld')
254 ); 294 );
@@ -271,23 +311,28 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
271 311
272 $this->assertEquals( 312 $this->assertEquals(
273 array( 313 array(
274 'linkdate' => '20001010_105536', 314 'id' => 0,
315 // Note that TZ in the import file is ignored.
316 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
275 'title' => 'Secret stuff', 317 'title' => 'Secret stuff',
276 'url' => 'https://private.tld', 318 'url' => 'https://private.tld',
277 'description' => "Super-secret stuff you're not supposed to know about", 319 'description' => "Super-secret stuff you're not supposed to know about",
278 'private' => 1, 320 'private' => 1,
279 'tags' => 'private secret' 321 'tags' => 'private secret',
322 'shorturl' => 'EokDtA',
280 ), 323 ),
281 $this->linkDb->getLinkFromUrl('https://private.tld') 324 $this->linkDb->getLinkFromUrl('https://private.tld')
282 ); 325 );
283 $this->assertEquals( 326 $this->assertEquals(
284 array( 327 array(
285 'linkdate' => '20160225_205548', 328 'id' => 1,
329 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
286 'title' => 'Public stuff', 330 'title' => 'Public stuff',
287 'url' => 'http://public.tld', 331 'url' => 'http://public.tld',
288 'description' => '', 332 'description' => '',
289 'private' => 0, 333 'private' => 0,
290 'tags' => 'public hello world' 334 'tags' => 'public hello world',
335 'shorturl' => 'Er9ddA',
291 ), 336 ),
292 $this->linkDb->getLinkFromUrl('http://public.tld') 337 $this->linkDb->getLinkFromUrl('http://public.tld')
293 ); 338 );
@@ -309,11 +354,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
309 $this->assertEquals(0, count_private($this->linkDb)); 354 $this->assertEquals(0, count_private($this->linkDb));
310 $this->assertEquals( 355 $this->assertEquals(
311 0, 356 0,
312 $this->linkDb['20001010_105536']['private'] 357 $this->linkDb[0]['private']
313 ); 358 );
314 $this->assertEquals( 359 $this->assertEquals(
315 0, 360 0,
316 $this->linkDb['20160225_205548']['private'] 361 $this->linkDb[1]['private']
317 ); 362 );
318 } 363 }
319 364
@@ -333,11 +378,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
333 $this->assertEquals(2, count_private($this->linkDb)); 378 $this->assertEquals(2, count_private($this->linkDb));
334 $this->assertEquals( 379 $this->assertEquals(
335 1, 380 1,
336 $this->linkDb['20001010_105536']['private'] 381 $this->linkDb['0']['private']
337 ); 382 );
338 $this->assertEquals( 383 $this->assertEquals(
339 1, 384 1,
340 $this->linkDb['20160225_205548']['private'] 385 $this->linkDb['1']['private']
341 ); 386 );
342 } 387 }
343 388
@@ -359,13 +404,12 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
359 $this->assertEquals(2, count_private($this->linkDb)); 404 $this->assertEquals(2, count_private($this->linkDb));
360 $this->assertEquals( 405 $this->assertEquals(
361 1, 406 1,
362 $this->linkDb['20001010_105536']['private'] 407 $this->linkDb[0]['private']
363 ); 408 );
364 $this->assertEquals( 409 $this->assertEquals(
365 1, 410 1,
366 $this->linkDb['20160225_205548']['private'] 411 $this->linkDb[1]['private']
367 ); 412 );
368
369 // re-import as public, enable overwriting 413 // re-import as public, enable overwriting
370 $post = array( 414 $post = array(
371 'privacy' => 'public', 415 'privacy' => 'public',
@@ -380,11 +424,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
380 $this->assertEquals(0, count_private($this->linkDb)); 424 $this->assertEquals(0, count_private($this->linkDb));
381 $this->assertEquals( 425 $this->assertEquals(
382 0, 426 0,
383 $this->linkDb['20001010_105536']['private'] 427 $this->linkDb[0]['private']
384 ); 428 );
385 $this->assertEquals( 429 $this->assertEquals(
386 0, 430 0,
387 $this->linkDb['20160225_205548']['private'] 431 $this->linkDb[1]['private']
388 ); 432 );
389 } 433 }
390 434
@@ -406,11 +450,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
406 $this->assertEquals(0, count_private($this->linkDb)); 450 $this->assertEquals(0, count_private($this->linkDb));
407 $this->assertEquals( 451 $this->assertEquals(
408 0, 452 0,
409 $this->linkDb['20001010_105536']['private'] 453 $this->linkDb['0']['private']
410 ); 454 );
411 $this->assertEquals( 455 $this->assertEquals(
412 0, 456 0,
413 $this->linkDb['20160225_205548']['private'] 457 $this->linkDb['1']['private']
414 ); 458 );
415 459
416 // re-import as private, enable overwriting 460 // re-import as private, enable overwriting
@@ -427,11 +471,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
427 $this->assertEquals(2, count_private($this->linkDb)); 471 $this->assertEquals(2, count_private($this->linkDb));
428 $this->assertEquals( 472 $this->assertEquals(
429 1, 473 1,
430 $this->linkDb['20001010_105536']['private'] 474 $this->linkDb['0']['private']
431 ); 475 );
432 $this->assertEquals( 476 $this->assertEquals(
433 1, 477 1,
434 $this->linkDb['20160225_205548']['private'] 478 $this->linkDb['1']['private']
435 ); 479 );
436 } 480 }
437 481
@@ -480,11 +524,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
480 $this->assertEquals(0, count_private($this->linkDb)); 524 $this->assertEquals(0, count_private($this->linkDb));
481 $this->assertEquals( 525 $this->assertEquals(
482 'tag1 tag2 tag3 private secret', 526 'tag1 tag2 tag3 private secret',
483 $this->linkDb['20001010_105536']['tags'] 527 $this->linkDb['0']['tags']
484 ); 528 );
485 $this->assertEquals( 529 $this->assertEquals(
486 'tag1 tag2 tag3 public hello world', 530 'tag1 tag2 tag3 public hello world',
487 $this->linkDb['20160225_205548']['tags'] 531 $this->linkDb['1']['tags']
488 ); 532 );
489 } 533 }
490 534
@@ -507,16 +551,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
507 $this->assertEquals(0, count_private($this->linkDb)); 551 $this->assertEquals(0, count_private($this->linkDb));
508 $this->assertEquals( 552 $this->assertEquals(
509 'tag1&amp; tag2 &quot;tag3&quot; private secret', 553 'tag1&amp; tag2 &quot;tag3&quot; private secret',
510 $this->linkDb['20001010_105536']['tags'] 554 $this->linkDb['0']['tags']
511 ); 555 );
512 $this->assertEquals( 556 $this->assertEquals(
513 'tag1&amp; tag2 &quot;tag3&quot; public hello world', 557 'tag1&amp; tag2 &quot;tag3&quot; public hello world',
514 $this->linkDb['20160225_205548']['tags'] 558 $this->linkDb['1']['tags']
515 ); 559 );
516 } 560 }
517 561
518 /** 562 /**
519 * Ensure each imported bookmark has a unique linkdate 563 * Ensure each imported bookmark has a unique id
520 * 564 *
521 * See https://github.com/shaarli/Shaarli/issues/351 565 * See https://github.com/shaarli/Shaarli/issues/351
522 */ 566 */
@@ -531,16 +575,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
531 $this->assertEquals(3, count($this->linkDb)); 575 $this->assertEquals(3, count($this->linkDb));
532 $this->assertEquals(0, count_private($this->linkDb)); 576 $this->assertEquals(0, count_private($this->linkDb));
533 $this->assertEquals( 577 $this->assertEquals(
534 '20160225_205548', 578 0,
535 $this->linkDb['20160225_205548']['linkdate'] 579 $this->linkDb[0]['id']
536 ); 580 );
537 $this->assertEquals( 581 $this->assertEquals(
538 '20160225_205549', 582 1,
539 $this->linkDb['20160225_205549']['linkdate'] 583 $this->linkDb[1]['id']
540 ); 584 );
541 $this->assertEquals( 585 $this->assertEquals(
542 '20160225_205550', 586 2,
543 $this->linkDb['20160225_205550']['linkdate'] 587 $this->linkDb[2]['id']
544 ); 588 );
545 } 589 }
546} 590}
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index 0d0ad922..4948fe52 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -214,6 +214,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
214 $refDB = new ReferenceLinkDB(); 214 $refDB = new ReferenceLinkDB();
215 $refDB->write(self::$testDatastore); 215 $refDB->write(self::$testDatastore);
216 $linkDB = new LinkDB(self::$testDatastore, true, false); 216 $linkDB = new LinkDB(self::$testDatastore, true, false);
217
217 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 218 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
218 $updater = new Updater(array(), $linkDB, $this->conf, true); 219 $updater = new Updater(array(), $linkDB, $this->conf, true);
219 $updater->updateMethodRenameDashTags(); 220 $updater->updateMethodRenameDashTags();
@@ -287,4 +288,101 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
287 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); 288 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
288 unlink($sandbox .'.json.php'); 289 unlink($sandbox .'.json.php');
289 } 290 }
291
292 /**
293 * Test updateMethodDatastoreIds().
294 */
295 public function testDatastoreIds()
296 {
297 $links = array(
298 '20121206_182539' => array(
299 'linkdate' => '20121206_182539',
300 'title' => 'Geek and Poke',
301 'url' => 'http://geek-and-poke.com/',
302 'description' => 'desc',
303 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ',
304 'updated' => '20121206_190301',
305 'private' => false,
306 ),
307 '20121206_172539' => array(
308 'linkdate' => '20121206_172539',
309 'title' => 'UserFriendly - Samba',
310 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306',
311 'description' => '',
312 'tags' => 'samba cartoon web',
313 'private' => false,
314 ),
315 '20121206_142300' => array(
316 'linkdate' => '20121206_142300',
317 'title' => 'UserFriendly - Web Designer',
318 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206',
319 'description' => 'Naming conventions... #private',
320 'tags' => 'samba cartoon web',
321 'private' => true,
322 ),
323 );
324 $refDB = new ReferenceLinkDB();
325 $refDB->setLinks($links);
326 $refDB->write(self::$testDatastore);
327 $linkDB = new LinkDB(self::$testDatastore, true, false);
328
329 $checksum = hash_file('sha1', self::$testDatastore);
330
331 $this->conf->set('resource.data_dir', 'sandbox');
332 $this->conf->set('resource.datastore', self::$testDatastore);
333
334 $updater = new Updater(array(), $linkDB, $this->conf, true);
335 $this->assertTrue($updater->updateMethodDatastoreIds());
336
337 $linkDB = new LinkDB(self::$testDatastore, true, false);
338
339 $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php');
340 $backup = $backup[0];
341
342 $this->assertFileExists($backup);
343 $this->assertEquals($checksum, hash_file('sha1', $backup));
344 unlink($backup);
345
346 $this->assertEquals(3, count($linkDB));
347 $this->assertTrue(isset($linkDB[0]));
348 $this->assertFalse(isset($linkDB[0]['linkdate']));
349 $this->assertEquals(0, $linkDB[0]['id']);
350 $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']);
351 $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']);
352 $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
353 $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
354 $this->assertTrue($linkDB[0]['private']);
355 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']);
356
357 $this->assertTrue(isset($linkDB[1]));
358 $this->assertFalse(isset($linkDB[1]['linkdate']));
359 $this->assertEquals(1, $linkDB[1]['id']);
360 $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
361 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']);
362
363 $this->assertTrue(isset($linkDB[2]));
364 $this->assertFalse(isset($linkDB[2]['linkdate']));
365 $this->assertEquals(2, $linkDB[2]['id']);
366 $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
367 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']);
368 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']);
369 }
370
371 /**
372 * Test updateMethodDatastoreIds() with the update already applied: nothing to do.
373 */
374 public function testDatastoreIdsNothingToDo()
375 {
376 $refDB = new ReferenceLinkDB();
377 $refDB->write(self::$testDatastore);
378 $linkDB = new LinkDB(self::$testDatastore, true, false);
379
380 $this->conf->set('resource.data_dir', 'sandbox');
381 $this->conf->set('resource.datastore', self::$testDatastore);
382
383 $checksum = hash_file('sha1', self::$testDatastore);
384 $updater = new Updater(array(), $linkDB, $this->conf, true);
385 $this->assertTrue($updater->updateMethodDatastoreIds());
386 $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore));
387 }
290} 388}
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php
index 1f545c7d..6b7904dd 100644
--- a/tests/plugins/PluginIssoTest.php
+++ b/tests/plugins/PluginIssoTest.php
@@ -47,12 +47,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
47 $conf->set('plugins.ISSO_SERVER', 'value'); 47 $conf->set('plugins.ISSO_SERVER', 'value');
48 48
49 $str = 'http://randomstr.com/test'; 49 $str = 'http://randomstr.com/test';
50 $date = '20161118_100001';
50 $data = array( 51 $data = array(
51 'title' => $str, 52 'title' => $str,
52 'links' => array( 53 'links' => array(
53 array( 54 array(
55 'id' => 12,
54 'url' => $str, 56 'url' => $str,
55 'linkdate' => 'abc', 57 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
56 ) 58 )
57 ) 59 )
58 ); 60 );
@@ -65,7 +67,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
65 67
66 // plugin data 68 // plugin data
67 $this->assertEquals(1, count($data['plugin_end_zone'])); 69 $this->assertEquals(1, count($data['plugin_end_zone']));
68 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'abc')); 70 $this->assertNotFalse(strpos(
71 $data['plugin_end_zone'][0],
72 'data-isso-id="'. $data['links'][0]['id'] .'"'
73 ));
74 $this->assertNotFalse(strpos(
75 $data['plugin_end_zone'][0],
76 'data-title="'. $data['links'][0]['id'] .'"'
77 ));
69 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js')); 78 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
70 } 79 }
71 80
@@ -78,16 +87,20 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
78 $conf->set('plugins.ISSO_SERVER', 'value'); 87 $conf->set('plugins.ISSO_SERVER', 'value');
79 88
80 $str = 'http://randomstr.com/test'; 89 $str = 'http://randomstr.com/test';
90 $date1 = '20161118_100001';
91 $date2 = '20161118_100002';
81 $data = array( 92 $data = array(
82 'title' => $str, 93 'title' => $str,
83 'links' => array( 94 'links' => array(
84 array( 95 array(
96 'id' => 12,
85 'url' => $str, 97 'url' => $str,
86 'linkdate' => 'abc', 98 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
87 ), 99 ),
88 array( 100 array(
101 'id' => 13,
89 'url' => $str . '2', 102 'url' => $str . '2',
90 'linkdate' => 'abc2', 103 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
91 ), 104 ),
92 ) 105 )
93 ); 106 );
@@ -106,12 +119,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
106 $conf->set('plugins.ISSO_SERVER', 'value'); 119 $conf->set('plugins.ISSO_SERVER', 'value');
107 120
108 $str = 'http://randomstr.com/test'; 121 $str = 'http://randomstr.com/test';
122 $date = '20161118_100001';
109 $data = array( 123 $data = array(
110 'title' => $str, 124 'title' => $str,
111 'links' => array( 125 'links' => array(
112 array( 126 array(
127 'id' => 12,
113 'url' => $str, 128 'url' => $str,
114 'linkdate' => 'abc', 129 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
115 ) 130 )
116 ), 131 ),
117 'search_term' => $str 132 'search_term' => $str
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index 12bdda24..4a67b2dc 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -8,8 +8,8 @@ require_once 'application/Utils.php';
8require_once 'plugins/markdown/markdown.php'; 8require_once 'plugins/markdown/markdown.php';
9 9
10/** 10/**
11 * Class PlugQrcodeTest 11 * Class PluginMarkdownTest
12 * Unit test for the QR-Code plugin 12 * Unit test for the Markdown plugin
13 */ 13 */
14class PluginMarkdownTest extends PHPUnit_Framework_TestCase 14class PluginMarkdownTest extends PHPUnit_Framework_TestCase
15{ 15{
@@ -130,8 +130,11 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
130 )) 130 ))
131 ); 131 );
132 132
133 $data = hook_markdown_render_linklist($data); 133 $processed = hook_markdown_render_linklist($data);
134 $this->assertEquals($str, $data['links'][0]['description']); 134 $this->assertEquals($str, $processed['links'][0]['description']);
135
136 $processed = hook_markdown_render_feed($data);
137 $this->assertEquals($str, $processed['links'][0]['description']);
135 138
136 $data = array( 139 $data = array(
137 // Columns data 140 // Columns data
@@ -153,6 +156,37 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
153 } 156 }
154 157
155 /** 158 /**
159 * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`).
160 */
161 function testNoMarkdownNotExcactlyMatching()
162 {
163 $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
164 $data = array(
165 'links' => array(array(
166 'description' => $str,
167 'tags' => '.' . NO_MD_TAG,
168 'taglist' => array('.'. NO_MD_TAG),
169 ))
170 );
171
172 $data = hook_markdown_render_feed($data);
173 $this->assertContains('<em>', $data['links'][0]['description']);
174 }
175
176 /**
177 * Test hashtag links processed with markdown.
178 */
179 function testMarkdownHashtagLinks()
180 {
181 $md = file_get_contents('tests/plugins/resources/markdown.md');
182 $md = format_description($md);
183 $html = file_get_contents('tests/plugins/resources/markdown.html');
184
185 $data = process_markdown($md);
186 $this->assertEquals($html, $data);
187 }
188
189 /**
156 * Test hashtag links processed with markdown. 190 * Test hashtag links processed with markdown.
157 */ 191 */
158 function testMarkdownHashtagLinks() 192 function testMarkdownHashtagLinks()
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index abca4656..36d58c68 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,7 +4,7 @@
4 */ 4 */
5class ReferenceLinkDB 5class ReferenceLinkDB
6{ 6{
7 public static $NB_LINKS_TOTAL = 7; 7 public static $NB_LINKS_TOTAL = 8;
8 8
9 private $_links = array(); 9 private $_links = array();
10 private $_publicCount = 0; 10 private $_publicCount = 0;
@@ -16,66 +16,87 @@ class ReferenceLinkDB
16 public function __construct() 16 public function __construct()
17 { 17 {
18 $this->addLink( 18 $this->addLink(
19 41,
19 'Link title: @website', 20 'Link title: @website',
20 '?WDWyig', 21 '?WDWyig',
21 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag', 22 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
22 0, 23 0,
23 '20150310_114651', 24 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
24 'sTuff' 25 'sTuff',
26 null,
27 'WDWyig'
25 ); 28 );
26 29
27 $this->addLink( 30 $this->addLink(
31 42,
32 'Note: I have a big ID but an old date',
33 '?WDWyig',
34 'Used to test links reordering.',
35 0,
36 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'),
37 'ut'
38 );
39
40 $this->addLink(
41 8,
28 'Free as in Freedom 2.0 @website', 42 'Free as in Freedom 2.0 @website',
29 'https://static.fsf.org/nosvn/faif-2.0.pdf', 43 'https://static.fsf.org/nosvn/faif-2.0.pdf',
30 'Richard Stallman and the Free Software Revolution. Read this. #hashtag', 44 'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
31 0, 45 0,
32 '20150310_114633', 46 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
33 'free gnu software stallman -exclude stuff hashtag', 47 'free gnu software stallman -exclude stuff hashtag',
34 '20160803_093033' 48 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
35 ); 49 );
36 50
37 $this->addLink( 51 $this->addLink(
52 7,
38 'MediaGoblin', 53 'MediaGoblin',
39 'http://mediagoblin.org/', 54 'http://mediagoblin.org/',
40 'A free software media publishing platform #hashtagOther', 55 'A free software media publishing platform #hashtagOther',
41 0, 56 0,
42 '20130614_184135', 57 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
43 'gnu media web .hidden hashtag' 58 'gnu media web .hidden hashtag',
59 null,
60 'IuWvgA'
44 ); 61 );
45 62
46 $this->addLink( 63 $this->addLink(
64 6,
47 'w3c-markup-validator', 65 'w3c-markup-validator',
48 'https://dvcs.w3.org/hg/markup-validator/summary', 66 'https://dvcs.w3.org/hg/markup-validator/summary',
49 'Mercurial repository for the W3C Validator #private', 67 'Mercurial repository for the W3C Validator #private',
50 1, 68 1,
51 '20141125_084734', 69 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
52 'css html w3c web Mercurial' 70 'css html w3c web Mercurial'
53 ); 71 );
54 72
55 $this->addLink( 73 $this->addLink(
74 4,
56 'UserFriendly - Web Designer', 75 'UserFriendly - Web Designer',
57 'http://ars.userfriendly.org/cartoons/?id=20121206', 76 'http://ars.userfriendly.org/cartoons/?id=20121206',
58 'Naming conventions... #private', 77 'Naming conventions... #private',
59 0, 78 0,
60 '20121206_142300', 79 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
61 'dev cartoon web' 80 'dev cartoon web'
62 ); 81 );
63 82
64 $this->addLink( 83 $this->addLink(
84 1,
65 'UserFriendly - Samba', 85 'UserFriendly - Samba',
66 'http://ars.userfriendly.org/cartoons/?id=20010306', 86 'http://ars.userfriendly.org/cartoons/?id=20010306',
67 'Tropical printing', 87 'Tropical printing',
68 0, 88 0,
69 '20121206_172539', 89 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
70 'samba cartoon web' 90 'samba cartoon web'
71 ); 91 );
72 92
73 $this->addLink( 93 $this->addLink(
94 0,
74 'Geek and Poke', 95 'Geek and Poke',
75 'http://geek-and-poke.com/', 96 'http://geek-and-poke.com/',
76 '', 97 '',
77 1, 98 1,
78 '20121206_182539', 99 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
79 'dev cartoon tag1 tag2 tag3 tag4 ' 100 'dev cartoon tag1 tag2 tag3 tag4 '
80 ); 101 );
81 } 102 }
@@ -83,18 +104,20 @@ class ReferenceLinkDB
83 /** 104 /**
84 * Adds a new link 105 * Adds a new link
85 */ 106 */
86 protected function addLink($title, $url, $description, $private, $date, $tags, $updated = '') 107 protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '')
87 { 108 {
88 $link = array( 109 $link = array(
110 'id' => $id,
89 'title' => $title, 111 'title' => $title,
90 'url' => $url, 112 'url' => $url,
91 'description' => $description, 113 'description' => $description,
92 'private' => $private, 114 'private' => $private,
93 'linkdate' => $date,
94 'tags' => $tags, 115 'tags' => $tags,
116 'created' => $date,
95 'updated' => $updated, 117 'updated' => $updated,
118 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
96 ); 119 );
97 $this->_links[$date] = $link; 120 $this->_links[$id] = $link;
98 121
99 if ($private) { 122 if ($private) {
100 $this->_privateCount++; 123 $this->_privateCount++;
@@ -142,4 +165,14 @@ class ReferenceLinkDB
142 { 165 {
143 return $this->_links; 166 return $this->_links;
144 } 167 }
168
169 /**
170 * Setter to override link creation.
171 *
172 * @param array $links List of links.
173 */
174 public function setLinks($links)
175 {
176 $this->_links = $links;
177 }
145} 178}
diff --git a/tmp/.htaccess b/tmp/.htaccess
index b584d98c..f601c1ee 100644
--- a/tmp/.htaccess
+++ b/tmp/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/tpl/daily.html b/tpl/daily.html
index b82ad483..eba0af3b 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -49,13 +49,13 @@
49 {$link=$value} 49 {$link=$value}
50 <div class="dailyEntry"> 50 <div class="dailyEntry">
51 <div class="dailyEntryPermalink"> 51 <div class="dailyEntryPermalink">
52 <a href="?{$link.linkdate|smallHash}"> 52 <a href="?{$value.shorturl}">
53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink"> 53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
54 </a> 54 </a>
55 </div> 55 </div>
56 {if="!$hide_timestamps || isLoggedIn()"} 56 {if="!$hide_timestamps || isLoggedIn()"}
57 <div class="dailyEntryLinkdate"> 57 <div class="dailyEntryLinkdate">
58 <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a> 58 <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
59 </div> 59 </div>
60 {/if} 60 {/if}
61 {if="$link.tags"} 61 {if="$link.tags"}
diff --git a/tpl/editlink.html b/tpl/editlink.html
index 441b5302..870cc168 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -8,13 +8,18 @@
8{elseif="$link.description==''"}onload="document.linkform.lf_description.focus();" 8{elseif="$link.description==''"}onload="document.linkform.lf_description.focus();"
9{else}onload="document.linkform.lf_tags.focus();"{/if} > 9{else}onload="document.linkform.lf_tags.focus();"{/if} >
10<div id="pageheader"> 10<div id="pageheader">
11 {if="$source !== 'firefoxsocialapi'"} 11 {if="$source !== 'firefoxsocialapi'"}
12 {include="page.header"} 12 {include="page.header"}
13 {/if} 13 {else}
14 <div id="editlinkform"> 14 <div id="shaarli_title"><a href="{$titleLink}">{$shaarlititle}</a></div>
15 <form method="post" name="linkform"> 15 {/if}
16 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> 16 <div id="editlinkform">
17 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br> 17 <form method="post" name="linkform">
18 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
19 {if="isset($link.id)"}
20 <input type="hidden" name="lf_id" value="{$link.id}">
21 {/if}
22 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br>
18 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br> 23 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br>
19 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br> 24 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br>
20 <label for="lf_tags"><i>Tags</i></label><br> 25 <label for="lf_tags"><i>Tags</i></label><br>
@@ -25,20 +30,20 @@
25 {$value} 30 {$value}
26 {/loop} 31 {/loop}
27 32
28 {if="($link_is_new && $default_private_links) || $link.private == true"} 33 {if="($link_is_new && $default_private_links) || $link.private == true"}
29 <input type="checkbox" checked="checked" name="lf_private" id="lf_private"> 34 <input type="checkbox" checked="checked" name="lf_private" id="lf_private">
30 &nbsp;<label for="lf_private"><i>Private</i></label><br> 35 &nbsp;<label for="lf_private"><i>Private</i></label><br>
31 {else} 36 {else}
32 <input type="checkbox" name="lf_private" id="lf_private"> 37 <input type="checkbox" name="lf_private" id="lf_private">
33 &nbsp;<label for="lf_private"><i>Private</i></label><br> 38 &nbsp;<label for="lf_private"><i>Private</i></label><br>
34 {/if} 39 {/if}
35 <input type="submit" value="Save" name="save_edit" class="bigbutton"> 40 <input type="submit" value="Save" name="save_edit" class="bigbutton">
36 <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton"> 41 <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton">
37 {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if} 42 {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if}
38 <input type="hidden" name="token" value="{$token}"> 43 <input type="hidden" name="token" value="{$token}">
39 {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} 44 {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if}
40 </form> 45 </form>
41 </div> 46 </div>
42</div> 47</div>
43{if="$source !== 'firefoxsocialapi'"} 48{if="$source !== 'firefoxsocialapi'"}
44{include="page.footer"} 49{include="page.footer"}
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html
index 40fd421a..aead0459 100644
--- a/tpl/feed.atom.html
+++ b/tpl/feed.atom.html
@@ -30,9 +30,7 @@
30 <published>{$value.pub_iso_date}</published> 30 <published>{$value.pub_iso_date}</published>
31 <updated>{$value.up_iso_date}</updated> 31 <updated>{$value.up_iso_date}</updated>
32 {/if} 32 {/if}
33 <content type="html" xml:lang="{$language}"> 33 <content type="html" xml:lang="{$language}"><![CDATA[{$value.description}]]></content>
34 <![CDATA[{$value.description}]]>
35 </content>
36 {loop="$value.taglist"} 34 {loop="$value.taglist"}
37 <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" /> 35 <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
38 {/loop} 36 {/loop}
diff --git a/tpl/includes.html b/tpl/includes.html
index f94ce1be..7b2997ce 100644
--- a/tpl/includes.html
+++ b/tpl/includes.html
@@ -2,6 +2,7 @@
2<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 2<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
3<meta name="format-detection" content="telephone=no" /> 3<meta name="format-detection" content="telephone=no" />
4<meta name="viewport" content="width=device-width,initial-scale=1.0" /> 4<meta name="viewport" content="width=device-width,initial-scale=1.0" />
5<meta name="referrer" content="same-origin">
5<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" />
6<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> 7<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
7<link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" /> 8<link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" />
@@ -11,4 +12,4 @@
11{loop="$plugins_includes.css_files"} 12{loop="$plugins_includes.css_files"}
12<link type="text/css" rel="stylesheet" href="{$value}#"/> 13<link type="text/css" rel="stylesheet" href="{$value}#"/>
13{/loop} 14{/loop}
14<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/> \ No newline at end of file 15<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/>
diff --git a/tpl/linklist.html b/tpl/linklist.html
index 70c9cf79..0f1a5e8c 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -29,10 +29,8 @@
29 </form> 29 </form>
30 {loop="$plugins_header.fields_toolbar"} 30 {loop="$plugins_header.fields_toolbar"}
31 <form 31 <form
32 {loop="$value"} 32 {loop="$value.attr"}
33 {if="$key!='inputs'"} 33 {$key}="{$value}"
34 {$key}="{$value}"
35 {/if}
36 {/loop}> 34 {/loop}>
37 {loop="$value.inputs"} 35 {loop="$value.inputs"}
38 <input 36 <input
@@ -83,12 +81,13 @@
83 {if="isLoggedIn()"} 81 {if="isLoggedIn()"}
84 <div class="linkeditbuttons"> 82 <div class="linkeditbuttons">
85 <form method="GET" class="buttoneditform"> 83 <form method="GET" class="buttoneditform">
86 <input type="hidden" name="edit_link" value="{$value.linkdate}"> 84 <input type="hidden" name="edit_link" value="{$value.id}">
87 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit"> 85 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
88 </form><br> 86 </form><br>
89 <form method="GET" class="buttoneditform"> 87 <form method="POST" class="buttoneditform">
88 <input type="hidden" name="lf_linkdate" value="{$value.id}">
90 <input type="hidden" name="token" value="{$token}"> 89 <input type="hidden" name="token" value="{$token}">
91 <input type="hidden" name="delete_link" value="{$value.linkdate}"> 90 <input type="hidden" name="delete_link">
92 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete" 91 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
93 class="button_delete" onClick="return confirmDeleteLink();"> 92 class="button_delete" onClick="return confirmDeleteLink();">
94 </form> 93 </form>
@@ -102,7 +101,7 @@
102 {if="!$hide_timestamps || isLoggedIn()"} 101 {if="!$hide_timestamps || isLoggedIn()"}
103 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'} 102 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
104 <span class="linkdate" title="Permalink"> 103 <span class="linkdate" title="Permalink">
105 <a href="?{$value.linkdate|smallHash}"> 104 <a href="?{$value.shorturl}">
106 <span title="{$updated}"> 105 <span title="{$updated}">
107 {function="strftime('%c', $value.timestamp)"} 106 {function="strftime('%c', $value.timestamp)"}
108 {if="$value.updated_timestamp"}*{/if} 107 {if="$value.updated_timestamp"}*{/if}
diff --git a/tpl/linklist.paging.html b/tpl/linklist.paging.html
index 42f58d0d..86019c01 100644
--- a/tpl/linklist.paging.html
+++ b/tpl/linklist.paging.html
@@ -15,10 +15,8 @@
15 {loop="$action_plugin"} 15 {loop="$action_plugin"}
16 <div class="paging_privatelinks"> 16 <div class="paging_privatelinks">
17 <a 17 <a
18 {loop="$value"} 18 {loop="$value.attr"}
19 {if="$key!='html'"} 19 {$key}="{$value}"
20 {$key}="{$value}"
21 {/if}
22 {/loop}> 20 {/loop}>
23 {$value.html} 21 {$value.html}
24 </a> 22 </a>
diff --git a/tpl/loginform.html b/tpl/loginform.html
index a49b42d3..84176385 100644
--- a/tpl/loginform.html
+++ b/tpl/loginform.html
@@ -2,7 +2,7 @@
2<html> 2<html>
3<head>{include="includes"}</head> 3<head>{include="includes"}</head>
4<body 4<body
5{if="ban_canLogin()"} 5{if="ban_canLogin($conf)"}
6 {if="empty($username)"} 6 {if="empty($username)"}
7 onload="document.loginform.login.focus();" 7 onload="document.loginform.login.focus();"
8 {else} 8 {else}
@@ -13,7 +13,7 @@
13 {include="page.header"} 13 {include="page.header"}
14 14
15 <div id="headerform"> 15 <div id="headerform">
16 {if="!ban_canLogin()"} 16 {if="!ban_canLogin($conf)"}
17 You have been banned from login after too many failed attempts. Try later. 17 You have been banned from login after too many failed attempts. Try later.
18 {else} 18 {else}
19 <form method="post" name="loginform"> 19 <form method="post" name="loginform">
diff --git a/tpl/page.header.html b/tpl/page.header.html
index 89879678..cce61ec4 100644
--- a/tpl/page.header.html
+++ b/tpl/page.header.html
@@ -36,10 +36,8 @@
36 <li><a href="?do=daily">Daily</a></li> 36 <li><a href="?do=daily">Daily</a></li>
37 {loop="$plugins_header.buttons_toolbar"} 37 {loop="$plugins_header.buttons_toolbar"}
38 <li><a 38 <li><a
39 {loop="$value"} 39 {loop="$value.attr"}
40 {if="$key!='html'"} 40 {$key}="{$value}"
41 {$key}="{$value}"
42 {/if}
43 {/loop}> 41 {/loop}>
44 {$value.html} 42 {$value.html}
45 </a></li> 43 </a></li>
diff --git a/tpl/pluginsadmin.html b/tpl/pluginsadmin.html
index 672f4993..ead1734e 100644
--- a/tpl/pluginsadmin.html
+++ b/tpl/pluginsadmin.html
@@ -38,11 +38,11 @@
38 <tr data-line="{$key}" data-order="{$counter}"> 38 <tr data-line="{$key}" data-order="{$counter}">
39 <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td> 39 <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td>
40 <td class="center"> 40 <td class="center">
41 <a href="#" 41 <a href="#" class="arrow"
42 onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));"> 42 onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));">
43 43
44 </a> 44 </a>
45 <a href="#" 45 <a href="#" class="arrow"
46 onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));"> 46 onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));">
47 47
48 </a> 48 </a>
diff --git a/tpl/tools.html b/tpl/tools.html
index 8e285f44..e06d239d 100644
--- a/tpl/tools.html
+++ b/tpl/tools.html
@@ -50,12 +50,15 @@
50 &nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a private Note (text post) to your Shaarli. 50 &nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a private Note (text post) to your Shaarli.
51 </span> 51 </span>
52 </a><br><br> 52 </a><br><br>
53
54 {if="$sslenabled"}
53 <a class="smallbutton" onclick="activateFirefoxSocial(this)"> 55 <a class="smallbutton" onclick="activateFirefoxSocial(this)">
54 <b>✚Add to Firefox social</b> 56 <b>✚Add to Firefox social</b>
55 </a> 57 </a>
56 <a href="#"> 58 <a href="#">
57 <span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span> 59 <span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span>
58 </a><br><br> 60 </a><br><br>
61 {/if}
59 62
60 {loop="$tools_plugin"} 63 {loop="$tools_plugin"}
61 {$value} 64 {$value}
@@ -64,6 +67,7 @@
64 <div class="clear"></div> 67 <div class="clear"></div>
65 68
66 <script> 69 <script>
70 {if="$sslenabled"}
67 function activateFirefoxSocial(node) { 71 function activateFirefoxSocial(node) {
68 var loc = location.href; 72 var loc = location.href;
69 var baseURL = loc.substring(0, loc.lastIndexOf("/")); 73 var baseURL = loc.substring(0, loc.lastIndexOf("/"));
@@ -87,7 +91,7 @@
87 var activate = new CustomEvent("ActivateSocialFeature"); 91 var activate = new CustomEvent("ActivateSocialFeature");
88 node.dispatchEvent(activate); 92 node.dispatchEvent(activate);
89 } 93 }
90 94 {/if}
91 function alertBookmarklet() { 95 function alertBookmarklet() {
92 alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...'); 96 alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');
93 return false; 97 return false;