aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--COPYING66
-rw-r--r--README.md89
-rw-r--r--cache/.htaccess2
-rw-r--r--data/.htaccess2
-rw-r--r--images/calendar.pngbin675 -> 650 bytes
-rw-r--r--images/delete_icon.pngbin150 -> 302 bytes
-rw-r--r--images/edit_icon.pngbin394 -> 1548 bytes
-rw-r--r--images/feed-icon-14x14.pngbin689 -> 658 bytes
-rw-r--r--images/floral_left.pngbin2468 -> 1284 bytes
-rw-r--r--images/floral_right.pngbin2447 -> 1309 bytes
-rw-r--r--images/private.pngbin650 -> 2636 bytes
-rw-r--r--images/private_16x16.pngbin439 -> 679 bytes
-rw-r--r--images/private_16x16_active.pngbin475 -> 648 bytes
-rw-r--r--images/qrcode.pngbin218 -> 321 bytes
-rw-r--r--images/squiggle.pngbin950 -> 684 bytes
-rw-r--r--images/squiggle2.pngbin898 -> 720 bytes
-rw-r--r--images/squiggle_closing.pngbin2878 -> 1244 bytes
-rw-r--r--images/tag_blue.pngbin586 -> 714 bytes
-rw-r--r--inc/qr.min.js14
-rw-r--r--inc/shaarli.css19
-rw-r--r--index.php355
-rw-r--r--pagecache/.htaccess2
-rw-r--r--shaarli_version.txt1
-rw-r--r--tmp/.htaccess2
-rw-r--r--tpl/configure.html3
-rw-r--r--tpl/import.html2
-rw-r--r--tpl/linklist.html3
-rw-r--r--tpl/page.header.html6
-rw-r--r--tpl/tagcloud.html2
-rw-r--r--tpl/tools.html2
31 files changed, 336 insertions, 239 deletions
diff --git a/.gitignore b/.gitignore
index bcd745e4..6452c2c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,7 @@ pagecache
7# Eclipse project files 7# Eclipse project files
8.settings 8.settings
9.buildpath 9.buildpath
10.project \ No newline at end of file 10.project
11
12# Ignore raintpl generated pages
13*.rtpl.php \ No newline at end of file
diff --git a/COPYING b/COPYING
index d9731d27..91ef0340 100644
--- a/COPYING
+++ b/COPYING
@@ -1,6 +1,52 @@
1Shaarli is distributed under the zlib/libpng License: 1Files: *
2License: zlib/libpng
3Copyright: (c) 2011-2014 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
4 (c) 2011-2014 Alexandre Alapetite <alexandre@alapetite.fr>
5 (c) 2011-2014 David Sferruzza <david.sferruzza@gmail.com>
6 (c) 2011-2014 Christophe HENRY <christophe.henry@sbgodin.fr>
7 (c) 2011-2014 Mathieu Chabanon <git@matchab.fr>
8 (c) 2011-2014 BoboTiG <bobotig@gmail.com>
9 (c) 2011-2014 Bronco <bronco@warriordudimanche.net>
10 (c) 2011-2014 Emilien Klein <emilien@klein.st>
11 (c) 2011-2014 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
12 (c) 2011-2014 Lionel Martin <renarddesmers@gmail.com>
13 (c) 2011-2014 lehollandaisvolant <levoltigeurhollandais@gmail.com>
14 (c) 2011-2014 timo van neerden <fire@lehollandaisvolant.net>
15 (c) 2011-2014 nodiscc <nodiscc@gmail.com>
16
17
18
19Files: images/calendar.png, images/edit_icon.png, images/feed-icon-14x14.png, images/private.png, images/private_16x16.png, images/private_16x16_active.png, images/qrcode.png, images/tag_blue.png
20License: CC-BY (http://creativecommons.org/licenses/by/3.0/)
21Copyright: (c) 2014 Yusuke Kamiyamane
22Source: http://p.yusukekamiyamane.com/
23
24Files: images/delete_icon.png
25License: CC-BY (http://creativecommons.org/licenses/by/3.0/)
26Copyright: (c) 2014 Designmodo
27Source: http://designmodo.com/linecons-free/
28
29
30Files: images/floral_left.png, images/floral_right.png, images/squiggle.png, images/squiggle2.png, images/squiggle_closing.png
31Licence: Public Domain
32Source: https://openclipart.org/people/j4p4n/j4p4n_ornimental_bookend_-_left.svg
33
34
35Files: images/Paper_texture_v5_by_bashcorpo_w1000.jpg
36Licence: Public Domain
37Source: http://bashcorpo.deviantart.com/art/Grungy-paper-texture-v-5-22966998
38
39Files: images/logo.png
40License: zlib/libpng
41Copyright: (c) 2011-2014 idleman idleman@idleman.fr
42
43Files: ins/qr.min.js
44License: GPLv3
45Copyright: (C) 2014 Alasdair Mercer, http://neocotic.com
46
47--------------------------------------------------------
48ZLIB/LIBPNG LICENSE
2 49
3Copyright (c) 2011 Sbastien SAUVAGE (sebsauvage.net)
4 50
5This software is provided 'as-is', without any express or implied warranty. 51This software is provided 'as-is', without any express or implied warranty.
6In no event will the authors be held liable for any damages arising from 52In no event will the authors be held liable for any damages arising from
@@ -19,3 +65,19 @@ freely, subject to the following restrictions:
19 not be misrepresented as being the original software. 65 not be misrepresented as being the original software.
20 66
21 3. This notice may not be removed or altered from any source distribution. 67 3. This notice may not be removed or altered from any source distribution.
68
69----------------------------------------------------
70GPLv3 LICENSE
71
72This program is free software: you can redistribute it and/or modify
73it under the terms of the GNU General Public License as published by
74the Free Software Foundation, either version 3 of the License, or
75(at your option) any later version.
76
77This program is distributed in the hope that it will be useful,
78but WITHOUT ANY WARRANTY; without even the implied warranty of
79MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
80GNU General Public License for more details.
81
82You should have received a copy of the GNU General Public License
83along with this program. If not, see <http://www.gnu.org/licenses/>. \ No newline at end of file
diff --git a/README.md b/README.md
index cff718cf..d0743783 100644
--- a/README.md
+++ b/README.md
@@ -1,71 +1,76 @@
1![Shaarli logo](http://sebsauvage.net/wiki/lib/exe/fetch.php?media=php:php_shaarli:php_shaarli_logo_inkscape_w600_transp-nq8.png) 1![Shaarli logo](https://cdn.mediacru.sh/W2NGCIHB3quT.png)
2 2
3Shaarli, the personal, minimalist, super-fast, no-database delicious clone. 3Shaarli, the personal, minimalist, super-fast, no-database delicious clone.
4 4
5You want to share the links you discover ? Shaarli is a minimalist delicious clone you can install on your own website. 5You want to share the links you discover ? Shaarli is a minimalist delicious clone you can install on your own website.
6It is designed to be personal (single-user), fast and handy. 6It is designed to be personal (single-user), fast and handy.
7 7
8 8
9Features: 9## Features:
10 10
11 * Minimalist design (simple is beautiful) 11 * Minimalist design (simple is beautiful)
12 * **FAST** 12 * **FAST**
13 * Dead-simple installation: Drop the files, open the page. No database required. 13 * Dead-simple installation: Drop the files, open the page. No database required.
14 * Easy to use: Single button in your browser to bookmark a page 14 * Easy to use: Single button in your browser to bookmark a page (**bookmarklet**)
15 * Save url, title, description (unlimited size). Classify links with tags (with autocomplete) 15 * Save **URL, title, description** (unlimited size).
16 * Tag renaming, merging and deletion. 16 * Classify, search and filter links with **tags**.
17 * Automatic thumbnails for various services (imgur, imageshack.us, flickr, youtube, vimeo, dailymotion…) 17 * Tag autocompletion, renaming, merging and deletion.
18 * Automatic conversion of URLs to clickable links in descriptions. Support for http/ftp/file/apt/magnet protocols. 18 * Save links as **public or private**
19 * Save links as public or private 19 * Browse links by page, filter by tag or use the **full text search engine**
20 * 1-clic access to your private links/notes 20 * **Tag cloud**
21 * Browse links by page, filter by tag or use the full text search engine 21 * **Picture wall** (which can be filtered by tag or text search)
22 * Permalinks (with QR-Code) for easy reference 22 * **“Daily”** Newspaper-like digest, browsable by day.
23 * RSS and ATOM feeds (which can be filtered by tag or text search) 23 * **Permalinks** (with QR-Code) for easy reference
24 * Tag cloud 24 * **RSS** and ATOM feeds
25 * Picture wall (which can be filtered by tag or text search) 25 * Can be filtered by tag or text search!
26 * “Links of the day” Newspaper-like digest, browsable by day. 26 * “Daily” RSS feed: Get each day a digest of all new links.
27 * “Daily” RSS feed: Get each day a digest of all new links. 27 * Can **import/export** Netscape bookmarks (for import/export from/to Firefox, Opera, Chrome, Delicious…)
28 * [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) protocol support 28 * Automatic **image/video thumbnails** for various services (imgur, imageshack.us, flickr, youtube, vimeo, dailymotion…)
29 * Support for http/ftp/file/apt/magnet protocol links
30 * URLs in descriptions are automatically converted to clickable links in descriptions
29 * Easy backup (Data stored in a single file) 31 * Easy backup (Data stored in a single file)
32 * 1-click access to your private links/notes
30 * Compact storage (1315 links stored in 150 kb) 33 * Compact storage (1315 links stored in 150 kb)
31 * Mobile browsers support 34 * Mobile browsers support
32 * Also works with javascript disabled 35 * Also works with javascript disabled
33 * Can import/export Netscape bookmarks (for import/export from/to Firefox, Opera, Chrome, Delicious…)
34 * Brute force protected login form 36 * Brute force protected login form
35 * Protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery), session cookie hijacking. 37 * [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) protocol support
36 * Automatic removal of annoying FeedBurner/Google FeedProxy parameters in URL (?utm_source…) 38 * Automatic removal of annoying FeedBurner/Google FeedProxy parameters in URL (?utm_source…)
37 * Shaarli is a bookmarking application, but you can use it for micro-blogging (like Twitter), a pastebin, an online notepad, a snippet repository, etc.
38 * You will be automatically notified by a discreet popup if a new version is available
39 * Pages are easy to customize (using CSS and simple RainTPL templates) 39 * Pages are easy to customize (using CSS and simple RainTPL templates)
40 * Protected against [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery), session cookie hijacking.
41 * You will be automatically notified by a discreet popup if a new version is available
42 * **Shaarli is a bookmarking application, but you can use it for micro-blogging (like Twitter), a pastebin, an online notepad, a snippet repository, etc. See [Usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)**
43
44## Links
45 * **[Wiki/documentation](https://github.com/shaarli/Shaarli/wiki)**
46 * [Bugs/Feature requests/Discussion](https://github.com/shaarli/Shaarli/issues/)
47
40 48
49## Installing
50Shaarli requires php 5.1
41 51
42Requires php 5.1 52 * Download the latest stable release from https://github.com/shaarli/Shaarli/releases
53 * Unpack the archive in a directory on your web server
54 * Visit this directory from a web browser.
55 * Choose login, password, timezone and page title. Save.
43 56
44More information on the project page: 57_To get the development version, download https://github.com/shaarli/Shaarli/archive/master.zip or `git clone https://github.com/shaarli/Shaarli`_
45http://sebsauvage.net/wiki/doku.php?id=php:shaarli
46 58
47------------------------------------------------------------------------------ 59## Upgrading
60Delete all files and directories except the `data` directory, then unzip the new version of Shaarli.
61You will not lose your links and you will not have to reconfigure it.
48 62
49Shaarli is distributed under the zlib/libpng License:
50 63
51Copyright (c) 2011 Sébastien SAUVAGE (sebsauvage.net) 64## Screenshots
52 65
53This software is provided 'as-is', without any express or implied warranty. 66[![](https://cdn.mediacru.sh/AjZc6-emICeO.png)](https://cdn.mediacru.sh/kE8SyD-PvGuC.png) [![](https://cdn.mediacru.sh/MfC-DzklMYs2.png)](https://cdn.mediacru.sh/iqTvO1-yP9pU.png) [![](https://cdn.mediacru.sh/dxmXskaubYcg.png)](https://cdn.mediacru.sh/mMoi31f94wdL.png) [![](https://cdn.mediacru.sh/-ptB2veFivBp.png)](https://cdn.mediacru.sh/GcoZPZmCZ-DR.png) [![](https://cdn.mediacru.sh/QmRdTAr8x427.png)](https://cdn.mediacru.sh/TDDujpMWT31q.png)
54In no event will the authors be held liable for any damages arising from
55the use of this software.
56 67
57Permission is granted to anyone to use this software for any purpose, 68## About
58including commercial applications, and to alter it and redistribute it
59freely, subject to the following restrictions:
60 69
61 1. The origin of this software must not be misrepresented; you must not 70Original Project page: http://sebsauvage.net/wiki/doku.php?id=php:shaarli
62 claim that you wrote the original software. If you use this software 71Shaarli is developed by [Sébastien SAUVAGE](http://sebsauvage.net) and [contributors](COPYING).
63 in a product, an acknowledgment in the product documentation would
64 be appreciated but is not required.
65 72
66 2. Altered source versions must be plainly marked as such, and must 73Shaarli is [Free Software](https://en.wikipedia.org/wiki/Free_software) distributed under the [zlib/libpng License](http://www.gzip.org/zlib/zlib_license.html)
67 not be misrepresented as being the original software.
68 74
69 3. This notice may not be removed or altered from any source distribution. 75This friendly fork is maintained by the community at https://github.com/shaarli/Shaarli
70 76
71------------------------------------------------------------------------------
diff --git a/cache/.htaccess b/cache/.htaccess
new file mode 100644
index 00000000..b584d98c
--- /dev/null
+++ b/cache/.htaccess
@@ -0,0 +1,2 @@
1Allow from none
2Deny from all
diff --git a/data/.htaccess b/data/.htaccess
new file mode 100644
index 00000000..b584d98c
--- /dev/null
+++ b/data/.htaccess
@@ -0,0 +1,2 @@
1Allow from none
2Deny from all
diff --git a/images/calendar.png b/images/calendar.png
index 65891385..81c74519 100644
--- a/images/calendar.png
+++ b/images/calendar.png
Binary files differ
diff --git a/images/delete_icon.png b/images/delete_icon.png
index 55e388b4..810b94d8 100644
--- a/images/delete_icon.png
+++ b/images/delete_icon.png
Binary files differ
diff --git a/images/edit_icon.png b/images/edit_icon.png
index 5cff5743..16c440c8 100644
--- a/images/edit_icon.png
+++ b/images/edit_icon.png
Binary files differ
diff --git a/images/feed-icon-14x14.png b/images/feed-icon-14x14.png
index b3c949d2..10161702 100644
--- a/images/feed-icon-14x14.png
+++ b/images/feed-icon-14x14.png
Binary files differ
diff --git a/images/floral_left.png b/images/floral_left.png
index 5a4ad89b..f09a861d 100644
--- a/images/floral_left.png
+++ b/images/floral_left.png
Binary files differ
diff --git a/images/floral_right.png b/images/floral_right.png
index cb7b201f..0dfb6112 100644
--- a/images/floral_right.png
+++ b/images/floral_right.png
Binary files differ
diff --git a/images/private.png b/images/private.png
index 5cea272a..1364b355 100644
--- a/images/private.png
+++ b/images/private.png
Binary files differ
diff --git a/images/private_16x16.png b/images/private_16x16.png
index d58c4823..8bb34d7d 100644
--- a/images/private_16x16.png
+++ b/images/private_16x16.png
Binary files differ
diff --git a/images/private_16x16_active.png b/images/private_16x16_active.png
index dd43baf2..af990d2c 100644
--- a/images/private_16x16_active.png
+++ b/images/private_16x16_active.png
Binary files differ
diff --git a/images/qrcode.png b/images/qrcode.png
index 99f25267..c2cfa476 100644
--- a/images/qrcode.png
+++ b/images/qrcode.png
Binary files differ
diff --git a/images/squiggle.png b/images/squiggle.png
index 9fd2129d..a6ce218c 100644
--- a/images/squiggle.png
+++ b/images/squiggle.png
Binary files differ
diff --git a/images/squiggle2.png b/images/squiggle2.png
index 23409ce3..c795f0a3 100644
--- a/images/squiggle2.png
+++ b/images/squiggle2.png
Binary files differ
diff --git a/images/squiggle_closing.png b/images/squiggle_closing.png
index 901fff5f..3f9d02b1 100644
--- a/images/squiggle_closing.png
+++ b/images/squiggle_closing.png
Binary files differ
diff --git a/images/tag_blue.png b/images/tag_blue.png
index 9757fc6e..7ec902fc 100644
--- a/images/tag_blue.png
+++ b/images/tag_blue.png
Binary files differ
diff --git a/inc/qr.min.js b/inc/qr.min.js
index 663ce930..19d704e1 100644
--- a/inc/qr.min.js
+++ b/inc/qr.min.js
@@ -1,9 +1,5 @@
1// [qr.js](http://neocotic.com/qr.js) 1.0.3 1/*! qr-js v1.1.3 | (c) 2014 Alasdair Mercer | GPL v3 License
2// (c) 2011 Alasdair Mercer 2jsqrencode | (c) 2010 tz@execpc.com | GPL v3 License
3// Freely distributable under the MIT license. 3*/
4// Based on jsqrencode 4!function(a){"use strict";function b(){return T?new r:a.document.createElement("canvas")}function c(){return T?new x:a.document.createElement("img")}function d(b,c,d){var e=c.mime||B;a.location.href=b.toDataURL(e).replace(e,C),"function"==typeof d&&d()}function e(a){return"string"==typeof a&&(a={value:a}),a||{}}function f(a){function b(b){a[b]=function(){throw new Error(b+" requires HTML5 canvas element support")}}var c,d=["canvas","image","save","saveSync","toDataURL"];for(c=0;c<d.length;c++)b(d[c])}function g(a,b,c){function d(){w.write(e,f,0,f.length,0,function(a){w.close(e),c(a)})}if("string"!=typeof b.path)return c(new TypeError("Invalid path type: "+typeof b.path));var e,f;a.toBuffer(function(a,b){return a?c(a):(f=b,void(e&&d()))}),w.open(b.path,"w",N,function(a,b){return a?c(a):(e=b,void(f&&d()))})}function h(a,b){if("string"!=typeof b.path)throw new TypeError("Invalid path type: "+typeof b.path);var c=a.toBuffer(),d=w.openSync(b.path,"w",N);try{w.writeSync(d,c,0,c.length,0)}finally{w.closeSync(d)}}function i(a,b){var c;a>b&&(c=a,a=b,b=c),c=b,c*=b,c+=b,c>>=1,c+=a,S[c]=1}function j(a,b){var c;for(R[a+z*b]=1,c=-2;2>c;c++)R[a+c+z*(b-2)]=1,R[a-2+z*(b+c+1)]=1,R[a+2+z*(b+c)]=1,R[a+c+1+z*(b+2)]=1;for(c=0;2>c;c++)i(a-1,b+c),i(a+1,b-c),i(a-c,b-1),i(a+c,b+1)}function k(a){for(;a>=255;)a-=255,a=(a>>8)+(255&a);return a}function l(a,b,c,d){var e,f,g;for(f=0;d>f;f++)W[c+f]=0;for(f=0;b>f;f++){if(e=H[W[a+f]^W[c]],255!==e)for(g=1;d>g;g++)W[c+g-1]=W[c+g]^G[k(e+U[d-g])];else for(g=c;c+d>g;g++)W[g]=W[g+1];W[c+d-1]=255===e?0:G[k(e+U[0])]}}function m(a,b){var c;return a>b&&(c=a,a=b,b=c),c=b,c+=b*b,c>>=1,c+=a,1===S[c]}function n(a){var b,c,d,e;switch(a){case 0:for(c=0;z>c;c++)for(b=0;z>b;b++)b+c&1||m(b,c)||(R[b+c*z]^=1);break;case 1:for(c=0;z>c;c++)for(b=0;z>b;b++)1&c||m(b,c)||(R[b+c*z]^=1);break;case 2:for(c=0;z>c;c++)for(d=0,b=0;z>b;b++,d++)3===d&&(d=0),d||m(b,c)||(R[b+c*z]^=1);break;case 3:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=e,b=0;z>b;b++,d++)3===d&&(d=0),d||m(b,c)||(R[b+c*z]^=1);break;case 4:for(c=0;z>c;c++)for(d=0,e=c>>1&1,b=0;z>b;b++,d++)3===d&&(d=0,e=!e),e||m(b,c)||(R[b+c*z]^=1);break;case 5:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(b&c&1)+!(!d|!e)||m(b,c)||(R[b+c*z]^=1);break;case 6:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(b&c&1)+(d&&d===e)&1||m(b,c)||(R[b+c*z]^=1);break;case 7:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(d&&d===e)+(b+c&1)&1||m(b,c)||(R[b+c*z]^=1)}}function o(a){var b,c=0;for(b=0;a>=b;b++)O[b]>=5&&(c+=I+O[b]-5);for(b=3;a-1>b;b+=2)O[b-2]===O[b+2]&&O[b+2]===O[b-1]&&O[b-1]===O[b+1]&&3*O[b-1]===O[b]&&(0===O[b-3]||b+3>a||3*O[b-3]>=4*O[b]||3*O[b+3]>=4*O[b])&&(c+=K);return c}function p(){var a,b,c,d,e,f,g,h,i;for(c=e=f=0,i=0;z-1>i;i++)for(h=0;z-1>h;h++)(R[h+z*i]&&R[h+1+z*i]&&R[h+z*(i+1)]&&R[h+1+z*(i+1)]||!(R[h+z*i]||R[h+1+z*i]||R[h+z*(i+1)]||R[h+1+z*(i+1)]))&&(c+=J);for(i=0;z>i;i++){for(O[0]=0,g=a=h=0;z>h;h++)(b=R[h+z*i])===a?O[g]++:O[++g]=1,a=b,e+=a?1:-1;c+=o(g)}for(0>e&&(e=-e),d=e,d+=d<<2,d<<=1;d>z*z;)d-=z*z,f++;for(c+=f*L,h=0;z>h;h++){for(O[0]=0,g=a=i=0;z>i;i++)(b=R[h+z*i])===a?O[g]++:O[++g]=1,a=b;c+=o(g)}return c}function q(a){var b,c,d,e,f,g,h,o;f=a.length,y=0;do if(y++,d=4*(Q-1)+16*(y-1),u=D[d++],v=D[d++],s=D[d++],t=D[d],d=s*(u+v)+v-3+(9>=y),d>=f)break;while(40>y);for(z=17+4*y,g=s+(s+t)*(u+v)+v,f=0;g>f;f++)P[f]=0;for(W=a.slice(0),f=0;z*z>f;f++)R[f]=0;for(f=0;(z*(z+1)+1)/2>f;f++)S[f]=0;for(f=0;3>f;f++){for(d=o=0,1===f&&(d=z-7),2===f&&(o=z-7),R[o+3+z*(d+3)]=1,h=0;6>h;h++)R[o+h+z*d]=1,R[o+z*(d+h+1)]=1,R[o+6+z*(d+h)]=1,R[o+h+1+z*(d+6)]=1;for(h=1;5>h;h++)i(o+h,d+1),i(o+1,d+h+1),i(o+5,d+h),i(o+h+1,d+5);for(h=2;4>h;h++)R[o+h+z*(d+2)]=1,R[o+2+z*(d+h+1)]=1,R[o+4+z*(d+h)]=1,R[o+h+1+z*(d+4)]=1}if(y>1)for(f=A[y],o=z-7;;){for(h=z-7;h>f-3&&(j(h,o),!(f>h));)h-=f;if(f+9>=o)break;o-=f,j(6,o),j(o,6)}for(R[8+z*(z-8)]=1,o=0;7>o;o++)i(7,o),i(z-8,o),i(7,o+z-7);for(h=0;8>h;h++)i(h,7),i(h+z-8,7),i(h,z-8);for(h=0;9>h;h++)i(h,8);for(h=0;8>h;h++)i(h+z-8,8),i(8,h);for(o=0;7>o;o++)i(8,o+z-7);for(h=0;z-14>h;h++)1&h?(i(8+h,6),i(6,8+h)):(R[8+h+6*z]=1,R[6+z*(8+h)]=1);if(y>6)for(f=M[y-7],d=17,h=0;6>h;h++)for(o=0;3>o;o++,d--)1&(d>11?y>>d-12:f>>d)?(R[5-h+z*(2-o+z-11)]=1,R[2-o+z-11+z*(5-h)]=1):(i(5-h,2-o+z-11),i(2-o+z-11,5-h));for(o=0;z>o;o++)for(h=0;o>=h;h++)R[h+z*o]&&i(h,o);for(g=W.length,b=0;g>b;b++)P[b]=W.charCodeAt(b);if(W=P.slice(0),h=s*(u+v)+v,g>=h-2&&(g=h-2,y>9&&g--),b=g,y>9){for(W[b+2]=0,W[b+3]=0;b--;)f=W[b],W[b+3]|=255&f<<4,W[b+2]=f>>4;W[2]|=255&g<<4,W[1]=g>>4,W[0]=64|g>>12}else{for(W[b+1]=0,W[b+2]=0;b--;)f=W[b],W[b+2]|=255&f<<4,W[b+1]=f>>4;W[1]|=255&g<<4,W[0]=64|g>>4}for(b=g+3-(10>y);h>b;)W[b++]=236,W[b++]=17;for(U[0]=1,b=0;t>b;b++){for(U[b+1]=1,c=b;c>0;c--)U[c]=U[c]?U[c-1]^G[k(H[U[c]]+b)]:U[c-1];U[0]=G[k(H[U[0]]+b)]}for(b=0;t>=b;b++)U[b]=H[U[b]];for(d=h,o=0,b=0;u>b;b++)l(o,s,d,t),o+=s,d+=t;for(b=0;v>b;b++)l(o,s+1,d,t),o+=s+1,d+=t;for(o=0,b=0;s>b;b++){for(c=0;u>c;c++)P[o++]=W[b+c*s];for(c=0;v>c;c++)P[o++]=W[u*s+b+c*(s+1)]}for(c=0;v>c;c++)P[o++]=W[u*s+b+c*(s+1)];for(b=0;t>b;b++)for(c=0;u+v>c;c++)P[o++]=W[h+b+c*t];for(W=P,h=o=z-1,d=g=1,e=(s+t)*(u+v)+v,b=0;e>b;b++)for(f=W[b],c=0;8>c;c++,f<<=1){128&f&&(R[h+z*o]=1);do g?h--:(h++,d?0!==o?o--:(h-=2,d=!d,6===h&&(h--,o=9)):o!==z-1?o++:(h-=2,d=!d,6===h&&(h--,o-=8))),g=!g;while(m(h,o))}for(W=R.slice(0),f=0,o=3e4,d=0;8>d&&(n(d),h=p(),o>h&&(o=h,f=d),7!==f);d++)R=W.slice(0);for(f!==d&&n(f),o=F[f+(Q-1<<3)],d=0;8>d;d++,o>>=1)1&o&&(R[z-1-d+8*z]=1,6>d?R[8+z*d]=1:R[8+z*(d+1)]=1);for(d=0;7>d;d++,o>>=1)1&o&&(R[8+z*(z-7+d)]=1,d?R[6-d+8*z]=1:R[7+8*z]=1);return R}var r,s,t,u,v,w,x,y,z,A=[0,11,15,19,23,27,31,16,18,20,22,24,26,28,20,22,24,24,26,28,28,22,24,24,26,26,28,28,24,24,26,26,26,28,28,24,26,26,26,28,28],B="image/png",C="image/octet-stream",D=[1,0,19,7,1,0,16,10,1,0,13,13,1,0,9,17,1,0,34,10,1,0,28,16,1,0,22,22,1,0,16,28,1,0,55,15,1,0,44,26,2,0,17,18,2,0,13,22,1,0,80,20,2,0,32,18,2,0,24,26,4,0,9,16,1,0,108,26,2,0,43,24,2,2,15,18,2,2,11,22,2,0,68,18,4,0,27,16,4,0,19,24,4,0,15,28,2,0,78,20,4,0,31,18,2,4,14,18,4,1,13,26,2,0,97,24,2,2,38,22,4,2,18,22,4,2,14,26,2,0,116,30,3,2,36,22,4,4,16,20,4,4,12,24,2,2,68,18,4,1,43,26,6,2,19,24,6,2,15,28,4,0,81,20,1,4,50,30,4,4,22,28,3,8,12,24,2,2,92,24,6,2,36,22,4,6,20,26,7,4,14,28,4,0,107,26,8,1,37,22,8,4,20,24,12,4,11,22,3,1,115,30,4,5,40,24,11,5,16,20,11,5,12,24,5,1,87,22,5,5,41,24,5,7,24,30,11,7,12,24,5,1,98,24,7,3,45,28,15,2,19,24,3,13,15,30,1,5,107,28,10,1,46,28,1,15,22,28,2,17,14,28,5,1,120,30,9,4,43,26,17,1,22,28,2,19,14,28,3,4,113,28,3,11,44,26,17,4,21,26,9,16,13,26,3,5,107,28,3,13,41,26,15,5,24,30,15,10,15,28,4,4,116,28,17,0,42,26,17,6,22,28,19,6,16,30,2,7,111,28,17,0,46,28,7,16,24,30,34,0,13,24,4,5,121,30,4,14,47,28,11,14,24,30,16,14,15,30,6,4,117,30,6,14,45,28,11,16,24,30,30,2,16,30,8,4,106,26,8,13,47,28,7,22,24,30,22,13,15,30,10,2,114,28,19,4,46,28,28,6,22,28,33,4,16,30,8,4,122,30,22,3,45,28,8,26,23,30,12,28,15,30,3,10,117,30,3,23,45,28,4,31,24,30,11,31,15,30,7,7,116,30,21,7,45,28,1,37,23,30,19,26,15,30,5,10,115,30,19,10,47,28,15,25,24,30,23,25,15,30,13,3,115,30,2,29,46,28,42,1,24,30,23,28,15,30,17,0,115,30,10,23,46,28,10,35,24,30,19,35,15,30,17,1,115,30,14,21,46,28,29,19,24,30,11,46,15,30,13,6,115,30,14,23,46,28,44,7,24,30,59,1,16,30,12,7,121,30,12,26,47,28,39,14,24,30,22,41,15,30,6,14,121,30,6,34,47,28,46,10,24,30,2,64,15,30,17,4,122,30,29,14,46,28,49,10,24,30,24,46,15,30,4,18,122,30,13,32,46,28,48,14,24,30,42,32,15,30,20,4,117,30,40,7,47,28,43,22,24,30,10,67,15,30,19,6,118,30,18,31,47,28,34,34,24,30,20,61,15,30],E={L:1,M:2,Q:3,H:4},F=[30660,29427,32170,30877,26159,25368,27713,26998,21522,20773,24188,23371,17913,16590,20375,19104,13663,12392,16177,14854,9396,8579,11994,11245,5769,5054,7399,6608,1890,597,3340,2107],G=[1,2,4,8,16,32,64,128,29,58,116,232,205,135,19,38,76,152,45,90,180,117,234,201,143,3,6,12,24,48,96,192,157,39,78,156,37,74,148,53,106,212,181,119,238,193,159,35,70,140,5,10,20,40,80,160,93,186,105,210,185,111,222,161,95,190,97,194,153,47,94,188,101,202,137,15,30,60,120,240,253,231,211,187,107,214,177,127,254,225,223,163,91,182,113,226,217,175,67,134,17,34,68,136,13,26,52,104,208,189,103,206,129,31,62,124,248,237,199,147,59,118,236,197,151,51,102,204,133,23,46,92,184,109,218,169,79,158,33,66,132,21,42,84,168,77,154,41,82,164,85,170,73,146,57,114,228,213,183,115,230,209,191,99,198,145,63,126,252,229,215,179,123,246,241,255,227,219,171,75,150,49,98,196,149,55,110,220,165,87,174,65,130,25,50,100,200,141,7,14,28,56,112,224,221,167,83,166,81,162,89,178,121,242,249,239,195,155,43,86,172,69,138,9,18,36,72,144,61,122,244,245,247,243,251,235,203,139,11,22,44,88,176,125,250,233,207,131,27,54,108,216,173,71,142,0],H=[255,0,1,25,2,50,26,198,3,223,51,238,27,104,199,75,4,100,224,14,52,141,239,129,28,193,105,248,200,8,76,113,5,138,101,47,225,36,15,33,53,147,142,218,240,18,130,69,29,181,194,125,106,39,249,185,201,154,9,120,77,228,114,166,6,191,139,98,102,221,48,253,226,152,37,179,16,145,34,136,54,208,148,206,143,150,219,189,241,210,19,92,131,56,70,64,30,66,182,163,195,72,126,110,107,58,40,84,250,133,186,61,202,94,155,159,10,21,121,43,78,212,229,172,115,243,167,87,7,112,192,247,140,128,99,13,103,74,222,237,49,197,254,24,227,165,153,119,38,184,180,124,17,68,146,217,35,32,137,46,55,63,209,91,149,188,207,205,144,135,151,178,220,252,190,97,242,86,211,171,20,42,93,158,132,60,57,83,71,109,65,162,31,45,67,216,183,123,164,118,196,23,73,236,127,12,111,246,108,161,59,82,41,157,85,170,251,96,134,177,187,204,62,90,203,89,95,176,156,169,160,81,11,245,22,235,122,117,44,215,79,174,213,233,230,231,173,232,116,214,244,234,168,80,88,175],I=3,J=3,K=40,L=10,M=[3220,1468,2713,1235,3062,1890,2119,1549,2344,2936,1117,2583,1330,2470,1667,2249,2028,3780,481,4011,142,3098,831,3445,592,2517,1776,2234,1951,2827,1070,2660,1345,3177],N=parseInt("0666",8),O=[],P=[],Q=1,R=[],S=[],T=!1,U=[],V=a.qr,W=[],X={VERSION:"1.1.3",canvas:function(a){a=e(a);var c=a.size>=1&&a.size<=10?a.size:4;c*=25;var d=a.canvas||b(),f=d.getContext("2d");f.canvas.width=c,f.canvas.height=c,f.fillStyle=a.background||"#fff",f.fillRect(0,0,c,c),Q=E[a.level&&a.level.toUpperCase()||"L"];var g=q(a.value||"");f.lineWidth=1;var h=c;h/=z,h=Math.floor(h),f.clearRect(0,0,c,c),f.fillStyle=a.background||"#fff",f.fillRect(0,0,h*(z+8),h*(z+8)),f.fillStyle=a.foreground||"#000";var i,j;for(i=0;z>i;i++)for(j=0;z>j;j++)g[j*z+i]&&f.fillRect(h*i,h*j,h,h);return d},image:function(a){a=e(a);var b=this.canvas(a),d=a.image||c();return d.src=b.toDataURL(a.mime||B),d.height=b.height,d.width=b.width,d},save:function(a,b,c){function f(a){h||(h=!0,c(a))}switch(a=e(a),typeof b){case"function":c=b,b=null;break;case"string":a.path=b}if("function"!=typeof c)throw new TypeError("Invalid callback type: "+typeof c);var h=!1,i=this.canvas(a);T?g(i,a,f):d(i,a,f)},saveSync:function(a,b){a=e(a),"string"==typeof b&&(a.path=b);var c=this.canvas(a);T?h(c,a):d(c,a)},toDataURL:function(a){return a=e(a),this.canvas(a).toDataURL(a.mime||B)},noConflict:function(){return a.qr=V,this}};"undefined"!=typeof exports?(T=!0,"undefined"!=typeof module&&module.exports&&(exports=module.exports=X),exports.qr=X,r=require("canvas"),x=r.Image,w=require("fs")):"function"==typeof define&&define.amd?define(function(){return X}):(a.HTMLCanvasElement||f(X),a.qr=X)}(this);
5// (c) 2010 tz@execpc.com 5//# sourceMappingURL=qr.min.map \ No newline at end of file
6// Licensed under the GPL Version 3 license.
7// For all details and documentation:
8// http://neocotic.com/qr.js
9(function(a){function Q(a){var c,h,i,j,k,m,n,u;k=a.length;C=0;do{C++;i=(r-1)*4+(C-1)*16;z=d[i++];A=d[i++];o=d[i++];p=d[i];i=o*(z+A)+A-3+(C<=9);if(k<=i)break}while(C<40);D=17+4*C;m=o+(o+p)*(z+A)+A;for(k=0;k<m;k++)q[k]=0;B=a.slice(0);for(k=0;k<D*D;k++)s[k]=0;for(k=0;k<(D*(D+1)+1)/2;k++)t[k]=0;for(k=0;k<3;k++){i=0;u=0;if(k===1)i=D-7;if(k===2)u=D-7;s[u+3+D*(i+3)]=1;for(n=0;n<6;n++){s[u+n+D*i]=1;s[u+D*(i+n+1)]=1;s[u+6+D*(i+n)]=1;s[u+n+1+D*(i+6)]=1}for(n=1;n<5;n++){I(u+n,i+1);I(u+1,i+n+1);I(u+5,i+n);I(u+n+1,i+5)}for(n=2;n<4;n++){s[u+n+D*(i+2)]=1;s[u+2+D*(i+n+1)]=1;s[u+4+D*(i+n)]=1;s[u+n+1+D*(i+4)]=1}}if(C>1){k=b[C];u=D-7;for(;;){n=D-7;while(n>k-3){J(n,u);if(n<k)break;n-=k}if(u<=k+9)break;u-=k;J(6,u);J(u,6)}}s[8+D*(D-8)]=1;for(u=0;u<7;u++){I(7,u);I(D-8,u);I(7,u+D-7)}for(n=0;n<8;n++){I(n,7);I(n+D-8,7);I(n,D-8)}for(n=0;n<9;n++)I(n,8);for(n=0;n<8;n++){I(n+D-8,8);I(8,n)}for(u=0;u<7;u++)I(8,u+D-7);for(n=0;n<D-14;n++){if(n&1){I(8+n,6);I(6,8+n)}else{s[8+n+D*6]=1;s[6+D*(8+n)]=1}}if(C>6){k=l[C-7];i=17;for(n=0;n<6;n++){for(u=0;u<3;u++,i--){if(1&(i>11?C>>i-12:k>>i)){s[5-n+D*(2-u+D-11)]=1;s[2-u+D-11+D*(5-n)]=1}else{I(5-n,2-u+D-11);I(2-u+D-11,5-n)}}}}for(u=0;u<D;u++){for(n=0;n<=u;n++){if(s[n+D*u])I(n,u)}}m=B.length;for(c=0;c<m;c++)q[c]=B.charCodeAt(c);B=q.slice(0);n=o*(z+A)+A;if(m>=n-2){m=n-2;if(C>9)m--}c=m;if(C>9){B[c+2]=0;B[c+3]=0;while(c--){k=B[c];B[c+3]|=255&k<<4;B[c+2]=k>>4}B[2]|=255&m<<4;B[1]=m>>4;B[0]=64|m>>12}else{B[c+1]=0;B[c+2]=0;while(c--){k=B[c];B[c+2]|=255&k<<4;B[c+1]=k>>4}B[1]|=255&m<<4;B[0]=64|m>>4}c=m+3-(C<10);while(c<n){B[c++]=236;B[c++]=17}x[0]=1;for(c=0;c<p;c++){x[c+1]=1;for(h=c;h>0;h--){x[h]=x[h]?x[h-1]^f[K(g[x[h]]+c)]:x[h-1]}x[0]=f[K(g[x[0]]+c)]}for(c=0;c<=p;c++)x[c]=g[x[c]];i=n;u=0;for(c=0;c<z;c++){L(u,o,i,p);u+=o;i+=p}for(c=0;c<A;c++){L(u,o+1,i,p);u+=o+1;i+=p}u=0;for(c=0;c<o;c++){for(h=0;h<z;h++){q[u++]=B[c+h*o]}for(h=0;h<A;h++){q[u++]=B[z*o+c+h*(o+1)]}}for(h=0;h<A;h++){q[u++]=B[z*o+c+h*(o+1)]}for(c=0;c<p;c++){for(h=0;h<z+A;h++){q[u++]=B[n+c+h*p]}}B=q;n=u=D-1;i=m=1;j=(o+p)*(z+A)+A;for(c=0;c<j;c++){k=B[c];for(h=0;h<8;h++,k<<=1){if(128&k)s[n+D*u]=1;do{if(m){n--}else{n++;if(i){if(u!==0){u--}else{n-=2;i=!i;if(n===6){n--;u=9}}}else{if(u!==D-1){u++}else{n-=2;i=!i;if(n===6){n--;u-=8}}}}m=!m}while(M(n,u))}}B=s.slice(0);k=0;u=3e4;for(i=0;i<8;i++){N(i);n=P();if(n<u){u=n;k=i}if(k===7)break;s=B.slice(0)}if(k!==i)N(k);u=e[k+(r-1<<3)];for(i=0;i<8;i++,u>>=1){if(u&1){s[D-1-i+D*8]=1;if(i<6){s[8+D*i]=1}else{s[8+D*(i+1)]=1}}}for(i=0;i<7;i++,u>>=1){if(u&1){s[8+D*(D-7+i)]=1;if(i){s[6-i+D*8]=1}else{s[7+D*8]=1}}}return s}function P(){var a,b,c,d,e,f,g=0,h=0,j=0;for(f=0;f<D-1;f++){for(e=0;e<D-1;e++){if(s[e+D*f]&&s[e+1+D*f]&&s[e+D*(f+1)]&&s[e+1+D*(f+1)]||!(s[e+D*f]||s[e+1+D*f]||s[e+D*(f+1)]||s[e+1+D*(f+1)])){g+=i}}}for(f=0;f<D;f++){m[0]=0;for(d=a=e=0;e<D;e++){if((b=s[e+D*f])===a){m[d]++}else{m[++d]=1}a=b;h+=a?1:-1}g+=O(d)}if(h<0)h=-h;c=h;c+=c<<2;c<<=1;while(c>D*D){c-=D*D;j++}g+=j*k;for(e=0;e<D;e++){m[0]=0;for(d=a=f=0;f<D;f++){if((b=s[e+D*f])===a){m[d]++}else{m[++d]=1}a=b}g+=O(d)}return g}function O(a){var b=0,c;for(c=0;c<=a;c++){if(m[c]>=5)b+=h+m[c]-5}for(c=3;c<a-1;c+=2){if(m[c-2]===m[c+2]&&m[c+2]===m[c-1]&&m[c-1]===m[c+1]&&m[c-1]*3===m[c]&&(m[c-3]===0||c+3>a||m[c-3]*3>=m[c]*4||m[c+3]*3>=m[c]*4)){b+=j}}return b}function N(a){var b,c,d,e;switch(a){case 0:for(c=0;c<D;c++){for(b=0;b<D;b++){if(!(b+c&1)&&!M(b,c)){s[b+c*D]^=1}}}break;case 1:for(c=0;c<D;c++){for(b=0;b<D;b++){if(!(c&1)&&!M(b,c))s[b+c*D]^=1}}break;case 2:for(c=0;c<D;c++){for(d=0,b=0;b<D;b++,d++){if(d===3)d=0;if(!d&&!M(b,c))s[b+c*D]^=1}}break;case 3:for(e=0,c=0;c<D;c++,e++){if(e===3)e=0;for(d=e,b=0;b<D;b++,d++){if(d===3)d=0;if(!d&&!M(b,c))s[b+c*D]^=1}}break;case 4:for(c=0;c<D;c++){for(d=0,e=c>>1&1,b=0;b<D;b++,d++){if(d===3){d=0;e=!e}if(!e&&!M(b,c))s[b+c*D]^=1}}break;case 5:for(e=0,c=0;c<D;c++,e++){if(e===3)e=0;for(d=0,b=0;b<D;b++,d++){if(d===3)d=0;if(!((b&c&1)+!(!d|!e))&&!M(b,c)){s[b+c*D]^=1}}}break;case 6:for(e=0,c=0;c<D;c++,e++){if(e===3)e=0;for(d=0,b=0;b<D;b++,d++){if(d===3)d=0;if(!((b&c&1)+(d&&d===e)&1)&&!M(b,c)){s[b+c*D]^=1}}}break;case 7:for(e=0,c=0;c<D;c++,e++){if(e===3)e=0;for(d=0,b=0;b<D;b++,d++){if(d===3)d=0;if(!((d&&d===e)+(b+c&1)&1)&&!M(b,c)){s[b+c*D]^=1}}}break}}function M(a,b){var c;if(a>b){c=a;a=b;b=c}c=b;c+=b*b;c>>=1;c+=a;return t[c]}function L(a,b,c,d){var e,h,i;for(h=0;h<d;h++)B[c+h]=0;for(h=0;h<b;h++){e=g[B[a+h]^B[c]];if(e!==255){for(i=1;i<d;i++){B[c+i-1]=B[c+i]^f[K(e+x[d-i])]}}else{for(i=c;i<c+d;i++){B[i]=B[i+1]}}B[c+d-1]=e===255?0:f[K(e+x[0])]}}function K(a){while(a>=255){a-=255;a=(a>>8)+(a&255)}return a}function J(a,b){var c;s[a+D*b]=1;for(c=-2;c<2;c++){s[a+c+D*(b-2)]=1;s[a-2+D*(b+c+1)]=1;s[a+2+D*(b+c)]=1;s[a+c+1+D*(b+2)]=1}for(c=0;c<2;c++){I(a-1,b+c);I(a+1,b-c);I(a-c,b-1);I(a+c,b+1)}}function I(a,b){var c;if(a>b){c=a;a=b;b=c}c=b;c*=b;c+=b;c>>=1;c+=a;t[c]=1}function H(a,b,c){try{var d=a.apply(c||this);if(typeof b==="function")return b(null,d);return d}catch(e){if(typeof b==="function")return b(e);throw e}}function G(){for(var a=arguments.length;a>=0;--a){if(typeof arguments[a]==="function")return arguments[a]}}function F(){return w?new v:a.document.createElement("img")}function E(){return w?new n:a.document.createElement("canvas")}var b=[0,11,15,19,23,27,31,16,18,20,22,24,26,28,20,22,24,24,26,28,28,22,24,24,26,26,28,28,24,24,26,26,26,28,28,24,26,26,26,28,28],c="image/octet-stream",d=[1,0,19,7,1,0,16,10,1,0,13,13,1,0,9,17,1,0,34,10,1,0,28,16,1,0,22,22,1,0,16,28,1,0,55,15,1,0,44,26,2,0,17,18,2,0,13,22,1,0,80,20,2,0,32,18,2,0,24,26,4,0,9,16,1,0,108,26,2,0,43,24,2,2,15,18,2,2,11,22,2,0,68,18,4,0,27,16,4,0,19,24,4,0,15,28,2,0,78,20,4,0,31,18,2,4,14,18,4,1,13,26,2,0,97,24,2,2,38,22,4,2,18,22,4,2,14,26,2,0,116,30,3,2,36,22,4,4,16,20,4,4,12,24,2,2,68,18,4,1,43,26,6,2,19,24,6,2,15,28,4,0,81,20,1,4,50,30,4,4,22,28,3,8,12,24,2,2,92,24,6,2,36,22,4,6,20,26,7,4,14,28,4,0,107,26,8,1,37,22,8,4,20,24,12,4,11,22,3,1,115,30,4,5,40,24,11,5,16,20,11,5,12,24,5,1,87,22,5,5,41,24,5,7,24,30,11,7,12,24,5,1,98,24,7,3,45,28,15,2,19,24,3,13,15,30,1,5,107,28,10,1,46,28,1,15,22,28,2,17,14,28,5,1,120,30,9,4,43,26,17,1,22,28,2,19,14,28,3,4,113,28,3,11,44,26,17,4,21,26,9,16,13,26,3,5,107,28,3,13,41,26,15,5,24,30,15,10,15,28,4,4,116,28,17,0,42,26,17,6,22,28,19,6,16,30,2,7,111,28,17,0,46,28,7,16,24,30,34,0,13,24,4,5,121,30,4,14,47,28,11,14,24,30,16,14,15,30,6,4,117,30,6,14,45,28,11,16,24,30,30,2,16,30,8,4,106,26,8,13,47,28,7,22,24,30,22,13,15,30,10,2,114,28,19,4,46,28,28,6,22,28,33,4,16,30,8,4,122,30,22,3,45,28,8,26,23,30,12,28,15,30,3,10,117,30,3,23,45,28,4,31,24,30,11,31,15,30,7,7,116,30,21,7,45,28,1,37,23,30,19,26,15,30,5,10,115,30,19,10,47,28,15,25,24,30,23,25,15,30,13,3,115,30,2,29,46,28,42,1,24,30,23,28,15,30,17,0,115,30,10,23,46,28,10,35,24,30,19,35,15,30,17,1,115,30,14,21,46,28,29,19,24,30,11,46,15,30,13,6,115,30,14,23,46,28,44,7,24,30,59,1,16,30,12,7,121,30,12,26,47,28,39,14,24,30,22,41,15,30,6,14,121,30,6,34,47,28,46,10,24,30,2,64,15,30,17,4,122,30,29,14,46,28,49,10,24,30,24,46,15,30,4,18,122,30,13,32,46,28,48,14,24,30,42,32,15,30,20,4,117,30,40,7,47,28,43,22,24,30,10,67,15,30,19,6,118,30,18,31,47,28,34,34,24,30,20,61,15,30],e=[30660,29427,32170,30877,26159,25368,27713,26998,21522,20773,24188,23371,17913,16590,20375,19104,13663,12392,16177,14854,9396,8579,11994,11245,5769,5054,7399,6608,1890,597,3340,2107],f=[1,2,4,8,16,32,64,128,29,58,116,232,205,135,19,38,76,152,45,90,180,117,234,201,143,3,6,12,24,48,96,192,157,39,78,156,37,74,148,53,106,212,181,119,238,193,159,35,70,140,5,10,20,40,80,160,93,186,105,210,185,111,222,161,95,190,97,194,153,47,94,188,101,202,137,15,30,60,120,240,253,231,211,187,107,214,177,127,254,225,223,163,91,182,113,226,217,175,67,134,17,34,68,136,13,26,52,104,208,189,103,206,129,31,62,124,248,237,199,147,59,118,236,197,151,51,102,204,133,23,46,92,184,109,218,169,79,158,33,66,132,21,42,84,168,77,154,41,82,164,85,170,73,146,57,114,228,213,183,115,230,209,191,99,198,145,63,126,252,229,215,179,123,246,241,255,227,219,171,75,150,49,98,196,149,55,110,220,165,87,174,65,130,25,50,100,200,141,7,14,28,56,112,224,221,167,83,166,81,162,89,178,121,242,249,239,195,155,43,86,172,69,138,9,18,36,72,144,61,122,244,245,247,243,251,235,203,139,11,22,44,88,176,125,250,233,207,131,27,54,108,216,173,71,142,0],g=[255,0,1,25,2,50,26,198,3,223,51,238,27,104,199,75,4,100,224,14,52,141,239,129,28,193,105,248,200,8,76,113,5,138,101,47,225,36,15,33,53,147,142,218,240,18,130,69,29,181,194,125,106,39,249,185,201,154,9,120,77,228,114,166,6,191,139,98,102,221,48,253,226,152,37,179,16,145,34,136,54,208,148,206,143,150,219,189,241,210,19,92,131,56,70,64,30,66,182,163,195,72,126,110,107,58,40,84,250,133,186,61,202,94,155,159,10,21,121,43,78,212,229,172,115,243,167,87,7,112,192,247,140,128,99,13,103,74,222,237,49,197,254,24,227,165,153,119,38,184,180,124,17,68,146,217,35,32,137,46,55,63,209,91,149,188,207,205,144,135,151,178,220,252,190,97,242,86,211,171,20,42,93,158,132,60,57,83,71,109,65,162,31,45,67,216,183,123,164,118,196,23,73,236,127,12,111,246,108,161,59,82,41,157,85,170,251,96,134,177,187,204,62,90,203,89,95,176,156,169,160,81,11,245,22,235,122,117,44,215,79,174,213,233,230,231,173,232,116,214,244,234,168,80,88,175],h=3,i=3,j=40,k=10,l=[3220,1468,2713,1235,3062,1890,2119,1549,2344,2936,1117,2583,1330,2470,1667,2249,2028,3780,481,4011,142,3098,831,3445,592,2517,1776,2234,1951,2827,1070,2660,1345,3177];var m=[],n,o,p,q=[],r=1,s=[],t=[],u,v,w=false,x=[],y=a.qr,z,A,B=[],C,D;var R={VERSION:"1.0.3",canvas:function(a,b){b=G(a,b);return H(function c(){switch(typeof a){case"object":break;case"string":a={value:a};break;default:a={};break}var b,c,d,e,f,g,h=4,i=25;if(a.size>=1&&a.size<=10)h=a.size;h*=i;b=a.canvas||E();c=b.getContext("2d");c.canvas.width=h;c.canvas.height=h;c.fillStyle=a.background||"#fff";c.fillRect(0,0,h,h);if(a.level){switch(a.level.toUpperCase()){case"L":r=1;break;case"M":r=2;break;case"Q":r=3;break;case"H":r=4;break}}g=Q(a.value||"");c.lineWidth=1;f=h;f/=D;f=Math.round(f-.5);c.clearRect(0,0,h,h);c.fillStyle=a.background||"#fff";c.fillRect(0,0,f*(D+8),f*(D+8));c.fillStyle=a.foreground||"#000";for(d=0;d<D;d++){for(e=0;e<D;e++){if(g[e*D+d])c.fillRect(f*d,f*e,f,f)}}return b},b,this)},image:function(a,b){b=G(a,b);return H(function c(){switch(typeof a){case"object":break;case"string":a={value:a};break;default:a={};break}var b=this.canvas(a),c=a.image||F();c.src=b.toDataURL();c.height=b.height;c.width=b.width;return c},b,this)},save:function(b,d,e){e=G(b,d,e);return H(function f(){switch(typeof b){case"object":if(typeof d==="string"&&!b.path)b.path=d;break;case"string":b={value:b};if(typeof d==="string")b.path=d;break;default:b={};break}var e=this.canvas(b);if(w){if(typeof b.path!=="string"){throw new TypeError("Invalid path type: "+typeof b.path)}var f,g,h=function i(){u.write(f,g,0,g.length,0,function a(a){u.close(f);if(a)throw a})};e.toBuffer(function j(a,b){if(a)throw a;g=b;if(f)h()});u.open(b.path,"w",438,function k(a,b){if(a)throw a;f=b;if(g)h()})}else{a.location.href=e.toDataURL().replace("image/png",c)}},e,this)},toDataURL:function(a,b){b=G(a,b);return H(function c(){return this.canvas(a).toDataURL()},b,this)},noConflict:function(b){return H(function c(){a.qr=y;return this},b,this)}};if(typeof exports!=="undefined"){w=true;if(typeof module!=="undefined"&&module.exports){exports=module.exports=R}exports.qr=R;n=require("canvas");v=n.Image;u=require("fs")}else if(typeof define==="function"&&define.amd){define("qr",function S(){return R})}else{if(!a.HTMLCanvasElement){R.canvas=function(){};R.image=function(){};R.save=function(){};R.toDataURL=function(){}}a.qr=R}})(this)
diff --git a/inc/shaarli.css b/inc/shaarli.css
index 52a48208..28394ed5 100644
--- a/inc/shaarli.css
+++ b/inc/shaarli.css
@@ -1,4 +1,4 @@
1/* CSS Stylsheet for Shaarli - http://sebsauvage.net/wiki/doku.php?id=php:shaarli */ 1/* Cascading Stylesheet for Shaarli - http://sebsauvage.net/wiki/doku.php?id=php:shaarli */
2 2
3/* CSS Reset from Yahoo to cope with browsers CSS inconsistencies. */ 3/* CSS Reset from Yahoo to cope with browsers CSS inconsistencies. */
4/* 4/*
@@ -7,7 +7,7 @@ version: 2.8.2r1
7*/ 7*/
8html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;} 8html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}
9 9
10body { font-family: "Trebuchet MS",Verdana,Arial,Helvetica,sans-serif; font-size:10pt; background-color: #ffffff; } 10body { font-family: "Trebuchet MS",Verdana,Arial,Helvetica,sans-serif; font-size:10pt; background-color: #ffffff; word-wrap: break-word; }
11input, textarea { 11input, textarea {
12 background-color: #dedede; 12 background-color: #dedede;
13 background: -webkit-gradient(linear, 0 0, 0 bottom, from(#dedede), to(#ffffff)); 13 background: -webkit-gradient(linear, 0 0, 0 bottom, from(#dedede), to(#ffffff));
@@ -232,14 +232,15 @@ cursor:pointer;
232.linktitle { font-size:14pt; font-weight:bold; } 232.linktitle { font-size:14pt; font-weight:bold; }
233.linktitle a { text-decoration: none; color:#80AD48; } 233.linktitle a { text-decoration: none; color:#80AD48; }
234.linktitle a:hover { color:#F57900; } 234.linktitle a:hover { color:#F57900; }
235.linkdate { font-size:8pt; color:#888; } 235.linkdate, .linkarchive { font-size:8pt; color:#888; }
236.linkdate a { background-image:url('../images/calendar.png');padding:2px 0 3px 20px;background-repeat:no-repeat;text-decoration: none; color:#E28E3F; } 236.linkdate a, .linkarchive a { color:#E28E3F; }
237.linkdate a:hover { color: #F57900 } 237.linkdate a {background-image:url('../images/calendar.png');padding:2px 0 3px 20px;background-repeat:no-repeat;text-decoration: none; }
238.linkdate a:hover, .linkarchive a:hover { color: #F57900 }
238.linkurl { font-size:8pt; color:#4BAA74; } 239.linkurl { font-size:8pt; color:#4BAA74; }
239.linkdescription { color:#000; margin-top:0; margin-bottom:12px; font-weight:normal; max-height:400px; overflow:auto; } 240.linkdescription { color:#000; margin-top:0; margin-bottom:12px; font-weight:normal; max-height:400px; overflow:auto; }
240.linkdescription a { text-decoration: none; color:#3465A4; } 241.linkdescription a { text-decoration: none; color:#3465A4; }
241.linkdescription a:hover { color:#F57900; } 242.linkdescription a:hover { color:#F57900; }
242.linktaglist { padding-top:10px;} 243.linktaglist { padding-top:10px; line-height:200%;}
243.linktag { 244.linktag {
244 245
245font-size:9pt; 246font-size:9pt;
@@ -408,13 +409,13 @@ div.dailyEntryDescription
408 overflow:auto; 409 overflow:auto;
409} 410}
410 411
411/* Common css screwdriver */ 412/* Common CSS screwdriver */
412.clear{ 413.clear{
413 clear:both; 414 clear:both;
414} 415}
415 416
416/* For lazy images loading in picture wall. 417/* For lazy images loading in picture wall.
417 using http://www.appelsiini.net/projects/lazyload 418 Using http://www.appelsiini.net/projects/lazyload
418 */ 419 */
419.lazyimage { display:none; } 420.lazyimage { display:none; }
420 421
@@ -451,7 +452,7 @@ a {color:#000!important;text-decoration:none!important;}
451#searchform_value { width:70% !important; } 452#searchform_value { width:70% !important; }
452#tagfilter_value { width:70% !important; } 453#tagfilter_value { width:70% !important; }
453div.qrcode { position:relative; float:left; top:-10px; left:0px; } 454div.qrcode { position:relative; float:left; top:-10px; left:0px; }
454#paging_privatelinks { float;none; } 455#paging_privatelinks { float:none; }
455#paging_linksperpage { float:none; margin-bottom:10px; font-size:smaller;} 456#paging_linksperpage { float:none; margin-bottom:10px; font-size:smaller;}
456#paging_older,#paging_newer,#paging_linksperpage a { border: 1px solid black; padding:3px 5px 3px 5px; background-color:#666; color:#fff; border-radius: 5px 5px 5px 5px;} 457#paging_older,#paging_newer,#paging_linksperpage a { border: 1px solid black; padding:3px 5px 3px 5px; background-color:#666; color:#fff; border-radius: 5px 5px 5px 5px;}
457.thumbnail { float:none; height:auto; margin: 0px; text-align:center;} 458.thumbnail { float:none; height:auto; margin: 0px; text-align:center;}
diff --git a/index.php b/index.php
index 0465a4e5..1d523a33 100644
--- a/index.php
+++ b/index.php
@@ -1,9 +1,9 @@
1<?php 1<?php
2// Shaarli 0.0.41 beta - Shaare your links... 2// Shaarli 0.0.42 beta - Shaare your links...
3// The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net 3// The personal, minimalist, super-fast, no-database Delicious clone. By sebsauvage.net
4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli 4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli
5// Licence: http://www.opensource.org/licenses/zlib-license.php 5// Licence: http://www.opensource.org/licenses/zlib-license.php
6// Requires: php 5.1.x (but autocomplete fields will only work if you have php 5.2.x) 6// Requires: PHP 5.1.x (but autocomplete fields will only work if you have PHP 5.2.x)
7// ----------------------------------------------------------------------------------------------- 7// -----------------------------------------------------------------------------------------------
8// NEVER TRUST IN PHP.INI 8// NEVER TRUST IN PHP.INI
9// Some hosts do not define a default timezone in php.ini, 9// Some hosts do not define a default timezone in php.ini,
@@ -21,22 +21,28 @@ $GLOBALS['config']['BAN_AFTER'] = 4; // Ban IP after this many failures.
21$GLOBALS['config']['BAN_DURATION'] = 1800; // Ban duration for IP address after login failures (in seconds) (1800 sec. = 30 minutes) 21$GLOBALS['config']['BAN_DURATION'] = 1800; // Ban duration for IP address after login failures (in seconds) (1800 sec. = 30 minutes)
22$GLOBALS['config']['OPEN_SHAARLI'] = false; // If true, anyone can add/edit/delete links without having to login 22$GLOBALS['config']['OPEN_SHAARLI'] = false; // If true, anyone can add/edit/delete links without having to login
23$GLOBALS['config']['HIDE_TIMESTAMPS'] = false; // If true, the moment when links were saved are not shown to users that are not logged in. 23$GLOBALS['config']['HIDE_TIMESTAMPS'] = false; // If true, the moment when links were saved are not shown to users that are not logged in.
24$GLOBALS['config']['SHOW_ATOM'] = false; // If true, an extra "ATOM feed" button will be displayed in the toolbar
24$GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links. 25$GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links.
25$GLOBALS['config']['CACHEDIR'] = 'cache'; // Cache directory for thumbnails for SLOW services (like flickr) 26$GLOBALS['config']['CACHEDIR'] = 'cache'; // Cache directory for thumbnails for SLOW services (like flickr)
26$GLOBALS['config']['PAGECACHE'] = 'pagecache'; // Page cache directory. 27$GLOBALS['config']['PAGECACHE'] = 'pagecache'; // Page cache directory.
27$GLOBALS['config']['ENABLE_LOCALCACHE'] = true; // Enable Shaarli to store thumbnail in a local cache. Disable to reduce webspace usage. 28$GLOBALS['config']['ENABLE_LOCALCACHE'] = true; // Enable Shaarli to store thumbnail in a local cache. Disable to reduce web space usage.
28$GLOBALS['config']['PUBSUBHUB_URL'] = ''; // PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable. 29$GLOBALS['config']['PUBSUBHUB_URL'] = ''; // PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable.
30$GLOBALS['config']['RAINTPL_TMP'] = 'tmp/' ; // Raintpl cache directory (keep the trailing slash!)
31$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/' ; // Raintpl template directory (keep the trailing slash!)
29$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli. 32$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli.
30$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours 33$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours
31 // Note: You must have publisher.php in the same directory as Shaarli index.php 34 // Note: You must have publisher.php in the same directory as Shaarli index.php
35$GLOBALS['config']['ARCHIVE_ORG'] = false; // For each link, add a link to an archived version on archive.org
32// ----------------------------------------------------------------------------------------------- 36// -----------------------------------------------------------------------------------------------
33// You should not touch below (or at your own risks !) 37// You should not touch below (or at your own risks!)
34// Optionnal config file. 38// Optional config file.
35if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php'); 39if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
36 40
37define('shaarli_version','0.0.41 beta'); 41define('shaarli_version','0.0.42 beta');
38define('PHPPREFIX','<?php /* '); // Prefix to encapsulate data in php code. 42define('PHPPREFIX','<?php /* '); // Prefix to encapsulate data in PHP code.
39define('PHPSUFFIX',' */ ?>'); // Suffix to encapsulate data in php code. 43define('PHPSUFFIX',' */ ?>'); // Suffix to encapsulate data in PHP code.
44// http://server.com/x/shaarli --> /shaarli/
45define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
40 46
41// Force cookie path (but do not change lifetime) 47// Force cookie path (but do not change lifetime)
42$cookie=session_get_cookie_params(); 48$cookie=session_get_cookie_params();
@@ -46,8 +52,8 @@ session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['HTTP_HOST']);
46// Set session parameters on server side. 52// Set session parameters on server side.
47define('INACTIVITY_TIMEOUT',3600); // (in seconds). If the user does not access any page within this time, his/her session is considered expired. 53define('INACTIVITY_TIMEOUT',3600); // (in seconds). If the user does not access any page within this time, his/her session is considered expired.
48ini_set('session.use_cookies', 1); // Use cookies to store session. 54ini_set('session.use_cookies', 1); // Use cookies to store session.
49ini_set('session.use_only_cookies', 1); // Force cookies for session (phpsessionID forbidden in URL) 55ini_set('session.use_only_cookies', 1); // Force cookies for session (phpsessionID forbidden in URL).
50ini_set('session.use_trans_sid', false); // Prevent php to use sessionID in URL if cookies are disabled. 56ini_set('session.use_trans_sid', false); // Prevent PHP form using sessionID in URL if cookies are disabled.
51session_name('shaarli'); 57session_name('shaarli');
52if (session_id() == '') session_start(); // Start session if needed (Some server auto-start sessions). 58if (session_id() == '') session_start(); // Start session if needed (Some server auto-start sessions).
53 59
@@ -61,9 +67,8 @@ error_reporting(E_ALL^E_WARNING); // See all error except warnings.
61//error_reporting(-1); // See all errors (for debugging only) 67//error_reporting(-1); // See all errors (for debugging only)
62 68
63include "inc/rain.tpl.class.php"; //include Rain TPL 69include "inc/rain.tpl.class.php"; //include Rain TPL
64raintpl::$tpl_dir = "tpl/"; // template directory 70raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
65if (!is_dir('tmp')) { mkdir('tmp',0705); chmod('tmp',0705); } 71raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory
66raintpl::$cache_dir = "tmp/"; // cache directory
67 72
68ob_start(); // Output buffering for the page cache. 73ob_start(); // Output buffering for the page cache.
69 74
@@ -83,18 +88,8 @@ header("Cache-Control: no-store, no-cache, must-revalidate");
83header("Cache-Control: post-check=0, pre-check=0", false); 88header("Cache-Control: post-check=0, pre-check=0", false);
84header("Pragma: no-cache"); 89header("Pragma: no-cache");
85 90
86// Directories creations (Note that your web host may require differents rights than 705.) 91// Directories creations (Note that your web host may require different rights than 705.)
87if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory ('.realpath(dirname(__FILE__)).').</pre>'); 92if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory ('.realpath(dirname(__FILE__)).').</pre>');
88if (!is_dir($GLOBALS['config']['DATADIR'])) { mkdir($GLOBALS['config']['DATADIR'],0705); chmod($GLOBALS['config']['DATADIR'],0705); }
89if (!is_dir('tmp')) { mkdir('tmp',0705); chmod('tmp',0705); } // For RainTPL temporary files.
90if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['DATADIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
91// Second check to see if Shaarli can write in its directory, because on some hosts is_writable() is not reliable.
92if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) die('<pre>ERROR: Shaarli does not have the right to write in its data directory ('.realpath($GLOBALS['config']['DATADIR']).').</pre>');
93if ($GLOBALS['config']['ENABLE_LOCALCACHE'])
94{
95 if (!is_dir($GLOBALS['config']['CACHEDIR'])) { mkdir($GLOBALS['config']['CACHEDIR'],0705); chmod($GLOBALS['config']['CACHEDIR'],0705); }
96 if (!is_file($GLOBALS['config']['CACHEDIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['CACHEDIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
97}
98 93
99// Handling of old config file which do not have the new parameters. 94// Handling of old config file which do not have the new parameters.
100if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(indexUrl()); 95if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(indexUrl());
@@ -103,6 +98,7 @@ if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
103if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false; 98if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
104if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false; 99if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false;
105if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false; 100if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
101if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
106// I really need to rewrite Shaarli with a proper configuation manager. 102// I really need to rewrite Shaarli with a proper configuation manager.
107 103
108// Run config screen if first run: 104// Run config screen if first run:
@@ -110,17 +106,19 @@ if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install();
110 106
111require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS. 107require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
112 108
109// a token depending of deployment salt, user password, and the current ip
110define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GLOBALS['salt']));
113 111
114autoLocale(); // Sniff browser language and set date format accordingly. 112autoLocale(); // Sniff browser language and set date format accordingly.
115header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling. 113header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling.
116 114
117// Check php version 115// Check PHP version
118function checkphpversion() 116function checkphpversion()
119{ 117{
120 if (version_compare(PHP_VERSION, '5.1.0') < 0) 118 if (version_compare(PHP_VERSION, '5.1.0') < 0)
121 { 119 {
122 header('Content-Type: text/plain; charset=utf-8'); 120 header('Content-Type: text/plain; charset=utf-8');
123 echo 'Your server supports php '.PHP_VERSION.'. Shaarli requires at least php 5.1.0, and thus cannot run. Sorry.'; 121 echo 'Your server supports PHP '.PHP_VERSION.'. Shaarli requires at least php 5.1.0, and thus cannot run. Sorry.';
124 exit; 122 exit;
125 } 123 }
126} 124}
@@ -137,9 +135,9 @@ function checkUpdate()
137 if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL']))) 135 if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL'])))
138 { 136 {
139 $version=shaarli_version; 137 $version=shaarli_version;
140 list($httpstatus,$headers,$data) = getHTTP('http://sebsauvage.net/files/shaarli_version.txt',2); 138 list($httpstatus,$headers,$data) = getHTTP('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.txt',2);
141 if (strpos($httpstatus,'200 OK')!==false) $version=$data; 139 if (strpos($httpstatus,'200 OK')!==false) $version=$data;
142 // If failed, nevermind. We don't want to bother the user with that. 140 // If failed, never mind. We don't want to bother the user with that.
143 file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date 141 file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
144 } 142 }
145 // Compare versions: 143 // Compare versions:
@@ -155,11 +153,11 @@ function checkUpdate()
155class pageCache 153class pageCache
156{ 154{
157 private $url; // Full URL of the page to cache (typically the value returned by pageUrl()) 155 private $url; // Full URL of the page to cache (typically the value returned by pageUrl())
158 private $shouldBeCached; // boolean: Should this url be cached ? 156 private $shouldBeCached; // boolean: Should this url be cached?
159 private $filename; // Name of the cache file for this url 157 private $filename; // Name of the cache file for this url.
160 158
161 /* 159 /*
162 $url = url (typically the value returned by pageUrl()) 160 $url = URL (typically the value returned by pageUrl())
163 $shouldBeCached = boolean. If false, the cache will be disabled. 161 $shouldBeCached = boolean. If false, the cache will be disabled.
164 */ 162 */
165 public function __construct($url,$shouldBeCached) 163 public function __construct($url,$shouldBeCached)
@@ -182,7 +180,6 @@ class pageCache
182 public function cache($page) 180 public function cache($page)
183 { 181 {
184 if (!$this->shouldBeCached) return; 182 if (!$this->shouldBeCached) return;
185 if (!is_dir($GLOBALS['config']['PAGECACHE'])) { mkdir($GLOBALS['config']['PAGECACHE'],0705); chmod($GLOBALS['config']['PAGECACHE'],0705); }
186 file_put_contents($this->filename,$page); 183 file_put_contents($this->filename,$page);
187 } 184 }
188 185
@@ -221,8 +218,8 @@ function nl2br_escaped($html)
221 return str_replace('>','&gt;',str_replace('<','&lt;',nl2br($html))); 218 return str_replace('>','&gt;',str_replace('<','&lt;',nl2br($html)));
222} 219}
223 220
224/* Returns the small hash of a string 221/* Returns the small hash of a string, using RFC 4648 base64url format
225 eg. smallHash('20111006_131924') --> yZH23w 222 e.g. smallHash('20111006_131924') --> yZH23w
226 Small hashes: 223 Small hashes:
227 - are unique (well, as unique as crc32, at last) 224 - are unique (well, as unique as crc32, at last)
228 - are always 6 characters long. 225 - are always 6 characters long.
@@ -233,13 +230,10 @@ function nl2br_escaped($html)
233function smallHash($text) 230function smallHash($text)
234{ 231{
235 $t = rtrim(base64_encode(hash('crc32',$text,true)),'='); 232 $t = rtrim(base64_encode(hash('crc32',$text,true)),'=');
236 $t = str_replace('+','-',$t); // Get rid of characters which need encoding in URLs. 233 return strtr($t, '+/', '-_');
237 $t = str_replace('/','_',$t);
238 $t = str_replace('=','@',$t);
239 return $t;
240} 234}
241 235
242// In a string, converts urls to clickable links. 236// In a string, converts URLs to clickable links.
243// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 237// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
244function text2clickable($url) 238function text2clickable($url)
245{ 239{
@@ -260,8 +254,8 @@ function keepMultipleSpaces($text)
260function autoLocale() 254function autoLocale()
261{ 255{
262 $loc='en_US'; // Default if browser does not send HTTP_ACCEPT_LANGUAGE 256 $loc='en_US'; // Default if browser does not send HTTP_ACCEPT_LANGUAGE
263 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // eg. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3" 257 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3"
264 { // (It's a bit crude, but it works very well. Prefered language is always presented first.) 258 { // (It's a bit crude, but it works very well. Preferred language is always presented first.)
265 if (preg_match('/([a-z]{2}(-[a-z]{2})?)/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) $loc=$matches[1]; 259 if (preg_match('/([a-z]{2}(-[a-z]{2})?)/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) $loc=$matches[1];
266 } 260 }
267 setlocale(LC_TIME,$loc); // LC_TIME = Set local for date/time format only. 261 setlocale(LC_TIME,$loc); // LC_TIME = Set local for date/time format only.
@@ -297,16 +291,20 @@ function allIPs()
297 return $ip; 291 return $ip;
298} 292}
299 293
294function fillSessionInfo() {
295 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
296 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
297 $_SESSION['username']=$GLOBALS['login'];
298 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
299}
300
300// Check that user/password is correct. 301// Check that user/password is correct.
301function check_auth($login,$password) 302function check_auth($login,$password)
302{ 303{
303 $hash = sha1($password.$login.$GLOBALS['salt']); 304 $hash = sha1($password.$login.$GLOBALS['salt']);
304 if ($login==$GLOBALS['login'] && $hash==$GLOBALS['hash']) 305 if ($login==$GLOBALS['login'] && $hash==$GLOBALS['hash'])
305 { // Login/password is correct. 306 { // Login/password is correct.
306 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // generate unique random number (different than phpsessionid) 307 fillSessionInfo();
307 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
308 $_SESSION['username']=$login;
309 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
310 logm('Login successful'); 308 logm('Login successful');
311 return True; 309 return True;
312 } 310 }
@@ -321,6 +319,11 @@ function isLoggedIn()
321 319
322 if (!isset($GLOBALS['login'])) return false; // Shaarli is not configured yet. 320 if (!isset($GLOBALS['login'])) return false; // Shaarli is not configured yet.
323 321
322 if (@$_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN)
323 {
324 fillSessionInfo();
325 return true;
326 }
324 // If session does not exist on server side, or IP address has changed, or session has expired, logout. 327 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
325 if (empty($_SESSION['uid']) || ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || time()>=$_SESSION['expires_on']) 328 if (empty($_SESSION['uid']) || ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || time()>=$_SESSION['expires_on'])
326 { 329 {
@@ -334,7 +337,9 @@ function isLoggedIn()
334} 337}
335 338
336// Force logout. 339// Force logout.
337function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); } } 340function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); }
341setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
342}
338 343
339 344
340// ------------------------------------------------------------------------------------------ 345// ------------------------------------------------------------------------------------------
@@ -391,17 +396,18 @@ if (isset($_POST['login']))
391{ 396{
392 if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.'); 397 if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.');
393 if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password']))) 398 if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password'])))
394 { // Login/password is ok. 399 { // Login/password is OK.
395 ban_loginOk(); 400 ban_loginOk();
396 // If user wants to keep the session cookie even after the browser closes: 401 // If user wants to keep the session cookie even after the browser closes:
397 if (!empty($_POST['longlastingsession'])) 402 if (!empty($_POST['longlastingsession']))
398 { 403 {
404 setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, time()+31536000, WEB_PATH);
399 $_SESSION['longlastingsession']=31536000; // (31536000 seconds = 1 year) 405 $_SESSION['longlastingsession']=31536000; // (31536000 seconds = 1 year)
400 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // Set session expiration on server-side. 406 $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // Set session expiration on server-side.
401 407
402 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; 408 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
403 session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['HTTP_HOST']); // Set session cookie expiration on client side 409 session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['HTTP_HOST']); // Set session cookie expiration on client side
404 // Note: Never forget the trailing slash on the cookie path ! 410 // Note: Never forget the trailing slash on the cookie path!
405 session_regenerate_id(true); // Send cookie with new expiration date to browser. 411 session_regenerate_id(true); // Send cookie with new expiration date to browser.
406 } 412 }
407 else // Standard session expiration (=when browser closes) 413 else // Standard session expiration (=when browser closes)
@@ -411,7 +417,7 @@ if (isset($_POST['login']))
411 session_regenerate_id(true); 417 session_regenerate_id(true);
412 } 418 }
413 // Optional redirect after login: 419 // Optional redirect after login:
414 if (isset($_GET['post'])) { header('Location: ?post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); exit; } 420 if (isset($_GET['post'])) { header('Location: ?post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); exit; }
415 if (isset($_POST['returnurl'])) 421 if (isset($_POST['returnurl']))
416 { 422 {
417 if (endsWith($_POST['returnurl'],'?do=login')) { header('Location: ?'); exit; } // Prevent loops over login screen. 423 if (endsWith($_POST['returnurl'],'?do=login')) { header('Location: ?'); exit; } // Prevent loops over login screen.
@@ -423,7 +429,7 @@ if (isset($_POST['login']))
423 { 429 {
424 ban_loginFailed(); 430 ban_loginFailed();
425 $redir = ''; 431 $redir = '';
426 if (isset($_GET['post'])) { $redir = '&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):''); } 432 if (isset($_GET['post'])) { $redir = '&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):''); }
427 echo '<script language="JavaScript">alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen. 433 echo '<script language="JavaScript">alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
428 exit; 434 exit;
429 } 435 }
@@ -433,7 +439,7 @@ if (isset($_POST['login']))
433// Misc utility functions: 439// Misc utility functions:
434 440
435// Returns the server URL (including port and http/https), without path. 441// Returns the server URL (including port and http/https), without path.
436// eg. "http://myserver.com:8080" 442// e.g. "http://myserver.com:8080"
437// You can append $_SERVER['SCRIPT_NAME'] to get the current script URL. 443// You can append $_SERVER['SCRIPT_NAME'] to get the current script URL.
438function serverUrl() 444function serverUrl()
439{ 445{
@@ -443,24 +449,24 @@ function serverUrl()
443} 449}
444 450
445// Returns the absolute URL of current script, without the query. 451// Returns the absolute URL of current script, without the query.
446// (eg. http://sebsauvage.net/links/) 452// (e.g. http://sebsauvage.net/links/)
447function indexUrl() 453function indexUrl()
448{ 454{
449 $scriptname = $_SERVER["SCRIPT_NAME"]; 455 $scriptname = $_SERVER["SCRIPT_NAME"];
450 // If the script is named 'index.php', we remove it (for better looking URLs, 456 // If the script is named 'index.php', we remove it (for better looking URLs,
451 // eg. http://mysite.com/shaarli/?abcde instead of http://mysite.com/shaarli/index.php?abcde) 457 // e.g. http://mysite.com/shaarli/?abcde instead of http://mysite.com/shaarli/index.php?abcde)
452 if (endswith($scriptname,'index.php')) $scriptname = substr($scriptname,0,strlen($scriptname)-9); 458 if (endswith($scriptname,'index.php')) $scriptname = substr($scriptname,0,strlen($scriptname)-9);
453 return serverUrl() . $scriptname; 459 return serverUrl() . $scriptname;
454} 460}
455 461
456// Returns the absolute URL of current script, WITH the query. 462// Returns the absolute URL of current script, WITH the query.
457// (eg. http://sebsauvage.net/links/?toto=titi&spamspamspam=humbug) 463// (e.g. http://sebsauvage.net/links/?toto=titi&spamspamspam=humbug)
458function pageUrl() 464function pageUrl()
459{ 465{
460 return indexUrl().(!empty($_SERVER["QUERY_STRING"]) ? '?'.$_SERVER["QUERY_STRING"] : ''); 466 return indexUrl().(!empty($_SERVER["QUERY_STRING"]) ? '?'.$_SERVER["QUERY_STRING"] : '');
461} 467}
462 468
463// Convert post_max_size/upload_max_filesize (eg.'16M') parameters to bytes. 469// Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
464function return_bytes($val) 470function return_bytes($val)
465{ 471{
466 $val = trim($val); $last=strtolower($val[strlen($val)-1]); 472 $val = trim($val); $last=strtolower($val[strlen($val)-1]);
@@ -481,7 +487,7 @@ function getMaxFileSize()
481 $size2 = return_bytes(ini_get('upload_max_filesize')); 487 $size2 = return_bytes(ini_get('upload_max_filesize'));
482 // Return the smaller of two: 488 // Return the smaller of two:
483 $maxsize = min($size1,$size2); 489 $maxsize = min($size1,$size2);
484 // FIXME: Then convert back to readable notations ? (eg. 2M instead of 2000000) 490 // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000)
485 return $maxsize; 491 return $maxsize;
486} 492}
487 493
@@ -529,7 +535,7 @@ function linkdate2iso8601($linkdate)
529function linkdate2locale($linkdate) 535function linkdate2locale($linkdate)
530{ 536{
531 return utf8_encode(strftime('%c',linkdate2timestamp($linkdate))); // %c is for automatic date format according to locale. 537 return utf8_encode(strftime('%c',linkdate2timestamp($linkdate))); // %c is for automatic date format according to locale.
532 // Note that if you use a local which is not installed on your webserver, 538 // Note that if you use a locale which is not installed on your webserver,
533 // the date will not be displayed in the chosen locale, but probably in US notation. 539 // the date will not be displayed in the chosen locale, but probably in US notation.
534} 540}
535 541
@@ -551,10 +557,10 @@ function http_parse_headers_shaarli( $headers )
551} 557}
552 558
553/* GET an URL. 559/* GET an URL.
554 Input: $url : url to get (http://...) 560 Input: $url : URL to get (http://...)
555 $timeout : Network timeout (will wait this many seconds for an anwser before giving up). 561 $timeout : Network timeout (will wait this many seconds for an anwser before giving up).
556 Output: An array. [0] = HTTP status message (eg. "HTTP/1.1 200 OK") or error message 562 Output: An array. [0] = HTTP status message (e.g. "HTTP/1.1 200 OK") or error message
557 [1] = associative array containing HTTP response headers (eg. echo getHTTP($url)[1]['Content-Type']) 563 [1] = associative array containing HTTP response headers (e.g. echo getHTTP($url)[1]['Content-Type'])
558 [2] = data 564 [2] = data
559 Example: list($httpstatus,$headers,$data) = getHTTP('http://sebauvage.net/'); 565 Example: list($httpstatus,$headers,$data) = getHTTP('http://sebauvage.net/');
560 if (strpos($httpstatus,'200 OK')!==false) 566 if (strpos($httpstatus,'200 OK')!==false)
@@ -570,11 +576,11 @@ function getHTTP($url,$timeout=30)
570 $context = stream_context_create($options); 576 $context = stream_context_create($options);
571 $data=file_get_contents($url,false,$context,-1, 4000000); // We download at most 4 Mb from source. 577 $data=file_get_contents($url,false,$context,-1, 4000000); // We download at most 4 Mb from source.
572 if (!$data) { return array('HTTP Error',array(),''); } 578 if (!$data) { return array('HTTP Error',array(),''); }
573 $httpStatus=$http_response_header[0]; // eg. "HTTP/1.1 200 OK" 579 $httpStatus=$http_response_header[0]; // e.g. "HTTP/1.1 200 OK"
574 $responseHeaders=http_parse_headers_shaarli($http_response_header); 580 $responseHeaders=http_parse_headers_shaarli($http_response_header);
575 return array($httpStatus,$responseHeaders,$data); 581 return array($httpStatus,$responseHeaders,$data);
576 } 582 }
577 catch (Exception $e) // getHTTP *can* fail silentely (we don't care if the title cannot be fetched) 583 catch (Exception $e) // getHTTP *can* fail silently (we don't care if the title cannot be fetched)
578 { 584 {
579 return array($e->getMessage(),'',''); 585 return array($e->getMessage(),'','');
580 } 586 }
@@ -600,14 +606,14 @@ function getToken()
600 return $rnd; 606 return $rnd;
601} 607}
602 608
603// Tells if a token is ok. Using this function will destroy the token. 609// Tells if a token is OK. Using this function will destroy the token.
604// true=token is ok. 610// true=token is OK.
605function tokenOk($token) 611function tokenOk($token)
606{ 612{
607 if (isset($_SESSION['tokens'][$token])) 613 if (isset($_SESSION['tokens'][$token]))
608 { 614 {
609 unset($_SESSION['tokens'][$token]); // Token is used: destroy it. 615 unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
610 return true; // Token is ok. 616 return true; // Token is OK.
611 } 617 }
612 return false; // Wrong token, or already used. 618 return false; // Wrong token, or already used.
613} 619}
@@ -642,8 +648,9 @@ class pageBuilder
642 $this->tpl->assign('version',shaarli_version); 648 $this->tpl->assign('version',shaarli_version);
643 $this->tpl->assign('scripturl',indexUrl()); 649 $this->tpl->assign('scripturl',indexUrl());
644 $this->tpl->assign('pagetitle','Shaarli'); 650 $this->tpl->assign('pagetitle','Shaarli');
645 $this->tpl->assign('privateonly',!empty($_SESSION['privateonly'])); // Show only private links ? 651 $this->tpl->assign('privateonly',!empty($_SESSION['privateonly'])); // Show only private links?
646 if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']); 652 if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']);
653 if (!empty($GLOBALS['titleLink'])) $this->tpl->assign('titleLink',$GLOBALS['titleLink']);
647 if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']); 654 if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']);
648 $this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] ); 655 $this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] );
649 return; 656 return;
@@ -657,7 +664,7 @@ class pageBuilder
657 } 664 }
658 665
659 // Render a specific page (using a template). 666 // Render a specific page (using a template).
660 // eg. pb.renderPage('picwall') 667 // e.g. pb.renderPage('picwall')
661 public function renderPage($page) 668 public function renderPage($page)
662 { 669 {
663 if ($this->tpl===false) $this->initialize(); // Lazy initialization 670 if ($this->tpl===false) $this->initialize(); // Lazy initialization
@@ -676,10 +683,10 @@ class pageBuilder
676 683
677 Available keys: 684 Available keys:
678 title : Title of the link 685 title : Title of the link
679 url : URL of the link. Can be absolute or relative. Relative URLs are permalinks (eg.'?m-ukcw') 686 url : URL of the link. Can be absolute or relative. Relative URLs are permalinks (e.g.'?m-ukcw')
680 description : description of the entry 687 description : description of the entry
681 private : Is this link private ? 0=no, other value=yes 688 private : Is this link private? 0=no, other value=yes
682 linkdate : date of the creation of this entry, in the form YYYYMMDD_HHMMSS (eg.'20110914_192317') 689 linkdate : date of the creation of this entry, in the form YYYYMMDD_HHMMSS (e.g.'20110914_192317')
683 tags : tags attached to this entry (separated by spaces) 690 tags : tags attached to this entry (separated by spaces)
684 691
685 We implement 3 interfaces: 692 We implement 3 interfaces:
@@ -689,15 +696,15 @@ class pageBuilder
689*/ 696*/
690class linkdb implements Iterator, Countable, ArrayAccess 697class linkdb implements Iterator, Countable, ArrayAccess
691{ 698{
692 private $links; // List of links (associative array. Key=linkdate (eg. "20110823_124546"), value= associative array (keys:title,description...) 699 private $links; // List of links (associative array. Key=linkdate (e.g. "20110823_124546"), value= associative array (keys:title,description...)
693 private $urls; // List of all recorded URLs (key=url, value=linkdate) for fast reserve search (url-->linkdate) 700 private $urls; // List of all recorded URLs (key=url, value=linkdate) for fast reserve search (url-->linkdate)
694 private $keys; // List of linkdate keys (for the Iterator interface implementation) 701 private $keys; // List of linkdate keys (for the Iterator interface implementation)
695 private $position; // Position in the $this->keys array. (for the Iterator interface implementation.) 702 private $position; // Position in the $this->keys array. (for the Iterator interface implementation.)
696 private $loggedin; // Is the used logged in ? (used to filter private links) 703 private $loggedin; // Is the user logged in? (used to filter private links)
697 704
698 // Constructor: 705 // Constructor:
699 function __construct($isLoggedIn) 706 function __construct($isLoggedIn)
700 // Input : $isLoggedIn : is the used logged in ? 707 // Input : $isLoggedIn : is the user logged in?
701 { 708 {
702 $this->loggedin = $isLoggedIn; 709 $this->loggedin = $isLoggedIn;
703 $this->checkdb(); // Make sure data file exists. 710 $this->checkdb(); // Make sure data file exists.
@@ -711,7 +718,7 @@ class linkdb implements Iterator, Countable, ArrayAccess
711 public function offsetSet($offset, $value) 718 public function offsetSet($offset, $value)
712 { 719 {
713 if (!$this->loggedin) die('You are not authorized to add a link.'); 720 if (!$this->loggedin) die('You are not authorized to add a link.');
714 if (empty($value['linkdate']) || empty($value['url'])) die('Internal Error: A link should always have a linkdate and url.'); 721 if (empty($value['linkdate']) || empty($value['url'])) die('Internal Error: A link should always have a linkdate and URL.');
715 if (empty($offset)) die('You must specify a key.'); 722 if (empty($offset)) die('You must specify a key.');
716 $this->links[$offset] = $value; 723 $this->links[$offset] = $value;
717 $this->urls[$value['url']]=$offset; 724 $this->urls[$value['url']]=$offset;
@@ -774,19 +781,19 @@ class linkdb implements Iterator, Countable, ArrayAccess
774 invalidateCaches(); 781 invalidateCaches();
775 } 782 }
776 783
777 // Returns the link for a given URL (if it exists). false it does not exist. 784 // Returns the link for a given URL (if it exists). False if it does not exist.
778 public function getLinkFromUrl($url) 785 public function getLinkFromUrl($url)
779 { 786 {
780 if (isset($this->urls[$url])) return $this->links[$this->urls[$url]]; 787 if (isset($this->urls[$url])) return $this->links[$this->urls[$url]];
781 return false; 788 return false;
782 } 789 }
783 790
784 // Case insentitive search among links (in url, title and description). Returns filtered list of links. 791 // Case insensitive search among links (in the URLs, title and description). Returns filtered list of links.
785 // eg. print_r($mydb->filterFulltext('hollandais')); 792 // e.g. print_r($mydb->filterFulltext('hollandais'));
786 public function filterFulltext($searchterms) 793 public function filterFulltext($searchterms)
787 { 794 {
788 // FIXME: explode(' ',$searchterms) and perform a AND search. 795 // FIXME: explode(' ',$searchterms) and perform a AND search.
789 // FIXME: accept double-quotes to search for a string "as is" ? 796 // FIXME: accept double-quotes to search for a string "as is"?
790 $filtered=array(); 797 $filtered=array();
791 $s = strtolower($searchterms); 798 $s = strtolower($searchterms);
792 foreach($this->links as $l) 799 foreach($this->links as $l)
@@ -803,7 +810,7 @@ class linkdb implements Iterator, Countable, ArrayAccess
803 810
804 // Filter by tag. 811 // Filter by tag.
805 // You can specify one or more tags (tags can be separated by space or comma). 812 // You can specify one or more tags (tags can be separated by space or comma).
806 // eg. print_r($mydb->filterTags('linux programming')); 813 // e.g. print_r($mydb->filterTags('linux programming'));
807 public function filterTags($tags,$casesensitive=false) 814 public function filterTags($tags,$casesensitive=false)
808 { 815 {
809 $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags))); 816 $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags)));
@@ -819,9 +826,9 @@ class linkdb implements Iterator, Countable, ArrayAccess
819 return $filtered; 826 return $filtered;
820 } 827 }
821 828
822 // Filter by day. Day must be in the form 'YYYYMMDD' (eg. '20120125') 829 // Filter by day. Day must be in the form 'YYYYMMDD' (e.g. '20120125')
823 // Sort order is: older articles first. 830 // Sort order is: older articles first.
824 // eg. print_r($mydb->filterDay('20120125')); 831 // e.g. print_r($mydb->filterDay('20120125'));
825 public function filterDay($day) 832 public function filterDay($day)
826 { 833 {
827 $filtered=array(); 834 $filtered=array();
@@ -876,13 +883,13 @@ class linkdb implements Iterator, Countable, ArrayAccess
876} 883}
877 884
878// ------------------------------------------------------------------------------------------ 885// ------------------------------------------------------------------------------------------
879// Ouput the last N links in RSS 2.0 format. 886// Output the last N links in RSS 2.0 format.
880function showRSS() 887function showRSS()
881{ 888{
882 header('Content-Type: application/rss+xml; charset=utf-8'); 889 header('Content-Type: application/rss+xml; charset=utf-8');
883 890
884 // $usepermalink : If true, use permalink instead of final link. 891 // $usepermalink : If true, use permalink instead of final link.
885 // User just has to add 'permalink' in URL parameters. eg. http://mysite.com/shaarli/?do=rss&permalinks 892 // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks
886 $usepermalinks = isset($_GET['permalinks']); 893 $usepermalinks = isset($_GET['permalinks']);
887 894
888 // Cache system 895 // Cache system
@@ -891,9 +898,9 @@ function showRSS()
891 $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; } 898 $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; }
892 899
893 // If cached was not found (or not usable), then read the database and build the response: 900 // If cached was not found (or not usable), then read the database and build the response:
894 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in). 901 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if user it not logged in).
895 902
896 // Optionnaly filter the results: 903 // Optionally filter the results:
897 $linksToDisplay=array(); 904 $linksToDisplay=array();
898 if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); 905 if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
899 elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); 906 elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
@@ -942,7 +949,7 @@ function showRSS()
942 echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description'])))).$descriptionlink.']]></description>'."\n</item>\n"; 949 echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description'])))).$descriptionlink.']]></description>'."\n</item>\n";
943 $i++; 950 $i++;
944 } 951 }
945 echo '</channel></rss><!-- Cached version of '.pageUrl().' -->'; 952 echo '</channel></rss><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
946 953
947 $cache->cache(ob_get_contents()); 954 $cache->cache(ob_get_contents());
948 ob_end_flush(); 955 ob_end_flush();
@@ -950,13 +957,13 @@ function showRSS()
950} 957}
951 958
952// ------------------------------------------------------------------------------------------ 959// ------------------------------------------------------------------------------------------
953// Ouput the last N links in ATOM format. 960// Output the last N links in ATOM format.
954function showATOM() 961function showATOM()
955{ 962{
956 header('Content-Type: application/atom+xml; charset=utf-8'); 963 header('Content-Type: application/atom+xml; charset=utf-8');
957 964
958 // $usepermalink : If true, use permalink instead of final link. 965 // $usepermalink : If true, use permalink instead of final link.
959 // User just has to add 'permalink' in URL parameters. eg. http://mysite.com/shaarli/?do=atom&permalinks 966 // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks
960 $usepermalinks = isset($_GET['permalinks']); 967 $usepermalinks = isset($_GET['permalinks']);
961 968
962 // Cache system 969 // Cache system
@@ -968,7 +975,7 @@ function showATOM()
968 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in). 975 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
969 976
970 977
971 // Optionnaly filter the results: 978 // Optionally filter the results:
972 $linksToDisplay=array(); 979 $linksToDisplay=array();
973 if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); 980 if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
974 elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); 981 elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
@@ -1027,7 +1034,7 @@ function showATOM()
1027 $feed.='<author><name>'.htmlspecialchars($pageaddr).'</name><uri>'.htmlspecialchars($pageaddr).'</uri></author>'; 1034 $feed.='<author><name>'.htmlspecialchars($pageaddr).'</name><uri>'.htmlspecialchars($pageaddr).'</uri></author>';
1028 $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do. 1035 $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
1029 $feed.=$entries; 1036 $feed.=$entries;
1030 $feed.='</feed><!-- Cached version of '.pageUrl().' -->'; 1037 $feed.='</feed><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
1031 echo $feed; 1038 echo $feed;
1032 1039
1033 $cache->cache(ob_get_contents()); 1040 $cache->cache(ob_get_contents());
@@ -1064,7 +1071,7 @@ function showDailyRSS()
1064 if (empty($days[$day])) $days[$day]=array(); 1071 if (empty($days[$day])) $days[$day]=array();
1065 $days[$day][]=$linkdate; 1072 $days[$day][]=$linkdate;
1066 } 1073 }
1067 if (count($days)>$nb_of_days) break; // Have we collected enough days ? 1074 if (count($days)>$nb_of_days) break; // Have we collected enough days?
1068 } 1075 }
1069 1076
1070 // Build the RSS feed. 1077 // Build the RSS feed.
@@ -1104,7 +1111,7 @@ function showDailyRSS()
1104 echo '<description><![CDATA['.$html.']]></description>'."\n</item>\n\n"; 1111 echo '<description><![CDATA['.$html.']]></description>'."\n</item>\n\n";
1105 1112
1106 } 1113 }
1107 echo '</channel></rss><!-- Cached version of '.pageUrl().' -->'; 1114 echo '</channel></rss><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
1108 1115
1109 $cache->cache(ob_get_contents()); 1116 $cache->cache(ob_get_contents());
1110 ob_end_flush(); 1117 ob_end_flush();
@@ -1143,7 +1150,7 @@ function showDaily()
1143 } 1150 }
1144 1151
1145 /* We need to spread the articles on 3 columns. 1152 /* We need to spread the articles on 3 columns.
1146 I did not want to use a javascript lib like http://masonry.desandro.com/ 1153 I did not want to use a JavaScript lib like http://masonry.desandro.com/
1147 so I manually spread entries with a simple method: I roughly evaluate the 1154 so I manually spread entries with a simple method: I roughly evaluate the
1148 height of a div according to title and description length. 1155 height of a div according to title and description length.
1149 */ 1156 */
@@ -1154,7 +1161,7 @@ function showDaily()
1154 // Roughly estimate length of entry (by counting characters) 1161 // Roughly estimate length of entry (by counting characters)
1155 // Title: 30 chars = 1 line. 1 line is 30 pixels height. 1162 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
1156 // Description: 836 characters gives roughly 342 pixel height. 1163 // Description: 836 characters gives roughly 342 pixel height.
1157 // This is not perfect, but it's usually ok. 1164 // This is not perfect, but it's usually OK.
1158 $length=strlen($link['title'])+(342*strlen($link['description']))/836; 1165 $length=strlen($link['title'])+(342*strlen($link['description']))/836;
1159 if ($link['thumbnail']) $length +=100; // 1 thumbnails roughly takes 100 pixels height. 1166 if ($link['thumbnail']) $length +=100; // 1 thumbnails roughly takes 100 pixels height.
1160 // Then put in column which is the less filled: 1167 // Then put in column which is the less filled:
@@ -1207,7 +1214,7 @@ function renderPage()
1207 // -------- Picture wall 1214 // -------- Picture wall
1208 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall')) 1215 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall'))
1209 { 1216 {
1210 // Optionnaly filter the results: 1217 // Optionally filter the results:
1211 $links=array(); 1218 $links=array();
1212 if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']); 1219 if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']);
1213 elseif (!empty($_GET['searchtags'])) $links = $LINKSDB->filterTags(trim($_GET['searchtags'])); 1220 elseif (!empty($_GET['searchtags'])) $links = $LINKSDB->filterTags(trim($_GET['searchtags']));
@@ -1287,7 +1294,7 @@ function renderPage()
1287 if (isset($_GET['linksperpage'])) 1294 if (isset($_GET['linksperpage']))
1288 { 1295 {
1289 if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); } 1296 if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); }
1290 // Make sure the referer is from Shaarli itself. 1297 // Make sure the referrer is Shaarli itself.
1291 $referer = '?'; 1298 $referer = '?';
1292 if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0) 1299 if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
1293 $referer = $_SERVER['HTTP_REFERER']; 1300 $referer = $_SERVER['HTTP_REFERER'];
@@ -1306,7 +1313,7 @@ function renderPage()
1306 { 1313 {
1307 unset($_SESSION['privateonly']); // See all links 1314 unset($_SESSION['privateonly']); // See all links
1308 } 1315 }
1309 // Make sure the referer is from Shaarli itself. 1316 // Make sure the referrer is Shaarli itself.
1310 $referer = '?'; 1317 $referer = '?';
1311 if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0) 1318 if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
1312 $referer = $_SERVER['HTTP_REFERER']; 1319 $referer = $_SERVER['HTTP_REFERER'];
@@ -1317,17 +1324,17 @@ function renderPage()
1317 // -------- Handle other actions allowed for non-logged in users: 1324 // -------- Handle other actions allowed for non-logged in users:
1318 if (!isLoggedIn()) 1325 if (!isLoggedIn())
1319 { 1326 {
1320 // User tries to post new link but is not loggedin: 1327 // User tries to post new link but is not logged in:
1321 // Show login screen, then redirect to ?post=... 1328 // Show login screen, then redirect to ?post=...
1322 if (isset($_GET['post'])) 1329 if (isset($_GET['post']))
1323 { 1330 {
1324 header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link. 1331 header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link.
1325 exit; 1332 exit;
1326 } 1333 }
1327 $PAGE = new pageBuilder; 1334 $PAGE = new pageBuilder;
1328 buildLinkList($PAGE,$LINKSDB); // Compute list of links to display 1335 buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
1329 $PAGE->renderPage('linklist'); 1336 $PAGE->renderPage('linklist');
1330 exit; // Never remove this one ! All operations below are reserved for logged in user. 1337 exit; // Never remove this one! All operations below are reserved for logged in user.
1331 } 1338 }
1332 1339
1333 // -------- All other functions are reserved for the registered user: 1340 // -------- All other functions are reserved for the registered user:
@@ -1348,7 +1355,7 @@ function renderPage()
1348 if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.'); 1355 if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.');
1349 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 1356 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
1350 { 1357 {
1351 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! 1358 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1352 1359
1353 // Make sure old password is correct. 1360 // Make sure old password is correct.
1354 $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1361 $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']);
@@ -1375,13 +1382,14 @@ function renderPage()
1375 { 1382 {
1376 if (!empty($_POST['title']) ) 1383 if (!empty($_POST['title']) )
1377 { 1384 {
1378 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! 1385 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1379 $tz = 'UTC'; 1386 $tz = 'UTC';
1380 if (!empty($_POST['continent']) && !empty($_POST['city'])) 1387 if (!empty($_POST['continent']) && !empty($_POST['city']))
1381 if (isTZvalid($_POST['continent'],$_POST['city'])) 1388 if (isTZvalid($_POST['continent'],$_POST['city']))
1382 $tz = $_POST['continent'].'/'.$_POST['city']; 1389 $tz = $_POST['continent'].'/'.$_POST['city'];
1383 $GLOBALS['timezone'] = $tz; 1390 $GLOBALS['timezone'] = $tz;
1384 $GLOBALS['title']=$_POST['title']; 1391 $GLOBALS['title']=$_POST['title'];
1392 $GLOBALS['titleLink']=$_POST['titleLink'];
1385 $GLOBALS['redirector']=$_POST['redirector']; 1393 $GLOBALS['redirector']=$_POST['redirector'];
1386 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']); 1394 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
1387 $GLOBALS['disablejquery']=!empty($_POST['disablejquery']); 1395 $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
@@ -1398,7 +1406,7 @@ function renderPage()
1398 $PAGE->assign('title',htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES)); 1406 $PAGE->assign('title',htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES));
1399 $PAGE->assign('redirector',htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES)); 1407 $PAGE->assign('redirector',htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES));
1400 list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']); 1408 list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']);
1401 $PAGE->assign('timezone_form',$timezone_form); // FIXME: put entire tz form generation in template ? 1409 $PAGE->assign('timezone_form',$timezone_form); // FIXME: Put entire tz form generation in template?
1402 $PAGE->assign('timezone_js',$timezone_js); 1410 $PAGE->assign('timezone_js',$timezone_js);
1403 $PAGE->renderPage('configure'); 1411 $PAGE->renderPage('configure');
1404 exit; 1412 exit;
@@ -1422,7 +1430,7 @@ function renderPage()
1422 if (!empty($_POST['deletetag']) && !empty($_POST['fromtag'])) 1430 if (!empty($_POST['deletetag']) && !empty($_POST['fromtag']))
1423 { 1431 {
1424 $needle=trim($_POST['fromtag']); 1432 $needle=trim($_POST['fromtag']);
1425 $linksToAlter = $LINKSDB->filterTags($needle,true); // true for case-sensitive tag search. 1433 $linksToAlter = $LINKSDB->filterTags($needle,true); // True for case-sensitive tag search.
1426 foreach($linksToAlter as $key=>$value) 1434 foreach($linksToAlter as $key=>$value)
1427 { 1435 {
1428 $tags = explode(' ',trim($value['tags'])); 1436 $tags = explode(' ',trim($value['tags']));
@@ -1430,7 +1438,7 @@ function renderPage()
1430 $value['tags']=trim(implode(' ',$tags)); 1438 $value['tags']=trim(implode(' ',$tags));
1431 $LINKSDB[$key]=$value; 1439 $LINKSDB[$key]=$value;
1432 } 1440 }
1433 $LINKSDB->savedb(); // save to disk 1441 $LINKSDB->savedb(); // Save to disk.
1434 echo '<script language="JavaScript">alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>'; 1442 echo '<script language="JavaScript">alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
1435 exit; 1443 exit;
1436 } 1444 }
@@ -1443,17 +1451,17 @@ function renderPage()
1443 foreach($linksToAlter as $key=>$value) 1451 foreach($linksToAlter as $key=>$value)
1444 { 1452 {
1445 $tags = explode(' ',trim($value['tags'])); 1453 $tags = explode(' ',trim($value['tags']));
1446 $tags[array_search($needle,$tags)] = trim($_POST['totag']); // Remplace tags value. 1454 $tags[array_search($needle,$tags)] = trim($_POST['totag']); // Replace tags value.
1447 $value['tags']=trim(implode(' ',$tags)); 1455 $value['tags']=trim(implode(' ',$tags));
1448 $LINKSDB[$key]=$value; 1456 $LINKSDB[$key]=$value;
1449 } 1457 }
1450 $LINKSDB->savedb(); // save to disk 1458 $LINKSDB->savedb(); // Save to disk.
1451 echo '<script language="JavaScript">alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>'; 1459 echo '<script language="JavaScript">alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
1452 exit; 1460 exit;
1453 } 1461 }
1454 } 1462 }
1455 1463
1456 // -------- User wants to add a link without using the bookmarklet: show form. 1464 // -------- User wants to add a link without using the bookmarklet: Show form.
1457 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=addlink')) 1465 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=addlink'))
1458 { 1466 {
1459 $PAGE = new pageBuilder; 1467 $PAGE = new pageBuilder;
@@ -1465,7 +1473,7 @@ function renderPage()
1465 // -------- User clicked the "Save" button when editing a link: Save link to database. 1473 // -------- User clicked the "Save" button when editing a link: Save link to database.
1466 if (isset($_POST['save_edit'])) 1474 if (isset($_POST['save_edit']))
1467 { 1475 {
1468 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! 1476 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1469 $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces. 1477 $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces.
1470 $linkdate=$_POST['lf_linkdate']; 1478 $linkdate=$_POST['lf_linkdate'];
1471 $url = trim($_POST['lf_url']); 1479 $url = trim($_POST['lf_url']);
@@ -1475,7 +1483,7 @@ function renderPage()
1475 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); 1483 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags));
1476 if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title. 1484 if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title.
1477 $LINKSDB[$linkdate] = $link; 1485 $LINKSDB[$linkdate] = $link;
1478 $LINKSDB->savedb(); // save to disk 1486 $LINKSDB->savedb(); // Save to disk.
1479 pubsubhub(); 1487 pubsubhub();
1480 1488
1481 // If we are called from the bookmarklet, we must close the popup: 1489 // If we are called from the bookmarklet, we must close the popup:
@@ -1489,7 +1497,7 @@ function renderPage()
1489 // -------- User clicked the "Cancel" button when editing a link. 1497 // -------- User clicked the "Cancel" button when editing a link.
1490 if (isset($_POST['cancel_edit'])) 1498 if (isset($_POST['cancel_edit']))
1491 { 1499 {
1492 // If we are called from the bookmarklet, we must close the popup; 1500 // If we are called from the bookmarklet, we must close the popup:
1493 if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script language="JavaScript">self.close();</script>'; exit; } 1501 if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script language="JavaScript">self.close();</script>'; exit; }
1494 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1502 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1495 $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. 1503 $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited.
@@ -1497,12 +1505,12 @@ function renderPage()
1497 exit; 1505 exit;
1498 } 1506 }
1499 1507
1500 // -------- User clicked the "Delete" button when editing a link : Delete link from database. 1508 // -------- User clicked the "Delete" button when editing a link: Delete link from database.
1501 if (isset($_POST['delete_link'])) 1509 if (isset($_POST['delete_link']))
1502 { 1510 {
1503 if (!tokenOk($_POST['token'])) die('Wrong token.'); 1511 if (!tokenOk($_POST['token'])) die('Wrong token.');
1504 // We do not need to ask for confirmation: 1512 // We do not need to ask for confirmation:
1505 // - confirmation is handled by javascript 1513 // - confirmation is handled by JavaScript
1506 // - we are protected from XSRF by the token. 1514 // - we are protected from XSRF by the token.
1507 $linkdate=$_POST['lf_linkdate']; 1515 $linkdate=$_POST['lf_linkdate'];
1508 unset($LINKSDB[$linkdate]); 1516 unset($LINKSDB[$linkdate]);
@@ -1552,7 +1560,7 @@ function renderPage()
1552 $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] ); // Get tags if it was provided in URL 1560 $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] ); // Get tags if it was provided in URL
1553 $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL 1561 $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL
1554 if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url; 1562 if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url;
1555 // If this is an HTTP link, we try go get the page to extact the title (otherwise we will to straight to the edit form.) 1563 // If this is an HTTP link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
1556 if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http') 1564 if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http')
1557 { 1565 {
1558 list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive. 1566 list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.
@@ -1581,7 +1589,11 @@ function renderPage()
1581 } 1589 }
1582 } 1590 }
1583 } 1591 }
1584 if ($url=='') $url='?'.smallHash($linkdate); // In case of empty URL, this is just a text (with a link that point to itself) 1592 if ($url=='') // In case of empty URL, this is just a text (with a link that points to itself)
1593 {
1594 $url='?'.smallHash($linkdate);
1595 $title='Note: ';
1596 }
1585 $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>$private); 1597 $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>$private);
1586 } 1598 }
1587 1599
@@ -1606,7 +1618,7 @@ function renderPage()
1606 exit; 1618 exit;
1607 } 1619 }
1608 $exportWhat=$_GET['what']; 1620 $exportWhat=$_GET['what'];
1609 if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export ???'); 1621 if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export???');
1610 1622
1611 header('Content-Type: text/html; charset=utf-8'); 1623 header('Content-Type: text/html; charset=utf-8');
1612 header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html'); 1624 header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html');
@@ -1679,8 +1691,8 @@ function importFile()
1679 $filename=$_FILES['filetoupload']['name']; 1691 $filename=$_FILES['filetoupload']['name'];
1680 $filesize=$_FILES['filetoupload']['size']; 1692 $filesize=$_FILES['filetoupload']['size'];
1681 $data=file_get_contents($_FILES['filetoupload']['tmp_name']); 1693 $data=file_get_contents($_FILES['filetoupload']['tmp_name']);
1682 $private = (empty($_POST['private']) ? 0 : 1); // Should the links be imported as private ? 1694 $private = (empty($_POST['private']) ? 0 : 1); // Should the links be imported as private?
1683 $overwrite = !empty($_POST['overwrite']) ; // Should the imported links overwrite existing ones ? 1695 $overwrite = !empty($_POST['overwrite']) ; // Should the imported links overwrite existing ones?
1684 $import_count=0; 1696 $import_count=0;
1685 1697
1686 // Sniff file type: 1698 // Sniff file type:
@@ -1691,7 +1703,7 @@ function importFile()
1691 if ($type=='netscape') 1703 if ($type=='netscape')
1692 { 1704 {
1693 // This is a standard Netscape-style bookmark file. 1705 // This is a standard Netscape-style bookmark file.
1694 // This format is supported by all browsers (except IE, of course), also delicious, diigo and others. 1706 // This format is supported by all browsers (except IE, of course), also Delicious, Diigo and others.
1695 foreach(explode('<DT>',$data) as $html) // explode is very fast 1707 foreach(explode('<DT>',$data) as $html) // explode is very fast
1696 { 1708 {
1697 $link = array('linkdate'=>'','title'=>'','url'=>'','description'=>'','tags'=>'','private'=>0); 1709 $link = array('linkdate'=>'','title'=>'','url'=>'','description'=>'','tags'=>'','private'=>0);
@@ -1725,14 +1737,14 @@ function importFile()
1725 1737
1726 // Make sure date/time is not already used by another link. 1738 // Make sure date/time is not already used by another link.
1727 // (Some bookmark files have several different links with the same ADD_DATE) 1739 // (Some bookmark files have several different links with the same ADD_DATE)
1728 // We increment date by 1 second until we find a date which is not used in db. 1740 // We increment date by 1 second until we find a date which is not used in DB.
1729 // (so that links that have the same date/time are more or less kept grouped by date, but do not conflict.) 1741 // (so that links that have the same date/time are more or less kept grouped by date, but do not conflict.)
1730 while (!empty($LINKSDB[date('Ymd_His',$raw_add_date)])) { $raw_add_date++; }// Yes, I know it's ugly. 1742 while (!empty($LINKSDB[date('Ymd_His',$raw_add_date)])) { $raw_add_date++; }// Yes, I know it's ugly.
1731 $link['linkdate']=date('Ymd_His',$raw_add_date); 1743 $link['linkdate']=date('Ymd_His',$raw_add_date);
1732 $LINKSDB[$link['linkdate']] = $link; 1744 $LINKSDB[$link['linkdate']] = $link;
1733 $import_count++; 1745 $import_count++;
1734 } 1746 }
1735 else // link already present in database. 1747 else // Link already present in database.
1736 { 1748 {
1737 if ($overwrite) 1749 if ($overwrite)
1738 { // If overwrite is required, we import link data, except date/time. 1750 { // If overwrite is required, we import link data, except date/time.
@@ -1747,11 +1759,11 @@ function importFile()
1747 } 1759 }
1748 $LINKSDB->savedb(); 1760 $LINKSDB->savedb();
1749 1761
1750 echo '<script language="JavaScript">alert("File '.$filename.' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>'; 1762 echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
1751 } 1763 }
1752 else 1764 else
1753 { 1765 {
1754 echo '<script language="JavaScript">alert("File '.$filename.' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>'; 1766 echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
1755 } 1767 }
1756} 1768}
1757 1769
@@ -1783,13 +1795,13 @@ function buildLinkList($PAGE,$LINKSDB)
1783 { 1795 {
1784 header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); 1796 header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
1785 echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.'; 1797 echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.';
1786 echo '<br>You would mind <a href="?">clicking here</a> ?'; 1798 echo '<br>You would mind <a href="?">clicking here</a>?';
1787 exit; 1799 exit;
1788 } 1800 }
1789 $search_type='permalink'; 1801 $search_type='permalink';
1790 } 1802 }
1791 else 1803 else
1792 $linksToDisplay = $LINKSDB; // otherwise, display without filtering. 1804 $linksToDisplay = $LINKSDB; // Otherwise, display without filtering.
1793 1805
1794 // Option: Show only private links 1806 // Option: Show only private links
1795 if (!empty($_SESSION['privateonly'])) 1807 if (!empty($_SESSION['privateonly']))
@@ -1803,11 +1815,11 @@ function buildLinkList($PAGE,$LINKSDB)
1803 } 1815 }
1804 1816
1805 // ---- Handle paging. 1817 // ---- Handle paging.
1806 /* Can someone explain to me why you get the following error when using array_keys() on an object which implements the interface ArrayAccess ??? 1818 /* Can someone explain to me why you get the following error when using array_keys() on an object which implements the interface ArrayAccess???
1807 "Warning: array_keys() expects parameter 1 to be array, object given in ... " 1819 "Warning: array_keys() expects parameter 1 to be array, object given in ... "
1808 If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); ) 1820 If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); )
1809 */ 1821 */
1810 $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // Stupid and ugly. Thanks php. 1822 $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // Stupid and ugly. Thanks PHP.
1811 1823
1812 // If there is only a single link, we change on-the-fly the title of the page. 1824 // If there is only a single link, we change on-the-fly the title of the page.
1813 if (count($linksToDisplay)==1) $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title']; 1825 if (count($linksToDisplay)==1) $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title'];
@@ -1854,7 +1866,7 @@ function buildLinkList($PAGE,$LINKSDB)
1854 $PAGE->assign('result_count',count($linksToDisplay)); 1866 $PAGE->assign('result_count',count($linksToDisplay));
1855 $PAGE->assign('search_type',$search_type); 1867 $PAGE->assign('search_type',$search_type);
1856 $PAGE->assign('search_crits',$search_crits); 1868 $PAGE->assign('search_crits',$search_crits);
1857 $PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // optional redirector URL 1869 $PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // Optional redirector URL.
1858 $PAGE->assign('token',$token); 1870 $PAGE->assign('token',$token);
1859 $PAGE->assign('links',$linkDisp); 1871 $PAGE->assign('links',$linkDisp);
1860 return; 1872 return;
@@ -1862,9 +1874,9 @@ function buildLinkList($PAGE,$LINKSDB)
1862 1874
1863// Compute the thumbnail for a link. 1875// Compute the thumbnail for a link.
1864// 1876//
1865// with a link to the original URL. 1877// With a link to the original URL.
1866// Understands various services (youtube.com...) 1878// Understands various services (youtube.com...)
1867// Input: $url = url for which the thumbnail must be found. 1879// Input: $url = URL for which the thumbnail must be found.
1868// $href = if provided, this URL will be followed instead of $url 1880// $href = if provided, this URL will be followed instead of $url
1869// Returns an associative array with thumbnail attributes (src,href,width,height,style,alt) 1881// Returns an associative array with thumbnail attributes (src,href,width,height,style,alt)
1870// Some of them may be missing. 1882// Some of them may be missing.
@@ -1875,19 +1887,19 @@ function computeThumbnail($url,$href=false)
1875 if ($href==false) $href=$url; 1887 if ($href==false) $href=$url;
1876 1888
1877 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. 1889 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
1878 // (eg. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg ) 1890 // (e.g. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg )
1879 // ^^^^^^^^^^^ ^^^^^^^^^^^ 1891 // ^^^^^^^^^^^ ^^^^^^^^^^^
1880 $domain = parse_url($url,PHP_URL_HOST); 1892 $domain = parse_url($url,PHP_URL_HOST);
1881 if ($domain=='youtube.com' || $domain=='www.youtube.com') 1893 if ($domain=='youtube.com' || $domain=='www.youtube.com')
1882 { 1894 {
1883 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail 1895 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail
1884 if (!empty($params['v'])) return array('src'=>'http://img.youtube.com/vi/'.$params['v'].'/default.jpg', 1896 if (!empty($params['v'])) return array('src'=>'https://img.youtube.com/vi/'.$params['v'].'/default.jpg',
1885 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail'); 1897 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
1886 } 1898 }
1887 if ($domain=='youtu.be') // Youtube short links 1899 if ($domain=='youtu.be') // Youtube short links
1888 { 1900 {
1889 $path = parse_url($url,PHP_URL_PATH); 1901 $path = parse_url($url,PHP_URL_PATH);
1890 return array('src'=>'http://img.youtube.com/vi'.$path.'/default.jpg', 1902 return array('src'=>'https://img.youtube.com/vi'.$path.'/default.jpg',
1891 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail'); 1903 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
1892 } 1904 }
1893 if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting 1905 if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting
@@ -1901,18 +1913,18 @@ function computeThumbnail($url,$href=false)
1901 { 1913 {
1902 $path = parse_url($url,PHP_URL_PATH); 1914 $path = parse_url($url,PHP_URL_PATH);
1903 if (startsWith($path,'/a/')) return array(); // Thumbnails for albums are not available. 1915 if (startsWith($path,'/a/')) return array(); // Thumbnails for albums are not available.
1904 if (startsWith($path,'/r/')) return array('src'=>'http://i.imgur.com/'.basename($path).'s.jpg', 1916 if (startsWith($path,'/r/')) return array('src'=>'https://i.imgur.com/'.basename($path).'s.jpg',
1905 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail'); 1917 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1906 if (startsWith($path,'/gallery/')) return array('src'=>'http://i.imgur.com'.substr($path,8).'s.jpg', 1918 if (startsWith($path,'/gallery/')) return array('src'=>'https://i.imgur.com'.substr($path,8).'s.jpg',
1907 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail'); 1919 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1908 1920
1909 if (substr_count($path,'/')==1) return array('src'=>'http://i.imgur.com/'.substr($path,1).'s.jpg', 1921 if (substr_count($path,'/')==1) return array('src'=>'https://i.imgur.com/'.substr($path,1).'s.jpg',
1910 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail'); 1922 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1911 } 1923 }
1912 if ($domain=='i.imgur.com') 1924 if ($domain=='i.imgur.com')
1913 { 1925 {
1914 $pi = pathinfo(parse_url($url,PHP_URL_PATH)); 1926 $pi = pathinfo(parse_url($url,PHP_URL_PATH));
1915 if (!empty($pi['filename'])) return array('src'=>'http://i.imgur.com/'.$pi['filename'].'s.jpg', 1927 if (!empty($pi['filename'])) return array('src'=>'https://i.imgur.com/'.$pi['filename'].'s.jpg',
1916 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail'); 1928 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1917 } 1929 }
1918 if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com') 1930 if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com')
@@ -1948,17 +1960,17 @@ function computeThumbnail($url,$href=false)
1948 ) 1960 )
1949 { 1961 {
1950 if ($domain=='vimeo.com') 1962 if ($domain=='vimeo.com')
1951 { // Make sure this vimeo url points to a video (/xxx... where xxx is numeric) 1963 { // Make sure this vimeo URL points to a video (/xxx... where xxx is numeric)
1952 $path = parse_url($url,PHP_URL_PATH); 1964 $path = parse_url($url,PHP_URL_PATH);
1953 if (!preg_match('!/\d+.+?!',$path)) return array(); // This is not a single video URL. 1965 if (!preg_match('!/\d+.+?!',$path)) return array(); // This is not a single video URL.
1954 } 1966 }
1955 if ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com')) 1967 if ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
1956 { // Make sure this url points to a single comic (/xxx... where xxx is numeric) 1968 { // Make sure this URL points to a single comic (/xxx... where xxx is numeric)
1957 $path = parse_url($url,PHP_URL_PATH); 1969 $path = parse_url($url,PHP_URL_PATH);
1958 if (!preg_match('!/\d+.+?!',$path)) return array(); 1970 if (!preg_match('!/\d+.+?!',$path)) return array();
1959 } 1971 }
1960 if ($domain=='ted.com' || endsWith($domain,'.ted.com')) 1972 if ($domain=='ted.com' || endsWith($domain,'.ted.com'))
1961 { // Make sure this TED url points to a video (/talks/...) 1973 { // Make sure this TED URL points to a video (/talks/...)
1962 $path = parse_url($url,PHP_URL_PATH); 1974 $path = parse_url($url,PHP_URL_PATH);
1963 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL. 1975 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
1964 } 1976 }
@@ -1985,7 +1997,7 @@ function computeThumbnail($url,$href=false)
1985// Returns the HTML code to display a thumbnail for a link 1997// Returns the HTML code to display a thumbnail for a link
1986// with a link to the original URL. 1998// with a link to the original URL.
1987// Understands various services (youtube.com...) 1999// Understands various services (youtube.com...)
1988// Input: $url = url for which the thumbnail must be found. 2000// Input: $url = URL for which the thumbnail must be found.
1989// $href = if provided, this URL will be followed instead of $url 2001// $href = if provided, this URL will be followed instead of $url
1990// Returns '' if no thumbnail available. 2002// Returns '' if no thumbnail available.
1991function thumbnail($url,$href=false) 2003function thumbnail($url,$href=false)
@@ -2006,7 +2018,7 @@ function thumbnail($url,$href=false)
2006// Returns the HTML code to display a thumbnail for a link 2018// Returns the HTML code to display a thumbnail for a link
2007// for the picture wall (using lazy image loading) 2019// for the picture wall (using lazy image loading)
2008// Understands various services (youtube.com...) 2020// Understands various services (youtube.com...)
2009// Input: $url = url for which the thumbnail must be found. 2021// Input: $url = URL for which the thumbnail must be found.
2010// $href = if provided, this URL will be followed instead of $url 2022// $href = if provided, this URL will be followed instead of $url
2011// Returns '' if no thumbnail available. 2023// Returns '' if no thumbnail available.
2012function lazyThumbnail($url,$href=false) 2024function lazyThumbnail($url,$href=false)
@@ -2016,7 +2028,7 @@ function lazyThumbnail($url,$href=false)
2016 2028
2017 $html='<a href="'.htmlspecialchars($t['href']).'">'; 2029 $html='<a href="'.htmlspecialchars($t['href']).'">';
2018 2030
2019 // Lazy image (only loaded by javascript when in the viewport). 2031 // Lazy image (only loaded by JavaScript when in the viewport).
2020 if (!empty($GLOBALS['disablejquery'])) // (except if jQuery is disabled) 2032 if (!empty($GLOBALS['disablejquery'])) // (except if jQuery is disabled)
2021 $html.='<img class="lazyimage" src="'.htmlspecialchars($t['src']).'"'; 2033 $html.='<img class="lazyimage" src="'.htmlspecialchars($t['src']).'"';
2022 else 2034 else
@@ -2028,7 +2040,7 @@ function lazyThumbnail($url,$href=false)
2028 if (!empty($t['alt'])) $html.=' alt="'.htmlspecialchars($t['alt']).'"'; 2040 if (!empty($t['alt'])) $html.=' alt="'.htmlspecialchars($t['alt']).'"';
2029 $html.='>'; 2041 $html.='>';
2030 2042
2031 // No-javascript fallback. 2043 // No-JavaScript fallback.
2032 $html.='<noscript><img src="'.htmlspecialchars($t['src']).'"'; 2044 $html.='<noscript><img src="'.htmlspecialchars($t['src']).'"';
2033 if (!empty($t['width'])) $html.=' width="'.htmlspecialchars($t['width']).'"'; 2045 if (!empty($t['width'])) $html.=' width="'.htmlspecialchars($t['width']).'"';
2034 if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"'; 2046 if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"';
@@ -2056,7 +2068,9 @@ function install()
2056 { // Step 2: Check if data in session is correct. 2068 { // Step 2: Check if data in session is correct.
2057 echo '<pre>Sessions do not seem to work correctly on your server.<br>'; 2069 echo '<pre>Sessions do not seem to work correctly on your server.<br>';
2058 echo 'Make sure the variable session.save_path is set correctly in your php config, and that you have write access to it.<br>'; 2070 echo 'Make sure the variable session.save_path is set correctly in your php config, and that you have write access to it.<br>';
2059 echo 'It currently points to '.session_save_path().'<br><br><a href="?">Click to try again.</a></pre>'; 2071 echo 'It currently points to '.session_save_path().'<br>';
2072 echo 'Check that the hostname used to access Shaarli contains a dot. On some browsers, accessing your server via a hostname like \'localhost\' or any custom hostname without a dot causes cookie storage to fail. We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>';
2073 echo '<br><a href="?">Click to try again.</a></pre>';
2060 die; 2074 die;
2061 } 2075 }
2062 if (!isset($_SESSION['session_tested'])) 2076 if (!isset($_SESSION['session_tested']))
@@ -2065,7 +2079,7 @@ function install()
2065 header('Location: '.indexUrl().'?test_session'); // Redirect to check stored data. 2079 header('Location: '.indexUrl().'?test_session'); // Redirect to check stored data.
2066 } 2080 }
2067 if (isset($_GET['test_session'])) 2081 if (isset($_GET['test_session']))
2068 { // Step 3: Sessions are ok. Remove test parameter from URL. 2082 { // Step 3: Sessions are OK. Remove test parameter from URL.
2069 header('Location: '.indexUrl()); 2083 header('Location: '.indexUrl());
2070 } 2084 }
2071 2085
@@ -2083,7 +2097,7 @@ function install()
2083 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 2097 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
2084 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.htmlspecialchars(indexUrl()) : $_POST['title'] ); 2098 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.htmlspecialchars(indexUrl()) : $_POST['title'] );
2085 writeConfig(); 2099 writeConfig();
2086 echo '<script language="JavaScript">alert("Shaarli is now configured. Please enter your login/password and start shaaring your links !");document.location=\'?do=login\';</script>'; 2100 echo '<script language="JavaScript">alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
2087 exit; 2101 exit;
2088 } 2102 }
2089 2103
@@ -2098,14 +2112,14 @@ function install()
2098 exit; 2112 exit;
2099} 2113}
2100 2114
2101// Generates the timezone selection form and javascript. 2115// Generates the timezone selection form and JavaScript.
2102// Input: (optional) current timezone (can be 'UTC/UTC'). It will be pre-selected. 2116// Input: (optional) current timezone (can be 'UTC/UTC'). It will be pre-selected.
2103// Output: array(html,js) 2117// Output: array(html,js)
2104// Example: list($htmlform,$js) = templateTZform('Europe/Paris'); // Europe/Paris pre-selected. 2118// Example: list($htmlform,$js) = templateTZform('Europe/Paris'); // Europe/Paris pre-selected.
2105// Returns array('','') if server does not support timezones list. (eg. php 5.1 on free.fr) 2119// Returns array('','') if server does not support timezones list. (e.g. PHP 5.1 on free.fr)
2106function templateTZform($ptz=false) 2120function templateTZform($ptz=false)
2107{ 2121{
2108 if (function_exists('timezone_identifiers_list')) // because of old php version (5.1) which can be found on free.fr 2122 if (function_exists('timezone_identifiers_list')) // because of old PHP version (5.1) which can be found on free.fr
2109 { 2123 {
2110 // Try to split the provided timezone. 2124 // Try to split the provided timezone.
2111 if ($ptz==false) { $l=timezone_identifiers_list(); $ptz=$l[0]; } 2125 if ($ptz==false) { $l=timezone_identifiers_list(); $ptz=$l[0]; }
@@ -2114,7 +2128,7 @@ function templateTZform($ptz=false)
2114 // Display config form: 2128 // Display config form:
2115 $timezone_form = ''; 2129 $timezone_form = '';
2116 $timezone_js = ''; 2130 $timezone_js = '';
2117 // The list is in the forme "Europe/Paris", "America/Argentina/Buenos_Aires"... 2131 // The list is in the form "Europe/Paris", "America/Argentina/Buenos_Aires"...
2118 // We split the list in continents/cities. 2132 // We split the list in continents/cities.
2119 $continents = array(); 2133 $continents = array();
2120 $cities = array(); 2134 $cities = array();
@@ -2152,9 +2166,9 @@ function templateTZform($ptz=false)
2152function isTZvalid($continent,$city) 2166function isTZvalid($continent,$city)
2153{ 2167{
2154 $tz = $continent.'/'.$city; 2168 $tz = $continent.'/'.$city;
2155 if (function_exists('timezone_identifiers_list')) // because of old php version (5.1) which can be found on free.fr 2169 if (function_exists('timezone_identifiers_list')) // because of old PHP version (5.1) which can be found on free.fr
2156 { 2170 {
2157 if (in_array($tz, timezone_identifiers_list())) // it's a valid timezone ? 2171 if (in_array($tz, timezone_identifiers_list())) // it's a valid timezone?
2158 return true; 2172 return true;
2159 } 2173 }
2160 return false; 2174 return false;
@@ -2197,7 +2211,7 @@ if (!function_exists('json_encode')) {
2197} 2211}
2198 2212
2199// Webservices (for use with jQuery/jQueryUI) 2213// Webservices (for use with jQuery/jQueryUI)
2200// eg. index.php?ws=tags&term=minecr 2214// e.g. index.php?ws=tags&term=minecr
2201function processWS() 2215function processWS()
2202{ 2216{
2203 if (empty($_GET['ws']) || empty($_GET['term'])) return; 2217 if (empty($_GET['ws']) || empty($_GET['term'])) return;
@@ -2205,7 +2219,7 @@ function processWS()
2205 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in). 2219 $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
2206 header('Content-Type: application/json; charset=utf-8'); 2220 header('Content-Type: application/json; charset=utf-8');
2207 2221
2208 // Search in tags (case insentitive, cumulative search) 2222 // Search in tags (case insensitive, cumulative search)
2209 if ($_GET['ws']=='tags') 2223 if ($_GET['ws']=='tags')
2210 { 2224 {
2211 $tags=explode(' ',str_replace(',',' ',$term)); $last = array_pop($tags); // Get the last term ("a b c d" ==> "a b c", "d") 2225 $tags=explode(' ',str_replace(',',' ',$term)); $last = array_pop($tags); // Get the last term ("a b c d" ==> "a b c", "d")
@@ -2221,7 +2235,7 @@ function processWS()
2221 exit; 2235 exit;
2222 } 2236 }
2223 2237
2224 // Search a single tag (case sentitive, single tag search) 2238 // Search a single tag (case sensitive, single tag search)
2225 if ($_GET['ws']=='singletag') 2239 if ($_GET['ws']=='singletag')
2226 { 2240 {
2227 /* To speed up things, we store list of tags in session */ 2241 /* To speed up things, we store list of tags in session */
@@ -2237,13 +2251,14 @@ function processWS()
2237 2251
2238// Re-write configuration file according to globals. 2252// Re-write configuration file according to globals.
2239// Requires some $GLOBALS to be set (login,hash,salt,title). 2253// Requires some $GLOBALS to be set (login,hash,salt,title).
2240// If the config file cannot be saved, an error message is dislayed and the user is redirected to "Tools" menu. 2254// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
2241// (otherwise, the function simply returns.) 2255// (otherwise, the function simply returns.)
2242function writeConfig() 2256function writeConfig()
2243{ 2257{
2244 if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config. 2258 if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
2245 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; '; 2259 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
2246 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';'; 2260 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
2261 $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
2247 $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; '; 2262 $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
2248 $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; '; 2263 $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
2249 $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; '; 2264 $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
@@ -2256,12 +2271,12 @@ function writeConfig()
2256 } 2271 }
2257} 2272}
2258 2273
2259/* Because some f*cking services like Flickr require an extra HTTP request to get the thumbnail URL, 2274/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
2260 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 2275 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
2261 The following function takes the URL a link (eg. a flickr page) and return the proper thumbnail. 2276 The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail.
2262 This function is called by passing the url: 2277 This function is called by passing the URL:
2263 http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL] 2278 http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL]
2264 [URL] is the URL of the link (eg. a flickr page) 2279 [URL] is the URL of the link (e.g. a flickr page)
2265 [HMAC] is the signature for the [URL] (so that these URL cannot be forged). 2280 [HMAC] is the signature for the [URL] (so that these URL cannot be forged).
2266 The function below will fetch the image from the webservice and store it in the cache. 2281 The function below will fetch the image from the webservice and store it in the cache.
2267*/ 2282*/
@@ -2269,7 +2284,7 @@ function genThumbnail()
2269{ 2284{
2270 // Make sure the parameters in the URL were generated by us. 2285 // Make sure the parameters in the URL were generated by us.
2271 $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']); 2286 $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']);
2272 if ($sign!=$_GET['hmac']) die('Naughty boy !'); 2287 if ($sign!=$_GET['hmac']) die('Naughty boy!');
2273 2288
2274 // Let's see if we don't already have the image for this URL in the cache. 2289 // Let's see if we don't already have the image for this URL in the cache.
2275 $thumbname=hash('sha1',$_GET['url']).'.jpg'; 2290 $thumbname=hash('sha1',$_GET['url']).'.jpg';
@@ -2294,22 +2309,22 @@ function genThumbnail()
2294 2309
2295 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')) 2310 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com'))
2296 { 2311 {
2297 // Crude replacement to handle new Flickr domain policy (They prefer www. now) 2312 // Crude replacement to handle new flickr domain policy (They prefer www. now)
2298 $url = str_replace('http://flickr.com/','http://www.flickr.com/',$url); 2313 $url = str_replace('http://flickr.com/','http://www.flickr.com/',$url);
2299 2314
2300 // Is this a link to an image, or to a flickr page ? 2315 // Is this a link to an image, or to a flickr page ?
2301 $imageurl=''; 2316 $imageurl='';
2302 if (endswith(parse_url($url,PHP_URL_PATH),'.jpg')) 2317 if (endswith(parse_url($url,PHP_URL_PATH),'.jpg'))
2303 { // This is a direct link to an image. eg. http://farm1.staticflickr.com/5/5921913_ac83ed27bd_o.jpg 2318 { // This is a direct link to an image. e.g. http://farm1.staticflickr.com/5/5921913_ac83ed27bd_o.jpg
2304 preg_match('!(http://farm\d+\.staticflickr\.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches); 2319 preg_match('!(http://farm\d+\.staticflickr\.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches);
2305 if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; 2320 if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg';
2306 } 2321 }
2307 else // this is a flickr page (html) 2322 else // This is a flickr page (html)
2308 { 2323 {
2309 list($httpstatus,$headers,$data) = getHTTP($url,20); // Get the flickr html page. 2324 list($httpstatus,$headers,$data) = getHTTP($url,20); // Get the flickr html page.
2310 if (strpos($httpstatus,'200 OK')!==false) 2325 if (strpos($httpstatus,'200 OK')!==false)
2311 { 2326 {
2312 // Flickr now nicely provides the URL of the thumbnail in each flickr page. 2327 // flickr now nicely provides the URL of the thumbnail in each flickr page.
2313 preg_match('!<link rel=\"image_src\" href=\"(.+?)\"!',$data,$matches); 2328 preg_match('!<link rel=\"image_src\" href=\"(.+?)\"!',$data,$matches);
2314 if (!empty($matches[1])) $imageurl=$matches[1]; 2329 if (!empty($matches[1])) $imageurl=$matches[1];
2315 2330
@@ -2340,9 +2355,9 @@ function genThumbnail()
2340 elseif ($domain=='vimeo.com' ) 2355 elseif ($domain=='vimeo.com' )
2341 { 2356 {
2342 // This is more complex: we have to perform a HTTP request, then parse the result. 2357 // This is more complex: we have to perform a HTTP request, then parse the result.
2343 // Maybe we should deport this to javascript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098 2358 // Maybe we should deport this to JavaScript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098
2344 $vid = substr(parse_url($url,PHP_URL_PATH),1); 2359 $vid = substr(parse_url($url,PHP_URL_PATH),1);
2345 list($httpstatus,$headers,$data) = getHTTP('http://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5); 2360 list($httpstatus,$headers,$data) = getHTTP('https://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5);
2346 if (strpos($httpstatus,'200 OK')!==false) 2361 if (strpos($httpstatus,'200 OK')!==false)
2347 { 2362 {
2348 $t = unserialize($data); 2363 $t = unserialize($data);
@@ -2476,7 +2491,7 @@ function resizeImage($filepath)
2476} 2491}
2477 2492
2478// Invalidate caches when the database is changed or the user logs out. 2493// Invalidate caches when the database is changed or the user logs out.
2479// (eg. tags cache). 2494// (e.g. tags cache).
2480function invalidateCaches() 2495function invalidateCaches()
2481{ 2496{
2482 unset($_SESSION['tags']); // Purge cache attached to session. 2497 unset($_SESSION['tags']); // Purge cache attached to session.
diff --git a/pagecache/.htaccess b/pagecache/.htaccess
new file mode 100644
index 00000000..b584d98c
--- /dev/null
+++ b/pagecache/.htaccess
@@ -0,0 +1,2 @@
1Allow from none
2Deny from all
diff --git a/shaarli_version.txt b/shaarli_version.txt
new file mode 100644
index 00000000..54049091
--- /dev/null
+++ b/shaarli_version.txt
@@ -0,0 +1 @@
0.0.42 beta
diff --git a/tmp/.htaccess b/tmp/.htaccess
new file mode 100644
index 00000000..b584d98c
--- /dev/null
+++ b/tmp/.htaccess
@@ -0,0 +1,2 @@
1Allow from none
2Deny from all
diff --git a/tpl/configure.html b/tpl/configure.html
index 62296cb2..645107ae 100644
--- a/tpl/configure.html
+++ b/tpl/configure.html
@@ -11,6 +11,7 @@
11 11
12 <tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr> 12 <tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr>
13 13
14 <tr><td><b>Title link:</b></td><td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label for="titleLink">(default value is: ?)</label></td></tr>
14 <tr><td valign="top"><b>Timezone:</b></td><td valign="top">{$timezone_form}</td></tr> 15 <tr><td valign="top"><b>Timezone:</b></td><td valign="top">{$timezone_form}</td></tr>
15 16
16 <tr><td valign="top"><b>Redirector</b></td><td><input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>(e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)</td></tr> 17 <tr><td valign="top"><b>Redirector</b></td><td><input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>(e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)</td></tr>
@@ -18,7 +19,7 @@
18 <tr><td valign="top"><b>Security:</b></td><td><input type="checkbox" name="disablesessionprotection" id="disablesessionprotection" {if="!empty($GLOBALS['disablesessionprotection'])"}checked{/if}><label for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get disconnected often or if your IP address changes often.)</label></td></tr> 19 <tr><td valign="top"><b>Security:</b></td><td><input type="checkbox" name="disablesessionprotection" id="disablesessionprotection" {if="!empty($GLOBALS['disablesessionprotection'])"}checked{/if}><label for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get disconnected often or if your IP address changes often.)</label></td></tr>
19 20
20 <tr><td valign="top"><b>Features:</b></td><td> 21 <tr><td valign="top"><b>Features:</b></td><td>
21 <input type="checkbox" name="disablejquery" id="disablejquery" {if="!empty($GLOBALS['disablejquery'])"}checked{/if}><label for="disablejquery">&nbsp;Disable jQuery and all heavy javascript (for example: Autocomplete in tags. Useful for slow computers.)</label> 22 <input type="checkbox" name="disablejquery" id="disablejquery" {if="!empty($GLOBALS['disablejquery'])"}checked{/if}><label for="disablejquery">&nbsp;Disable jQuery and all heavy JavaScript (for example: Autocomplete in tags. Useful for slow computers.)</label>
22 </td></tr> 23 </td></tr>
23 <tr><td valign="top"><b>New link:</b></td><td> 24 <tr><td valign="top"><b>New link:</b></td><td>
24 <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault">&nbsp;All new link are private by default</label></td> 25 <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault">&nbsp;All new link are private by default</label></td>
diff --git a/tpl/import.html b/tpl/import.html
index 9e581fc9..259e56ee 100644
--- a/tpl/import.html
+++ b/tpl/import.html
@@ -5,7 +5,7 @@
5<div id="pageheader"> 5<div id="pageheader">
6 {include="page.header"} 6 {include="page.header"}
7 <div id="uploaddiv"> 7 <div id="uploaddiv">
8 Import Netscape html bookmarks (as exported from Firefox/Chrome/Opera/delicious/diigo...) (Max: {$maxfilesize|htmlspecialchars} bytes). 8 Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize|htmlspecialchars} bytes).
9 <form method="POST" action="?do=upload" enctype="multipart/form-data" name="uploadform" id="uploadform"> 9 <form method="POST" action="?do=upload" enctype="multipart/form-data" name="uploadform" id="uploadform">
10 <input type="hidden" name="token" value="{$token}"> 10 <input type="hidden" name="token" value="{$token}">
11 <input type="file" name="filetoupload" size="80"> 11 <input type="file" name="filetoupload" size="80">
diff --git a/tpl/linklist.html b/tpl/linklist.html
index 5a742737..acb4bab0 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -48,6 +48,9 @@
48 {else} 48 {else}
49 <span class="linkdate" title="Short link here"><a href="?{$value.linkdate|smallHash}">permalink</a> - </span> 49 <span class="linkdate" title="Short link here"><a href="?{$value.linkdate|smallHash}">permalink</a> - </span>
50 {/if} 50 {/if}
51 {if="$GLOBALS['config']['ARCHIVE_ORG']"}
52 <span class="linkarchive"><a href="https://web.archive.org/web/{$value.url|htmlspecialchars}">archive</a> - </span>
53 {/if}
51 <div style="position:relative;display:inline;"><a href="http://qrfree.kaywa.com/?l=1&s=8&d={$scripturl|urlencode}%3F{$value.linkdate|smallHash}" 54 <div style="position:relative;display:inline;"><a href="http://qrfree.kaywa.com/?l=1&s=8&d={$scripturl|urlencode}%3F{$value.linkdate|smallHash}"
52 onclick="showQrCode(this); return false;" class="qrcode" data-permalink="{$scripturl}?{$value.linkdate|smallHash}"><img src="images/qrcode.png#" width="13" height="13" title="QR-Code"></a></div> - 55 onclick="showQrCode(this); return false;" class="qrcode" data-permalink="{$scripturl}?{$value.linkdate|smallHash}"><img src="images/qrcode.png#" width="13" height="13" title="QR-Code"></a></div> -
53 <span class="linkurl" title="Short link">{$value.url|htmlspecialchars}</span><br> 56 <span class="linkurl" title="Short link">{$value.url|htmlspecialchars}</span><br>
diff --git a/tpl/page.header.html b/tpl/page.header.html
index 125b365b..654a551d 100644
--- a/tpl/page.header.html
+++ b/tpl/page.header.html
@@ -2,7 +2,7 @@
2 <div id="logo" title="Share your links !" onclick="document.location='?';"></div> 2 <div id="logo" title="Share your links !" onclick="document.location='?';"></div>
3 <div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;" class="nomobile">Shaare your links...<br> 3 <div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;" class="nomobile">Shaare your links...<br>
4 {if="!empty($linkcount)"}{$linkcount} links{/if}</div> 4 {if="!empty($linkcount)"}{$linkcount} links{/if}</div>
5 <span id="shaarli_title"><a href="?">{$shaarlititle|htmlspecialchars}</a></span> 5 <span id="shaarli_title"><a href="{$titleLink}">{$shaarlititle|htmlspecialchars}</a></span>
6 6
7{if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"} 7{if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"}
8 {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} 8 {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
@@ -16,7 +16,9 @@
16 <a href="?do=login">Login</a> 16 <a href="?do=login">Login</a>
17 {/if} 17 {/if}
18 <a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a> 18 <a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a>
19 <a href="{$feedurl}?do=atom{$searchcrits}" style="padding-left:10px;" class="nomobile">ATOM Feed</a> 19 {if="$GLOBALS['config']['SHOW_ATOM']"}
20 <a href="{$feedurl}?do=atom{$searchcrits}" style="padding-left:10px;" class="nomobile">ATOM Feed</a>
21 {/if}
20 <a href="?do=tagcloud">Tag cloud</a> 22 <a href="?do=tagcloud">Tag cloud</a>
21 <a href="?do=picwall{$searchcrits}">Picture wall</a> 23 <a href="?do=picwall{$searchcrits}">Picture wall</a>
22 <a href="?do=daily">Daily</a> 24 <a href="?do=daily">Daily</a>
diff --git a/tpl/tagcloud.html b/tpl/tagcloud.html
index 6918c7be..0dd2c0d3 100644
--- a/tpl/tagcloud.html
+++ b/tpl/tagcloud.html
@@ -6,7 +6,7 @@
6<center> 6<center>
7<div id="cloudtag"> 7<div id="cloudtag">
8 {loop="tags"} 8 {loop="tags"}
9 <span style="color:#99f; font-size:9pt; padding-left:5px; padding-right:2px;">{$value.count}</span><a href="?searchtags={$key|htmlspecialchars}" style="font-size:{$value.size}pt; font-weight:bold; color:black; text-decoration:none">{$key|htmlspecialchars}</a> 9 <span style="color:#99f; font-size:9pt; padding-left:5px; padding-right:2px;">{$value.count}</span><a href="?searchtags={$key|urlencode}" style="font-size:{$value.size}pt; font-weight:bold; color:black; text-decoration:none">{$key|htmlspecialchars}</a>
10 {/loop} 10 {/loop}
11</div> 11</div>
12</center> 12</center>
diff --git a/tpl/tools.html b/tpl/tools.html
index 48ecc97e..ba1c1e8e 100644
--- a/tpl/tools.html
+++ b/tpl/tools.html
@@ -10,7 +10,7 @@
10 <a href="?do=changetag"><b>Rename/delete tags</b> <span>: Rename or delete a tag in all links</span></a><br><br> 10 <a href="?do=changetag"><b>Rename/delete tags</b> <span>: Rename or delete a tag in all links</span></a><br><br>
11 <a href="?do=import"><b>Import</b> <span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> <br><br> 11 <a href="?do=import"><b>Import</b> <span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> <br><br>
12 <a href="?do=export"><b>Export</b> <span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a><br><br> 12 <a href="?do=export"><b>Export</b> <span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a><br><br>
13 <a class="smallbutton" onclick="alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');return false;" href="javascript:javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;window.open('{$pageabsaddr}?post='%20+%20encodeURIComponent(url)+'&amp;title='%20+%20encodeURIComponent(title)+'&amp;source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1');})();"><b>Shaare link</b></a> <a href="#" style="clear:none;"><span>&#x21D0; Drag this link to your bookmarks toolbar (or right-click it and choose Bookmark This Link....).<br>&nbsp;&nbsp;&nbsp;&nbsp;Then click "Shaare link" button in any page you want to share.</span></a><br><br> 13 <a class="smallbutton" onclick="alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');return false;" href="javascript:javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;window.open('{$pageabsaddr}?post='%20+%20encodeURIComponent(url)+'&amp;title='%20+%20encodeURIComponent(title)+'&amp;description='%20+%20encodeURIComponent(document.getSelection())+'&amp;source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1');})();"><b>Shaare link</b></a> <a href="#" style="clear:none;"><span>&#x21D0; Drag this link to your bookmarks toolbar (or right-click it and choose Bookmark This Link....).<br>&nbsp;&nbsp;&nbsp;&nbsp;Then click "Shaare link" button in any page you want to share.</span></a><br><br>
14 <div class="clear"></div> 14 <div class="clear"></div>
15 </div> 15 </div>
16</div> 16</div>