aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2017-05-07 19:17:33 +0200
committerArthurHoaro <arthur@hoa.ro>2017-05-07 19:17:33 +0200
commit01e942d44c7194607649817216aeb5d65c6acad6 (patch)
tree15777aa1005251f119e6dd680291147117766b5b
parentbc22c9a0acb095970e9494cbe8954f0612e05dc0 (diff)
parent8868f3ca461011a8fb6dd9f90b60ed697ab52fc5 (diff)
downloadShaarli-01e942d44c7194607649817216aeb5d65c6acad6.tar.gz
Shaarli-01e942d44c7194607649817216aeb5d65c6acad6.tar.zst
Shaarli-01e942d44c7194607649817216aeb5d65c6acad6.zip
Merge tag 'v0.8.4' into stable
Release v0.8.4
-rw-r--r--.gitattributes1
-rw-r--r--.gitignore16
-rw-r--r--.travis.yml6
-rw-r--r--CHANGELOG.md920
-rw-r--r--COPYING4
-rw-r--r--Makefile37
-rw-r--r--README.md4
-rw-r--r--application/.htaccess15
-rw-r--r--application/ApplicationUtils.php32
-rw-r--r--application/CachedPage.php2
-rw-r--r--application/Config.php221
-rw-r--r--application/FeedBuilder.php46
-rw-r--r--application/FileUtils.php8
-rw-r--r--application/HttpUtils.php186
-rw-r--r--application/Languages.php21
-rw-r--r--application/LinkDB.php318
-rw-r--r--application/LinkFilter.php70
-rw-r--r--application/LinkUtils.php91
-rw-r--r--application/NetscapeBookmarkUtils.php139
-rw-r--r--application/PageBuilder.php50
-rw-r--r--application/PluginManager.php83
-rw-r--r--application/Router.php2
-rw-r--r--application/TimeZone.php10
-rw-r--r--application/Updater.php177
-rw-r--r--application/Url.php15
-rw-r--r--application/Utils.php69
-rw-r--r--application/config/ConfigIO.php33
-rw-r--r--application/config/ConfigJson.php78
-rw-r--r--application/config/ConfigManager.php394
-rw-r--r--application/config/ConfigPhp.php132
-rw-r--r--application/config/ConfigPlugin.php124
-rw-r--r--cache/.htaccess15
-rw-r--r--composer.json14
-rw-r--r--data/.htaccess15
-rw-r--r--doc/3rd-party-libraries.html9
-rw-r--r--doc/3rd-party-libraries.md1
-rw-r--r--doc/Backup,-restore,-import-and-export.html34
-rw-r--r--doc/Backup,-restore,-import-and-export.md31
-rw-r--r--doc/Browsing-and-searching.html8
-rw-r--r--doc/Coding-guidelines.html8
-rw-r--r--doc/Community-&-Related-software.html17
-rw-r--r--doc/Community-&-Related-software.md9
-rw-r--r--doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html58
-rw-r--r--doc/Create-and-serve-multiple-Shaarlis-(farm).html52
-rw-r--r--doc/Datastore-hacks.html8
-rw-r--r--doc/Development.html8
-rw-r--r--doc/Directory-structure.html60
-rw-r--r--doc/Docker.html104
-rw-r--r--doc/Download-CSS-styles-from-an-OPML-list.html12
-rw-r--r--doc/Download-and-Installation.html (renamed from doc/Download.html)93
-rw-r--r--doc/Download-and-Installation.md102
-rw-r--r--doc/Download.md31
-rw-r--r--doc/Example-patch---add-new-via-field-for-links.html8
-rw-r--r--doc/FAQ.html8
-rw-r--r--doc/Firefox-share.html8
-rw-r--r--doc/GnuPG-signature.html58
-rw-r--r--doc/Home.html8
-rw-r--r--doc/Plugin-System.html8
-rw-r--r--doc/Plugins.html8
-rw-r--r--doc/RSS-feeds.html8
-rw-r--r--doc/Release-Shaarli.html106
-rw-r--r--doc/Release-Shaarli.md64
-rw-r--r--doc/Security.html8
-rw-r--r--doc/Server-configuration.html74
-rw-r--r--doc/Server-configuration.md56
-rw-r--r--doc/Server-requirements.html46
-rw-r--r--doc/Server-requirements.md19
-rw-r--r--doc/Server-security.html28
-rw-r--r--doc/Server-security.md14
-rw-r--r--doc/Shaarli-configuration.html277
-rw-r--r--doc/Shaarli-configuration.md298
-rw-r--r--doc/Shaarli-installation.html72
-rw-r--r--doc/Shaarli-installation.md6
-rw-r--r--doc/Sharing-button.html8
-rw-r--r--doc/Static-analysis.html8
-rw-r--r--doc/TODO.html74
-rw-r--r--doc/TODO.md4
-rw-r--r--doc/Theming.html32
-rw-r--r--doc/Theming.md10
-rw-r--r--doc/Troubleshooting.html11
-rw-r--r--doc/Troubleshooting.md3
-rw-r--r--doc/Unit-tests.html110
-rw-r--r--doc/Upgrade-and-migration.html242
-rw-r--r--doc/Upgrade-and-migration.md161
-rw-r--r--doc/Upgrade-from-original-sebsauvage-Shaarli.html74
-rw-r--r--doc/Upgrade-from-original-sebsauvage-Shaarli.md4
-rw-r--r--doc/Usage.html8
-rw-r--r--doc/_Footer.html12
-rw-r--r--doc/_Footer.md2
-rw-r--r--doc/_Sidebar.html16
-rw-r--r--doc/_Sidebar.md8
-rw-r--r--doc/sidebar.html8
-rw-r--r--docker/.htaccess15
-rw-r--r--docker/development/Dockerfile32
-rw-r--r--docker/development/nginx.conf7
-rw-r--r--docker/production/Dockerfile25
-rw-r--r--docker/production/nginx.conf7
-rw-r--r--docker/production/stable/Dockerfile25
-rw-r--r--docker/production/stable/nginx.conf7
-rw-r--r--inc/shaarli.css13
-rw-r--r--index.php962
-rw-r--r--pagecache/.htaccess15
-rw-r--r--plugins/addlink_toolbar/addlink_toolbar.css4
-rw-r--r--plugins/addlink_toolbar/addlink_toolbar.html6
-rw-r--r--plugins/addlink_toolbar/addlink_toolbar.php24
-rw-r--r--plugins/archiveorg/archiveorg.html2
-rw-r--r--plugins/archiveorg/archiveorg.php3
-rw-r--r--plugins/demo_plugin/custom_demo.css4
-rw-r--r--plugins/demo_plugin/demo_plugin.php99
-rw-r--r--plugins/isso/isso.css3
-rw-r--r--plugins/isso/isso.html14
-rw-r--r--plugins/isso/isso.meta3
-rw-r--r--plugins/isso/isso.php54
-rw-r--r--plugins/markdown/Parsedown.php1528
-rw-r--r--plugins/markdown/README.md51
-rw-r--r--plugins/markdown/markdown.meta5
-rw-r--r--plugins/markdown/markdown.php105
-rw-r--r--plugins/piwik/piwik.meta4
-rw-r--r--plugins/piwik/piwik.php71
-rw-r--r--plugins/playvideos/playvideos.html1
-rw-r--r--plugins/playvideos/playvideos.php12
-rw-r--r--plugins/qrcode/qrcode.html2
-rw-r--r--plugins/readityourself/config.php.dist3
-rw-r--r--plugins/readityourself/readityourself.html2
-rw-r--r--plugins/readityourself/readityourself.php34
-rw-r--r--plugins/wallabag/README.md41
-rw-r--r--plugins/wallabag/config.php.dist4
-rw-r--r--plugins/wallabag/wallabag.html2
-rw-r--r--plugins/wallabag/wallabag.meta4
-rw-r--r--plugins/wallabag/wallabag.php38
-rw-r--r--shaarli_version.php2
-rw-r--r--tests/.htaccess15
-rw-r--r--tests/ApplicationUtilsTest.php57
-rw-r--r--tests/CacheTest.php3
-rw-r--r--tests/ConfigTest.php244
-rw-r--r--tests/FeedBuilderTest.php77
-rw-r--r--tests/HttpUtils/GetIpAdressFromProxyTest.php58
-rw-r--r--tests/LanguagesTest.php41
-rw-r--r--tests/LinkDBTest.php59
-rw-r--r--tests/LinkFilterTest.php32
-rw-r--r--tests/LinkUtilsTest.php88
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkExportTest.php (renamed from tests/NetscapeBookmarkUtilsTest.php)10
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkImportTest.php590
-rw-r--r--tests/NetscapeBookmarkUtils/input/empty.htm0
-rw-r--r--tests/NetscapeBookmarkUtils/input/internet_explorer_encoding.htm9
-rw-r--r--tests/NetscapeBookmarkUtils/input/netscape_basic.htm11
-rw-r--r--tests/NetscapeBookmarkUtils/input/netscape_nested.htm31
-rw-r--r--tests/NetscapeBookmarkUtils/input/no_doctype.htm7
-rw-r--r--tests/NetscapeBookmarkUtils/input/same_date.htm11
-rw-r--r--tests/PluginManagerTest.php46
-rw-r--r--tests/Updater/DummyUpdater.php12
-rw-r--r--tests/Updater/UpdaterTest.php347
-rw-r--r--tests/Url/UrlTest.php5
-rw-r--r--tests/UtilsTest.php37
-rw-r--r--tests/config/ConfigJsonTest.php133
-rw-r--r--tests/config/ConfigManagerTest.php172
-rw-r--r--tests/config/ConfigPhpTest.php82
-rw-r--r--tests/config/ConfigPluginTest.php121
-rw-r--r--tests/plugins/PluginArchiveorgTest.php102
-rw-r--r--tests/plugins/PluginIssoTest.php151
-rw-r--r--tests/plugins/PluginMarkdownTest.php99
-rw-r--r--tests/plugins/PluginReadityourselfTest.php31
-rw-r--r--tests/plugins/PluginWallabagTest.php29
-rw-r--r--tests/plugins/resources/markdown.html24
-rw-r--r--tests/plugins/resources/markdown.md24
-rw-r--r--tests/plugins/test/test.meta4
-rw-r--r--tests/utils/ReferenceLinkDB.php77
-rw-r--r--tests/utils/config/configInvalid.json.php5
-rw-r--r--tests/utils/config/configJson.json.php34
-rw-r--r--tests/utils/config/configPhp.php14
-rw-r--r--tmp/.htaccess15
-rw-r--r--tpl/configure.html114
-rw-r--r--tpl/daily.html12
-rw-r--r--tpl/dailyrss.html4
-rw-r--r--tpl/editlink.html37
-rw-r--r--tpl/export.bookmarks.html2
-rw-r--r--tpl/feed.atom.html9
-rw-r--r--tpl/feed.rss.html5
-rw-r--r--tpl/import.html38
-rw-r--r--tpl/includes.html3
-rw-r--r--tpl/linklist.html33
-rw-r--r--tpl/linklist.paging.html9
-rw-r--r--tpl/loginform.html4
-rw-r--r--tpl/page.header.html13
-rw-r--r--tpl/picwall.html2
-rw-r--r--tpl/pluginsadmin.html11
-rw-r--r--tpl/tagcloud.html2
-rw-r--r--tpl/tools.html10
188 files changed, 8070 insertions, 4348 deletions
diff --git a/.gitattributes b/.gitattributes
index aaf6a39e..d753b1db 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -21,7 +21,6 @@ Dockerfile text
21.gitattributes export-ignore 21.gitattributes export-ignore
22.gitignore export-ignore 22.gitignore export-ignore
23.travis.yml export-ignore 23.travis.yml export-ignore
24composer.json export-ignore
25doc/**/*.json export-ignore 24doc/**/*.json export-ignore
26doc/**/*.md export-ignore 25doc/**/*.md export-ignore
27docker/ export-ignore 26docker/ export-ignore
diff --git a/.gitignore b/.gitignore
index 75cd3a6b..095aaded 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
1# Ignore data/, tmp/, cache/ and pagecache/ 1# Shaarli runtime resources
2data 2data
3tmp 3tmp
4cache 4cache
@@ -9,18 +9,22 @@ pagecache
9.buildpath 9.buildpath
10.project 10.project
11 11
12# Ignore raintpl generated pages 12# Raintpl generated pages
13*.rtpl.php 13*.rtpl.php
14 14
15# Ignore test dependencies 15# 3rd-party dependencies
16composer.lock 16composer.lock
17/vendor/ 17vendor/
18 18
19# Ignore development and test resources 19# Release archives
20*.tar
21*.zip
22
23# Development and test resources
20coverage 24coverage
21doxygen 25doxygen
22sandbox 26sandbox
23phpmd.html 27phpmd.html
24 28
25# Ignore user plugin configuration 29# User plugin configuration
26plugins/*/config.php 30plugins/*/config.php
diff --git a/.travis.yml b/.travis.yml
index 7408b2e2..6ff1b20f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,10 @@
1sudo: false 1sudo: false
2language: php 2language: php
3cache:
4 directories:
5 - $HOME/.composer/cache
3php: 6php:
7 - 7.1
4 - 7.0 8 - 7.0
5 - 5.6 9 - 5.6
6 - 5.5 10 - 5.5
@@ -8,7 +12,7 @@ php:
8 - 5.3 12 - 5.3
9install: 13install:
10 - composer self-update 14 - composer self-update
11 - composer install 15 - composer install --prefer-dist
12script: 16script:
13 - make clean 17 - make clean
14 - make check_permissions 18 - make check_permissions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..1340db56
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,920 @@
1# Change Log
2All notable changes to this project will be documented in this file.
3
4The format is based on [Keep a Changelog](http://keepachangelog.com/)
5and this project adheres to [Semantic Versioning](http://semver.org/).
6
7
8## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED
9
10### Added
11
12### Changed
13
14### Fixed
15
16## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
17### Security
18- Markdown plugin: escape HTML entities by default
19
20
21## [v0.8.3](https://github.com/shaarli/Shaarli/releases/tag/v0.8.3) - 2017-01-20
22
23### Fixed
24
25- PHP 7.1 compatibility: add ConfigManager parameter to anti-bruteforce function call in login template.
26
27## [v0.8.2](https://github.com/shaarli/Shaarli/releases/tag/v0.8.2) - 2016-12-15
28
29### Fixed
30
31- Editing a link created before the new ID system would change its permalink.
32
33## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12
34
35> Note: this version will create an automatic backup of your database if anything goes wrong.
36
37### Added
38- Add CHANGELOG.md to track the whole project's history
39- Enable Composer cache for Travis builds
40- Save the last edition date for shaares and use it in Atom/RSS feeds
41- Plugins:
42 - Add an [Isso](https://posativ.org/isso/) plugin to enable user comments on permalinks
43 - Allow defining init functions, e.g. for performing checks and error processing
44 - Add a Piwik plugin for analytics.
45 - Markdown: add warning notice regarding HTML rendering
46- Meta tag to *not* send the referrer to external resources.
47
48### Changed
49- Link ID complete refactoring:
50 - Links now have a numeric ID instead of dates
51 - Short URLs are now created once and can't change over time (previous URL are kept)
52- Templates:
53 - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin`
54 - Cleanup `{loop}` declarations in templates
55 - Tools: hide Firefox Social button when not in HTTPS
56 - Firefox Social: show Shaarli's title when shaaring using Firefox Social
57- Release archives now have the same structure as GitHub-generated archives:
58 - archives contain a `Shaarli` directory, itself containing sources + dependencies
59 - the tarball is now gzipped
60- Plugins:
61 - Markdown: Parsedown library is now imported through Composer
62- Minor code cleanup: PHPDoc, spelling, unused variables, etc.
63- Docker: explicitly set the maximum file upload size to 10 MiB
64
65### Fixed
66- Fix the server `<self>` value in Atom/RSS feeds
67- Plugins:
68 - Tools: only display parameter description when it exists
69 - archive.org: do not propose archival of private notes
70 - Markdown:
71 - render links properly in code blocks
72 - bug regarding the `nomarkdown` tag
73 - W3C compliance
74- Use absolute URL for hashtags in RSS and ATOM feeds
75- Docker: specify the location of the favicon
76- ATOM feed: remove new line between content tag and data
77
78### Security
79- Allow whitelisting trusted IPs, else continue banning clients upon login failure
80
81
82## [v0.8.0](https://github.com/shaarli/Shaarli/releases/tag/v0.8.0) - 2016-10-12
83Shaarli now uses [Composer](https://getcomposer.org/) to handle its dependencies.
84Please use our release archives, or follow the
85[installation documentation](https://github.com/shaarli/Shaarli/wiki/Download-and-Installation).
86
87### Added
88- Composer is required to resolve Shaarli's PHP dependencies
89- Shaarli now supports `#hashtags`
90- Firefox social share now uses selected text as a description
91- Plugin parameters can have a description in each plugin's `.meta` file
92
93### Changed
94- Configuration is now stored as a JSON file
95- Previous configuration format will be automatically updated (PHP -> JSON)
96- Shaarli now defaults to cURL to fetch shaare titles
97- URL cleanup: remove `PHPSESSID` parameter
98- `nomarkdown` tag is no longer private, and now affects visitors
99- Cleanup template indentation
100- Rewrite bookmark import using a generic Netscape parser
101
102### Removed
103- Shaarli no longer references Delicious in its description
104
105### Deprecated
106- Shaarli configuration is not held as PHP globals anymore
107
108### Fixed
109- Ignore case for tags in autocompletion and cloud tag
110- Avoid generating empty tags
111- Fix a Dockerfile syntax error
112
113### Security
114- Fixed a bug preventing to change password
115- XSRF token now generated each time a page is rendered
116
117
118## [v0.7.0](https://github.com/shaarli/Shaarli/releases/tag/v0.7.0) - 2016-05-14
119### Added
120- Adds an option to encode redirector URL parameter
121- Atom/RSS feeds now support Markdown formatting, and plugins in general
122- Markdown: use the tag `.nomarkdown` to avoid markdown processing
123- Prefill the login field when the authentication has failed
124- Show a private links counter
125
126### Changed
127- Allow to use the bookmarklet in Firefox reader view (URL clean up)
128- Improve tagcloud font size
129- Improve title retrieving
130- Markdown: inline code background color
131- Refactor Netscape bookmark export
132- Refactor Atom/RSS feed generation
133
134### Removed
135- Remove delicious from Shaarli description
136
137### Fixed
138- Fix bad login redirections causing a 404 in a few cases
139- Fix tagcloud font-size with French locale
140- Don't display empty tags in tag search
141- Fix Awesomeplete conflicts with jQuery
142- Fix UTC timezone selection
143- Fix a bug preventing to import notes in browsers from bookmarks export
144- Don't redirect to ?post if ?addlink is reached while logged out
145
146
147## [v0.6.5](https://github.com/shaarli/Shaarli/releases/tag/v0.6.5) - 2016-03-02
148### Fixed
149- Fixes a regression generating an unnecessary warning (language in HTTP request)
150- Fixes a bug where going through multiple reverse proxy could generate malformed URL
151- Markdown: Fixes a bug where empty description blocks were displayed
152
153
154## [v0.6.4](https://github.com/shaarli/Shaarli/releases/tag/v0.6.4) - 2016-02-28
155### Added
156- Add an updater class to automate user data upgrades
157- Plugin admin page: adds a label for checkboxes and improve name display
158- Plugin Wallabag: API version can be specified in plugin admin page
159
160### Changed
161- Better tag cloud sorting, including special chars (`a > E > é > z`)
162- Autolocale now sets all locale categories, not just time
163- Use PHP's DateTime object instead of custom functions
164- Plugin hooks: process includes before header/footer
165- Markdown plugin: better styles for `<code>` and `<pre>` tags
166- Improve searching:
167 - search terms are now considered separated and won't only return exact results anymore
168 - exact search can be done with quotes `"this exact sentence"`
169 - search supports excluded terms starting a dash `-exclude`
170 - implement crossed search: terms + tags
171 - all of them combined across all shaare fields
172- New tag behaviour:
173 - tags starting with a dash will be renamed without it
174 - tags starting with a dot `.` will be hidden unless the user is logged in
175
176### Fixed
177- Fix Markdown plugin escape issues (code/quote blocks, etc.)
178- Link description aren't trimmed anymore to allow markdown format at the beginning of a shaare
179- Fixes plugin admin redirection page on error
180
181### Security
182- Fix a bug where non initialized variables were causing a warning
183- Fix a bug where saving a link after edit could cause a 404 error
184
185
186## [v0.6.3](https://github.com/shaarli/Shaarli/releases/tag/v0.6.3) - 2016-01-31
187### Added
188- Plugins administration page
189- Markdown plugin added for shaares description
190- Docker: Dockerfile is now in the main git repository and improved
191- Add a `.gitattributes` to ease repository management
192- Travis: include file permission checks
193
194### Changed
195- Auto retrieve of title know works with websites (HTTPS, follow redirections, etc.)
196- 404 page is now handled in a template
197- Date in log files updated to work with fail2ban
198- Wallabag: support of Wallabag v2 and minor fixes
199- Link search refactoring
200- Logging function refactoring
201
202### Fixed
203- Fix a bug where renaming a tag was causing a 404
204- Fix a bug allowing to search blank terms
205- Fix a bug preventing to remove a tag with special chars when searching
206
207
208## [v0.6.2](https://github.com/shaarli/Shaarli/releases/tag/v0.6.2) - 2015-12-23
209### Changed
210- Plugins: new footer hook
211- Plugins: improve QR code
212- Cleanup templates
213
214### Fixed
215- Plugins: use the actual link URL to generate QR codes
216- Templates: missing/erroneous page titles
217- Templates: missing variables resulting in PHP errors
218
219### Security
220- Fix invalid file permissions (remove executable bit)
221
222
223## [v0.6.1](https://github.com/shaarli/Shaarli/releases/tag/v0.6.1) - 2015-12-01
224### Added
225- Add OpenSearch support
226- Add a Doxygen makefile target
227- Tools: add fine-grained file/directory permission checks (installation)
228
229### Changed
230- Tools: check the 'stable' branch for new versions (updates)
231- Cleanup: introduce an `ApplicationUtils` class
232
233### Removed
234 - Cleanup: remove `json_encode()` function (built-in since PHP 5.2)
235
236### Fixed
237 - Auto-complete more than one tag
238 - Bookmarklet: support titles containing quotes
239 - URL encode links when setting a redirector
240
241
242## [v0.6.0](https://github.com/shaarli/Shaarli/releases/tag/v0.6.0) - 2015-11-18
243### Added
244- Introduce a plugin system
245- Add a demo_plugin
246- Add plugins:
247 - addlink_toolbar
248 - archiveorg
249 - playvideos
250 - qrcode
251 - readityourself
252 - wallabag
253
254### Changed
255- Coding style
256
257### Fixed
258- Adding a new link now returns the correct anchor in the URL
259- Set default file permissions
260
261
262## [v0.5.4](https://github.com/shaarli/Shaarli/releases/tag/v0.5.4) - 2015-09-15
263### Added
264- HTTPS: support being served behing an SSL-enabled proxy
265
266### Changed
267- HTTP/Server utilities: refactor & add test coverage
268- Project & documentation:
269 - improve/rewrite `README.md`
270 - update contributor list
271 - update `index.php` header
272
273### Fixed
274- PHP session IDs: handle hash algorithms and bits per char representations
275
276
277## [v0.5.3](https://github.com/shaarli/Shaarli/releases/tag/v0.5.3) - 2015-09-02
278### Fixed
279- Fix a bug that could prevent user to login
280
281
282## [0.5.3](https://github.com/shaarli/Shaarli/releases/tag/0.5.3) - 2015-09-02
283This release has been YANKED as it points to a tag that does not follow our naming convention. Please use `v0.5.3` instead
284
285### Fixed
286- Allow uppercase letters in PHP sessionid format
287
288
289## [v0.5.2](https://github.com/shaarli/Shaarli/releases/tag/v0.5.2) - 2015-08-31
290### Added
291- Add PHP 7 to Travis platforms
292
293### Changed
294- Also extract HTTPS page metadata (title)
295
296### Fixed
297- Fix regression preventing to load LinkDB info when adding an existing link
298
299### Security
300- Fix Full Path Disclosure upon cookie forgery
301
302
303## [v0.5.1](https://github.com/shaarli/Shaarli/releases/tag/v0.5.1) - 2015-08-17
304### Added
305- Add a link to the shaarli/shaarli DockerHub repository
306
307### Changed
308- Update local documentation
309- Improve timezone detection at installation
310- Improve feed cache handling
311- Improve URL cleanup for new links
312
313### Fixed
314- Fix 404 after editing a link while being logged out
315
316
317## [v0.5.0](https://github.com/shaarli/Shaarli/releases/tag/v0.5.0) - 2015-07-31
318### Added
319- Add Firefox Social API
320- Start code refactoring:
321 - add unit test coverage
322 - add Travis integration
323
324### Changed
325- Search/Filter by tag fieds can now be accessed quickly with the `Tab` key
326- Update documentation
327- Remove duplicate tags in links
328- Remove annoying URL patterns
329- Start code refactoring:
330 - move all settings to `data/config.php`
331 - refactor Config, LinkDB, TimeZone, Utils
332
333### Fixed
334- Fix locale handling
335- Fix note URLs
336- Fix page redirections
337- Fix daily RSS browsing
338- Fix title display
339- Restore compatibility with PHP 5.3
340
341### Security
342- Fix links not being hidden when `HIDE_PUBLIC_LINKS` is set
343
344
345## [v0.0.45beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.45beta) - 2015-03-16
346### Fixed
347- Fix improperly displayed Unicode character
348- Fix incorrect font size for "Add link" input field
349
350
351## [v0.0.44beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.44beta) - 2015-03-15
352### Added
353- Add a Makefile to run static code checkers
354- Add local documentation (help link in page footer)
355- Use awesomplete library for autocompletion
356- Use bLazy.js library for images lazy loading
357- New 'Add Note' bookmarklet to immediatly open a note (text post) compose window
358
359### Changed
360- Theme improvements and cleanup (menu, search fields, icons, linklist...)
361- Allow 'javascript:' links sharing (bookmarklets)
362- Make update check optional
363- Redirect to homepage after adding a link via "Add Link" dialog
364- Remove more annoying URL parameters for shared links
365- Code cleanup
366
367### Removed
368- Remove jQuery
369
370### Security
371- Don't disclose version to visitors (shaarli-version.txt)
372
373
374## [v0.0.43beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.43beta) - 2015-02-20
375### Added
376- Title button link URL is now configurable
377- RainTPL's TMP and TPL directories path are now configurable
378- Displayed URLs for each link are now clickable links
379- Show links timestamps in Daily view
380
381### Changed
382- Automatically prepend "Note:" to title of self-posts (posts not pointing to an URL)
383- Make ATOM toolbar button optional (`SHOW_ATOM` configuration variable)
384- Optional archive.org links for each Shaarli link (`ARCHIVE_ORG` option)
385- Thumbnails: force HTTPS when possible
386- Improve tag cloud font scaling
387- Allow pointing RSS items to the permalink instead of the direct URL (`ENABLE_RSS_PERMALINKS` option)
388- Update JS libraries and add version numbers in filenames
389- Updates to README and footer
390
391### Fixed
392- Fix problems when running Shaarli behind a reverse proxy (invalid RSS feed URL)
393- Update check now checks against the community fork version
394- Include `cache/`, `data/`, `pagecache/` and `tmp/` directories in the repository
395- Fix duplicate tag search returning no results
396- Fix unnecessary 404 error on "Add link" when the user is logged out
397- Fixes to copyright/licensing information and unlicensed media
398- Fixes for tag cloud invalid links
399- Coding style fixes/cleanup
400- Fix redirection after deleteing a link leading to a 404 error
401- Shaarli's HTML is now W3C-compliant
402- Search now works with Unicode characters
403
404### Security
405- Do not leak server's PHP version and Shaarli's full path on errors
406- Prevent Shaarli from sending a lot of duplicate cookies
407
408
409## [v0.0.42beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.42beta) - 2014-07-27
410### Added
411- Add QRCode Javascript library
412- Allow importing bookmarks with the same timestamp (hack)
413- Allow putting a description in the bookmarklet URL
414- Add `json_encode()` implementation for PHP<5.2
415- Highlight search results
416
417### Changed
418- Improve 'Stay signed in' behaviour
419- Improve `smallHash()`
420- Refactor QRCode generation
421- Update Javascript lazyloading
422- Update CSS
423
424### Removed
425- Remove jQuery from almost all pages
426
427### Fixed
428- Fix overlapping tags
429- Fix field foxus in the bookmarklet
430- Fix error message when `data/` is not writable
431- Fix HTML generation
432
433### Security
434- Fix XSS flaw
435
436
437## [v0.0.41beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.41beta) - 2013-03-08
438### Added
439- Add HTTPS to the allowed protocols
440- Add support for magnet links in link descriptions
441- Allow creating new links as private by default
442- Allow disabling jQuery
443- Check write permissions
444- Check session support before installation
445
446### Changed
447- Improve token security
448- RSS feed: allow inverting links/permalinks
449
450### Fixed
451- Fix display issues during installation
452- Fix popup redirection after login failure
453- Fix RSS formatting for Thunderbird
454- Fix thumbnail creation
455- Fix cache purge
456
457### Security
458- Fix login issue with WebKit browsers
459
460
461## [v0.0.40beta](https://github.com/shaarli/Shaarli/releases/tag/v0.0.40beta) - 2013-02-26
462Initial release on GitHub.
463
464
465## [v0.0.40beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-08-24
466### Added
467- Flickr thumbnail now also support albums, galleries and users
468- Add a configuration option to disable session cookie protection
469 Check this if your get disconnected often or your IP address changes often
470
471### Removed
472- Removed the xml comment in cached RSS/ATOM feed
473 (although W3C-compliant, this may cause problems in some feed readers)
474
475### Fixed
476- A bug in the RSS cache would present old items as new in some cases
477- A small bug (non-initialized variable) in page cache cleaning
478- Proper "Nothing found" message when search returns no results
479- No more 404 error when searching with empty input
480- Flickr thumbnails are back (Flickr has made some changes to their domains)
481
482## [v0.0.39beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-08-10
483### Added
484- A cache for RSS feed, ATOM feed and Daily RSS feed, because these URLs
485 are massively hammered. Cache is automatically purged whenever the database
486 is changed. This will reduce server load. I may add cache to other pages later.
487
488### Changed
489- No more global `$LINKSDB` (Yuk)
490- Background color was removed when hovering a link
491
492### Fixed
493- Small bug corrected in config screen on timezones
494- Calling a non-existing permalink now returns a crude 404 error instead of 200 (OK)
495 This is done on purpose
496- The `shaarli` session cookie now has a proper path
497 Thus you can now install several Shaarlis on the same server in different paths,
498 and each will have its session
499- Now when you delete a link, you go back the same page/search parameters you were on
500- Restore previously removed `error_get_last()`, to ensure PHP 5.1 compatibility
501 (Yes, now it works on free.fr hosting)
502- Added `dialog=1` in bookmarklet code for some browsers
503
504
505## [v0.0.38beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-02-06
506### Added
507- Automatic creation of the `tmp` directory with proper rights (for RainTPL)
508- When you click the key to see only private links, it turns yellow
509
510### Changed
511- The "Daily" page now automatically skips empty days.
512
513### Fixed
514- Corrected the tag encoding (there was a bug when selecting a second tag which contains accented characters)
515
516
517## [v0.0.37beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-02-01
518### Added
519- Basic CSS for mobiles, which makes Shaarli //much// more usable on mobile devices
520- Picture wall no more instantly kills your browser. Now it uses
521 [lazy image loading](http://www.appelsiini.net/projects/lazyload);
522 the pictures are loaded only as you scroll the page.
523 This will reduce browser memory usage (especially on mobile devices),
524 as well as server load.
525 If you have javascript disabled, the page will still work as before
526 (all images loaded at once)
527- RSS feed for the "Daily" page. 1 RSS entry per day, with all links of that day.
528 RSS feed provides the last 7 days (only non-empty days are returned).
529- In link list, added an icon to see only private links. Click to toggle (only private / all)
530
531
532## [v0.0.36beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-01-25
533### Added
534- Shaarli licence in COPYING
535
536### Changed
537- Display adjustments in "Daily" page
538
539### Fixed
540- Improper text color in install form
541- Error in QRCode url (missing '?')
542
543
544## [v0.0.35beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-01-25
545### Fixed
546- Corrected a bug introduced in 0.0.34 which would improperly preprend data to URLs
547
548
549## [v0.0.34beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-01-25
550### Added
551- There is now a QR-Code of each permalink to easily open a link on your smartphone
552- Protocols `file:` and `apt:` are now also converted to clickable links (patch by Francis Chavanon)
553- Thumbnail support for http://xkcd.com/ (patch by Emilien Klein)
554- Thumbnail support for http://pix.toile-libre.org/
555- Well I had _some_ mercy for users with antique browsers (IE) which do not have
556 support for gradients: I added a few `background-color`
557- First version of the "Shaarli Daily", a page showing all links of a specific day.
558 By default, you see the links of the previous day.
559 There is still work to do on this page (error checking, better navigation (calendar?),
560 RSS feed, CSS for mobile and printing...)
561
562### Changed
563- Upgraded bundled versions of jQuery (1.7.1) and jQuery UI (1.8.17)
564- Upgraded bundled version of RainTPL (2.7)
565- Changed HTTPS detection code
566
567### Fixed
568- In link edition, you can now click the word "Private" to check the box
569- Clicking a tag would not work properly if the tag contained special characters (like +)
570- Added proper jQuery licence (shame on me)
571
572
573## [v0.0.33beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2012-01-17
574### Added
575- Shaarli packaged to ease Linux distributions integration
576 As a simple user, you do not need to cope with these versions
577 Future releases of Shaarli will also be customized and published in these directories
578 Differences with the standard Shaarli version:
579 - deb:
580 - .tar.gz instead of .zip
581 - COPYING licence file added
582 - jQuery/jQuery-UI libraries removed to cope with Debian rules
583 This version links to the libs hosted at http://code.jquery.com
584 - rpm:
585 - sources located in a subdirectory with the same name as the zip file
586 - COPYING licence file added
587 - WARNING: When downloading the .tar.gz, always use wget (and not your browser),
588 otherwise the .tar.gz will be corrupted
589
590### Fixed
591- ATOM feed validates again
592
593### Security
594- XSS vulnerability patched (thanks to Stanislas D.!)
595
596
597## [v0.0.32beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-12-16
598### Added
599- Better check on URL parameters (patch by gege2061)
600- Add `max-height` and `overflow:auto` attributes so that content can be scrolled if too large
601
602### Changed
603- HTML generation moved to RainTPL templates (in the `tpl/` directory)
604- Better detection of HTTPS (patch by gege2061)
605- In RSS/ATOM feeds, the GUID is now the permalink instead of the final URL (patch by gege2061)
606- Jerrywham CSS patch included
607- Multiple spaces are now respected in description.
608 Thus you can use Shaarli as a personal pastebin (for posting source code, for example).
609
610### Removed
611- Page time generation was removed
612
613### Fixed
614- Tab order changed in login screen
615- Permalinks now work even if additional parameters have been added
616 (e.g. `/?E8Yj2Q&utm_source=blablabla...`)
617- user.css is included only if the file is present
618 (This prevents a useless CSS include which makes a harmless but useless 404 error.)
619
620
621## [v0.0.31beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-11-29
622### Added
623- Support for TED Talks (ted.com/talks) thumbnails (patch by Emilien K.)
624- partial [patch](http://www.idleman.fr/blog/?p=508) by Idleman: Better design consistency, icon on private links. In-page popup was not included because it causes problem on some websites
625- Support for bookmark files without ADD_DATE attributes
626- Logo is clickable
627- `user.css` can be added to overload Shaarli base CSS.(patch by Jerrywham).
628 Just put `user.css` in the same directory as shaarli.css.
629 Example: `<code css>#pageheader { background: blue; }</code>`
630 Please note that Shaarli CSS are not stable and may completely change on each version
631
632### Changed
633- Edit and Delete buttons in link list were replaced with icons. (patch by Jerrywham)
634
635### Fixed
636- Better error handling in thumbnail generation (patch by Emilien K.)
637- The top menu is no longer displayed in bookmarklet popup
638- Bookmark which have the exact same date/time are now correctly imported.
639 Most remaining import problems should be solved now
640- Comment in Shaarli export moved to beginning of file to prevent clash with last link description
641
642
643## [v0.0.30beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-11-18
644### Added
645- Add a small `delete` button in link list (after the `edit` button)
646
647### Fixed
648- Moved the call to PubSubHub
649
650
651## [v0.0.29beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-11-18
652### Fixed
653- Corrected a bug introduced in v0.0.28beta
654 (there was an error if you use the bookmarklet and you're not logged in)
655
656
657## [v0.0.28beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-11-17
658### Added
659- Thumbnail support for youtu.be URLs (YouTube short url service)
660- PubSubHub protocol support (from http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/).
661 Warning: This was not tested. You need to set your hub url in
662 `$GLOBALS['config']['PUBSUBHUB_URL']` and put the official client (`publisher.php`)
663 in the same directory as Shaarli's `index.php`
664- RSS and ATOM feeds now also contain tags (in `category` tags, as per their
665 respective specifications)
666
667### Changed
668- New Shaarli theme and logo by Idle (http://www.idleman.fr/blog/?p=469)
669- In picture wall, pictures point to Shaarli permalink instead of final URL.
670 This way, users can read the description.
671- In RSS/ATOM feeds, guid and link URL of permalinks are now proper absolute URLs
672- In RSS/ATOM feeds, URLs are now clickable
673- Rename `http_parse_headers()` to `http_parse_headers_shaarli()` to prevent
674 name collision with some PHP extensions
675
676### Fixed
677- Thumbnails removed for imgur.com/a/ URLs (Thumbnails are not available for albums on imgur)
678- Shaarli now correctly only tries to get thumbnails for vimeo video URLs
679- Fix a bug in imgur.com URLs handling that would cause some thumbnails not to appear
680- The search engine would not return a result if the word to search was the first in description
681- Extracted title is now correct if the page has two `title` html tags
682
683
684## [v0.0.27beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-10-18
685### Added
686- Add a picture wall, which can be filtered too: it will use the same filters
687 (tags,text search) as current page when clicked.
688
689
690## [v0.0.26beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-10-17
691### Changed
692- Made permalink more visible (smallHash)
693
694### Fixed
695- Removed extras space in description when URLs are converted to clickable links
696- Thumbnail for subreddit imgur urls (/r/...) were corrected (thanks to Accent Grave)
697
698
699## [v0.0.25beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-10-13
700### Added
701- Better CSS for printing (thanks to jerrywham suggestion)
702- Allow using a redirector or anonymizing proxy for links
703 (such as `http://anonym.to/?` to mask you `HTTP_REFERER`).
704 Just go to `Tools > Configure > Redirector`
705 (thanks to Accent Grave for the suggestion).
706- The `ENABLE_LOCALCACHE` option can be set to `false` for those who have
707 a limited quota on their host.
708 This will disable the local thumbnail cache.
709 Services which require the use of the cache will have no thumbnails
710 (vimeo, flickr, direct link to image).
711 Other services will still have a thumbnail (youtube,imgur.com,dailymotion,imageshack.us)
712
713### Changed
714- Now thumbnails generated by Shaarli are croped to a height of 120 pixels
715- YouTube thumbnails now use `default.jpg` instead of `2.jpg` (This is usually more pertinent)
716- Configuration options (such as `HIDE_TIMESTAMPS`, `ENABLE_THUMBNAILS`, etc.)
717 can now be put in a an external file so that you do not have to tweak them again
718 when you upgrade Shaarli.
719 Just add the file `data/options.php`.
720- If a single link is displayed, the page title contains the title of the link
721- Shaarli page title is clickable (and has the same link as "Home")
722- A few CSS tweaks (thanks to maethor for suggestion)
723
724### Fixed
725- Shaarli now supports newlines in titles (thanks to dixy)
726- The link to the RSS feed in page header was not correct
727
728
729## [v0.0.24beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
730### Added
731- Allow posting an entry without a link. (You can use Shaarli as a kind of "personal twitter")
732- Each Shaarli entry now has a short link (just click on the date of a link).
733 Now you can send a link that points to a single entry in your Shaarli
734- In descriptions, URLs are now clickable
735- Thumbnails will be generated for all link pointing to .jpg/png/gif
736 (as long as the images are less than 4 Mb and take less than 30 seconds to download)
737
738### Fixed
739- Now thumbnails also work for imgur gallery links (/gallery/...)
740 (Thanks to Accent Grave for the correction)
741- Removed useless debugging information in log
742- The filter in RSS/ATOM feed now works again properly (it was broken in 0.0.17beta)
743
744
745## [v0.0.23beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
746### Added
747- Added thumbnail support for imageshack.us
748
749### Changed
750- Now you can clic the sentence "Stay signed in" to tick the checkbox (patch by Emilien)
751- In tag editing, comma (,) are now automatically converted to spaces
752- In tag editing, autocomplete no longuer suggests a tag you have already entered in the same line
753
754
755## [v0.0.22beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
756### Added
757- Support for thumbnails for flickr.com
758- Allow staying signed in:
759 Your session will be kept open even if you close the browser.
760 This is available through a checkbox in the login screen.
761
762### Changed
763- Some hosts (flickr, vimeo) are slow as hell for the thumbnails,
764 or require an extra HTTP request.
765 For these hosts the thumbnail generation has been deported outside the generation
766 of the page to keep Shaarli snappy.
767 For these slow services, the thumbnails are also cached.
768
769### Fixed
770- Title was not properly passed if you had to login when using the bookmarklet (patch by shenshei)
771
772
773## [v0.0.21beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
774### Added
775- Thumbnails for some services
776 Currently supports: YouTube.com, dailymotion.com, vimeo.com (slow!) and imgur.com.
777 Thumbnails are enabled by default, but you can turn them off
778 (set `define('ENABLE_THUMBNAILS',true);` to `false`).
779
780### Changed
781- Removed the focus on the searchbox (this is cumbersome when you want to browse pages
782 and scroll with the keyboard)
783
784
785## [v0.0.20beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
786### Fixed
787- RSS feed is now served as `application/rss+xml` instead of `application/xhtml+xml`
788 (which was causing problem in //RSS Lounge//)
789- ATOM feed is now served as `application/atom+xml` instead of `application/xhtml+xml`
790
791
792## [v0.0.19beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
793### Added
794- ATOM feed
795
796### Fixed
797- Patch by Emilien to remove the update notification after the update
798
799
800## [v0.0.18beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
801### Added
802- You can now configure the title of your page
803- New screen to configure title and timezone
804
805### Changed
806- Nicer timezone selection patch by killruana
807
808### Fixed
809- New lines now appear correctly in the RSS feed descriptions.
810
811
812## [v0.0.17beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
813### Added
814- Change password screen added (based on a patch by killruana)
815- Autocomplete in the tag search form
816- You can rename or delete a tag in all links
817 (very handy if you misspelled a tag or want to merge tags)
818- When you click the RSS feed, the feed will be filtered with the same filters
819 as the page you were viewing
820
821### Changed
822- CSS adjustments by jerrywham
823- Minor corrections
824
825
826## [v0.0.16beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
827### Added
828- Upgrade notification:
829 If a new version of Shaarli is available, you will be notified by a discreet
830 message in top-right corner.
831 This message will only be visible if you are logged in, and the check will be
832 performed at most once a day.
833- Preliminary tag cloud (ugly for the moment, I need to find something better)
834
835### Changed
836- Replaced `preg_match()` with `version_compare()` to check PHP version
837- Includes a patch by Emilien K. to mask dates if user is not logged in.
838 The option can be activated by changing `define('HIDE_TIMESTAMPS',false);` to `true`
839
840
841## [v0.0.15beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
842### Added
843- New in import: Option to overwrite existing links when importing
844- On free.fr, automatic creation of the `/sessions` directory
845
846### Changed
847- CSS Stylesheet is now an external file (shaarli.css).
848 This reduces page size and eases customization.
849
850### Removed
851- Removed some parameters in URL added by some feed proxies (`#xtor=RSS-...`)
852
853### Fixed
854- Bug corrected: Prevented loop on login screen upon successful login after a failed login
855- Bug corrected in import: HTML entities were not properly decoded.
856 If you imported your Delicious/Diigo bookmarks, your should import them again
857 and use the 'overwrite' option of the import feature.
858
859
860## [v0.0.14beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
861### Added
862- You no longer need to disable `magic_quotes` on your host.
863 Shaarli will cope with this option beeing activated.
864
865
866## [v0.0.13beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
867### Added
868- Import: New option to import html bookmark file as private links
869- Import: Importing a bookmark file will not overwrite existing links anymore
870- Export: New options to export only public or private links
871
872### Changed
873- In tag autocomplete, tags are presented in use order
874 (most used tags first, instead of alphabetical order)
875- RSS Feed can now be filtered by tags or fulltext search. Just add to the feed url:
876 - `&searchtags=minecraft+video` for tag filtering
877 - `&searchterm=portal` for fulltext search to the feed url
878
879
880## [v0.0.12beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
881### Added
882- Add a check that the config file was properly created
883 (in case Shaarli does not have the write rights in its folder)
884- Open Shaarli: there is an option to open your Shaarli to anyone.
885 Anybody will be able to add/edit/delete links without having to login.
886 In code, change `define('OPEN_SHAARLI',false);` to `true`.
887 Note: No anti-spam for the moment. You are warned!
888- Autocomplete for tags
889
890
891## [v0.0.11beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
892### Added
893- Add a check and a warning for some hosts which still have `magic_quotes` activated
894
895
896## [v0.0.10beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
897### Added
898- Get rid of `&quot;` in titles
899
900
901## [v0.0.9beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
902### Added
903- Now works on hosts `free.fr` and `1and1`
904- Now works with PHP 5.1
905- PHP version is now checked and an error message is displayed if version is not correct
906
907### Fixed
908- No more error messages if the browser does not send `HTTP_REFERER`
909- No more error messages if the host has disabled http protocol in PHP config (eg. 1and1)
910
911
912## [v0.0.8beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)
913### Changed
914- In RSS feed, GUID content replaced with the URL of the link, because some
915 stupid RSS reader (like Google Reader) use `<guid>` as a link instead of using `<link>`
916
917
918## [v0.0.7beta](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history) - 2011-09-16
919First public release by Sebsauvage, see original article:
920[Adieu Delicious, Diigo et StumbleUpon. Salut Shaarli !](http://sebsauvage.net/rhaa/index.php?2011/09/16/09/29/58-adieu-delicious-diigo-et-stumbleupon-salut-shaarli-) (FR)
diff --git a/COPYING b/COPYING
index 28939100..22929463 100644
--- a/COPYING
+++ b/COPYING
@@ -72,10 +72,6 @@ Files: plugins/wallabag/wallabag.png
72License: MIT License (http://opensource.org/licenses/MIT) 72License: MIT License (http://opensource.org/licenses/MIT)
73Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag 73Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag
74 74
75Files: plugins/markdown/Parsedown.php
76License: MIT License (http://opensource.org/licenses/MIT)
77Copyright: (C) 2015 Emanuil Rusev - https://github.com/erusev/parsedown
78
79---------------------------------------------------- 75----------------------------------------------------
80ZLIB/LIBPNG LICENSE 76ZLIB/LIBPNG LICENSE
81 77
diff --git a/Makefile b/Makefile
index 75c54f28..60aec9a0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
1# Shaarli, the personal, minimalist, super-fast, no-database delicious clone. 1# The personal, minimalist, super-fast, database free, bookmarking service.
2# Makefile for PHP code analysis & testing 2# Makefile for PHP code analysis & testing, documentation and release generation
3 3
4# Prerequisites: 4# Prerequisites:
5# - install Composer, either: 5# - install Composer, either:
@@ -128,6 +128,39 @@ test:
128 @$(BIN)/phpunit tests 128 @$(BIN)/phpunit tests
129 129
130## 130##
131# Custom release archive generation
132#
133# For each tagged revision, GitHub provides tar and zip archives that correspond
134# to the output of git-archive
135#
136# These targets produce similar archives, featuring 3rd-party dependencies
137# to ease deployment on shared hosting.
138##
139ARCHIVE_VERSION := shaarli-$$(git describe)-full
140ARCHIVE_PREFIX=Shaarli/
141
142release_archive: release_tar release_zip
143
144### download 3rd-party PHP libraries
145composer_dependencies: clean
146 composer update --no-dev
147 find vendor/ -name ".git" -type d -exec rm -rf {} +
148
149### generate a release tarball and include 3rd-party dependencies
150release_tar: composer_dependencies
151 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
152 tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
153 gzip $(ARCHIVE_VERSION).tar
154
155### generate a release zip and include 3rd-party dependencies
156release_zip: composer_dependencies
157 git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
158 mkdir $(ARCHIVE_PREFIX)
159 rsync -a vendor/ $(ARCHIVE_PREFIX)vendor/
160 zip -r $(ARCHIVE_VERSION).zip $(ARCHIVE_PREFIX)vendor/
161 rm -rf $(ARCHIVE_PREFIX)
162
163##
131# Targets for repository and documentation maintenance 164# Targets for repository and documentation maintenance
132## 165##
133 166
diff --git a/README.md b/README.md
index d67c10ac..21062b9b 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ _It is designed to be personal (single-user), fast and handy._
16 16
17## Quickstart 17## Quickstart
18- [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki) 18- [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki)
19- [Change log](CHANGELOG.md)
19- [Bugs/Feature requests/Discussion](https://github.com/shaarli/Shaarli/issues/) 20- [Bugs/Feature requests/Discussion](https://github.com/shaarli/Shaarli/issues/)
20 21
21### Demo 22### Demo
@@ -25,7 +26,8 @@ It runs the latest development version of Shaarli and is updated/reset daily.
25Login: `demo`; Password: `demo` 26Login: `demo`; Password: `demo`
26 27
27### Installation & upgrade 28### Installation & upgrade
28- [Download](https://github.com/shaarli/Shaarli/wiki/Download) 29- [Download and installation](https://github.com/shaarli/Shaarli/wiki/Download-and-Installation)
30- [Upgrade and migration](https://github.com/shaarli/Shaarli/wiki/Upgrade-and-migration)
29- [Server requirements](https://github.com/shaarli/Shaarli/wiki/Server-requirements) 31- [Server requirements](https://github.com/shaarli/Shaarli/wiki/Server-requirements)
30- [Server configuration](https://github.com/shaarli/Shaarli/wiki/Server-configuration) 32- [Server configuration](https://github.com/shaarli/Shaarli/wiki/Server-configuration)
31- [Shaarli configuration](https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration) 33- [Shaarli configuration](https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration)
diff --git a/application/.htaccess b/application/.htaccess
index b584d98c..f601c1ee 100644
--- a/application/.htaccess
+++ b/application/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 978fc9da..7f963e97 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -15,6 +15,9 @@ class ApplicationUtils
15 * 15 *
16 * The code is read from the raw content of the version file on the Git server. 16 * The code is read from the raw content of the version file on the Git server.
17 * 17 *
18 * @param string $url URL to reach to get the latest version.
19 * @param int $timeout Timeout to check the URL (in seconds).
20 *
18 * @return mixed the version code from the repository if available, else 'false' 21 * @return mixed the version code from the repository if available, else 'false'
19 */ 22 */
20 public static function getLatestGitVersionCode($url, $timeout=2) 23 public static function getLatestGitVersionCode($url, $timeout=2)
@@ -49,6 +52,7 @@ class ApplicationUtils
49 * @param int $checkInterval the minimum interval between update checks (in seconds 52 * @param int $checkInterval the minimum interval between update checks (in seconds
50 * @param bool $enableCheck whether to check for new versions 53 * @param bool $enableCheck whether to check for new versions
51 * @param bool $isLoggedIn whether the user is logged in 54 * @param bool $isLoggedIn whether the user is logged in
55 * @param string $branch check update for the given branch
52 * 56 *
53 * @throws Exception an invalid branch has been set for update checks 57 * @throws Exception an invalid branch has been set for update checks
54 * 58 *
@@ -132,11 +136,11 @@ class ApplicationUtils
132 /** 136 /**
133 * Checks Shaarli has the proper access permissions to its resources 137 * Checks Shaarli has the proper access permissions to its resources
134 * 138 *
135 * @param array $globalConfig The $GLOBALS['config'] array 139 * @param ConfigManager $conf Configuration Manager instance.
136 * 140 *
137 * @return array A list of the detected configuration issues 141 * @return array A list of the detected configuration issues
138 */ 142 */
139 public static function checkResourcePermissions($globalConfig) 143 public static function checkResourcePermissions($conf)
140 { 144 {
141 $errors = array(); 145 $errors = array();
142 146
@@ -145,19 +149,19 @@ class ApplicationUtils
145 'application', 149 'application',
146 'inc', 150 'inc',
147 'plugins', 151 'plugins',
148 $globalConfig['RAINTPL_TPL'] 152 $conf->get('resource.raintpl_tpl'),
149 ) as $path) { 153 ) as $path) {
150 if (! is_readable(realpath($path))) { 154 if (! is_readable(realpath($path))) {
151 $errors[] = '"'.$path.'" directory is not readable'; 155 $errors[] = '"'.$path.'" directory is not readable';
152 } 156 }
153 } 157 }
154 158
155 // Check cache and data directories are readable and writeable 159 // Check cache and data directories are readable and writable
156 foreach (array( 160 foreach (array(
157 $globalConfig['CACHEDIR'], 161 $conf->get('resource.thumbnails_cache'),
158 $globalConfig['DATADIR'], 162 $conf->get('resource.data_dir'),
159 $globalConfig['PAGECACHE'], 163 $conf->get('resource.page_cache'),
160 $globalConfig['RAINTPL_TMP'] 164 $conf->get('resource.raintpl_tmp'),
161 ) as $path) { 165 ) as $path) {
162 if (! is_readable(realpath($path))) { 166 if (! is_readable(realpath($path))) {
163 $errors[] = '"'.$path.'" directory is not readable'; 167 $errors[] = '"'.$path.'" directory is not readable';
@@ -167,13 +171,13 @@ class ApplicationUtils
167 } 171 }
168 } 172 }
169 173
170 // Check configuration files are readable and writeable 174 // Check configuration files are readable and writable
171 foreach (array( 175 foreach (array(
172 $globalConfig['CONFIG_FILE'], 176 $conf->getConfigFileExt(),
173 $globalConfig['DATASTORE'], 177 $conf->get('resource.datastore'),
174 $globalConfig['IPBANS_FILENAME'], 178 $conf->get('resource.ban_file'),
175 $globalConfig['LOG_FILE'], 179 $conf->get('resource.log'),
176 $globalConfig['UPDATECHECK_FILENAME'] 180 $conf->get('resource.update_check'),
177 ) as $path) { 181 ) as $path) {
178 if (! is_file(realpath($path))) { 182 if (! is_file(realpath($path))) {
179 # the file may not exist yet 183 # the file may not exist yet
diff --git a/application/CachedPage.php b/application/CachedPage.php
index 50cfa9ac..5087d0c4 100644
--- a/application/CachedPage.php
+++ b/application/CachedPage.php
@@ -35,7 +35,7 @@ class CachedPage
35 /** 35 /**
36 * Returns the cached version of a page, if it exists and should be cached 36 * Returns the cached version of a page, if it exists and should be cached
37 * 37 *
38 * @return a cached version of the page if it exists, null otherwise 38 * @return string a cached version of the page if it exists, null otherwise
39 */ 39 */
40 public function cachedVersion() 40 public function cachedVersion()
41 { 41 {
diff --git a/application/Config.php b/application/Config.php
deleted file mode 100644
index 05a59452..00000000
--- a/application/Config.php
+++ /dev/null
@@ -1,221 +0,0 @@
1<?php
2/**
3 * Functions related to configuration management.
4 */
5
6/**
7 * Re-write configuration file according to given array.
8 * Requires mandatory fields listed in $MANDATORY_FIELDS.
9 *
10 * @param array $config contains all configuration fields.
11 * @param bool $isLoggedIn true if user is logged in.
12 *
13 * @return void
14 *
15 * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
16 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
17 * @throws Exception: an error occured while writing the new config file.
18 */
19function writeConfig($config, $isLoggedIn)
20{
21 // These fields are required in configuration.
22 $MANDATORY_FIELDS = array(
23 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
24 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
25 );
26
27 if (!isset($config['config']['CONFIG_FILE'])) {
28 throw new MissingFieldConfigException('CONFIG_FILE');
29 }
30
31 // Only logged in user can alter config.
32 if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
33 throw new UnauthorizedConfigException();
34 }
35
36 // Check that all mandatory fields are provided in $config.
37 foreach ($MANDATORY_FIELDS as $field) {
38 if (!isset($config[$field])) {
39 throw new MissingFieldConfigException($field);
40 }
41 }
42
43 $configStr = '<?php '. PHP_EOL;
44 $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
45 $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
46 $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
47 $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
48 $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
49 $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
50 $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
51 $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
52 $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
53 $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
54
55 // Store all $config['config']
56 foreach ($config['config'] as $key => $value) {
57 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
58 }
59
60 if (isset($config['plugins'])) {
61 foreach ($config['plugins'] as $key => $value) {
62 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
63 }
64 }
65
66 if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
67 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
68 ) {
69 throw new Exception(
70 'Shaarli could not create the config file.
71 Please make sure Shaarli has the right to write in the folder is it installed in.'
72 );
73 }
74}
75
76/**
77 * Process plugin administration form data and save it in an array.
78 *
79 * @param array $formData Data sent by the plugin admin form.
80 *
81 * @return array New list of enabled plugin, ordered.
82 *
83 * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
84 */
85function save_plugin_config($formData)
86{
87 // Make sure there are no duplicates in orders.
88 if (!validate_plugin_order($formData)) {
89 throw new PluginConfigOrderException();
90 }
91
92 $plugins = array();
93 $newEnabledPlugins = array();
94 foreach ($formData as $key => $data) {
95 if (startsWith($key, 'order')) {
96 continue;
97 }
98
99 // If there is no order, it means a disabled plugin has been enabled.
100 if (isset($formData['order_' . $key])) {
101 $plugins[(int) $formData['order_' . $key]] = $key;
102 }
103 else {
104 $newEnabledPlugins[] = $key;
105 }
106 }
107
108 // New enabled plugins will be added at the end of order.
109 $plugins = array_merge($plugins, $newEnabledPlugins);
110
111 // Sort plugins by order.
112 if (!ksort($plugins)) {
113 throw new PluginConfigOrderException();
114 }
115
116 $finalPlugins = array();
117 // Make plugins order continuous.
118 foreach ($plugins as $plugin) {
119 $finalPlugins[] = $plugin;
120 }
121
122 return $finalPlugins;
123}
124
125/**
126 * Validate plugin array submitted.
127 * Will fail if there is duplicate orders value.
128 *
129 * @param array $formData Data from submitted form.
130 *
131 * @return bool true if ok, false otherwise.
132 */
133function validate_plugin_order($formData)
134{
135 $orders = array();
136 foreach ($formData as $key => $value) {
137 // No duplicate order allowed.
138 if (in_array($value, $orders)) {
139 return false;
140 }
141
142 if (startsWith($key, 'order')) {
143 $orders[] = $value;
144 }
145 }
146
147 return true;
148}
149
150/**
151 * Affect plugin parameters values into plugins array.
152 *
153 * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
154 * @param mixed $config Plugins configuration.
155 *
156 * @return mixed Updated $plugins array.
157 */
158function load_plugin_parameter_values($plugins, $config)
159{
160 $out = $plugins;
161 foreach ($plugins as $name => $plugin) {
162 if (empty($plugin['parameters'])) {
163 continue;
164 }
165
166 foreach ($plugin['parameters'] as $key => $param) {
167 if (!empty($config[$key])) {
168 $out[$name]['parameters'][$key] = $config[$key];
169 }
170 }
171 }
172
173 return $out;
174}
175
176/**
177 * Exception used if a mandatory field is missing in given configuration.
178 */
179class MissingFieldConfigException extends Exception
180{
181 public $field;
182
183 /**
184 * Construct exception.
185 *
186 * @param string $field field name missing.
187 */
188 public function __construct($field)
189 {
190 $this->field = $field;
191 $this->message = 'Configuration value is required for '. $this->field;
192 }
193}
194
195/**
196 * Exception used if an unauthorized attempt to edit configuration has been made.
197 */
198class UnauthorizedConfigException extends Exception
199{
200 /**
201 * Construct exception.
202 */
203 public function __construct()
204 {
205 $this->message = 'You are not authorized to alter config.';
206 }
207}
208
209/**
210 * Exception used if an error occur while saving plugin configuration.
211 */
212class PluginConfigOrderException extends Exception
213{
214 /**
215 * Construct exception.
216 */
217 public function __construct()
218 {
219 $this->message = 'An error occurred while trying to save plugins loading order.';
220 }
221}
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
index ddefe6ce..fedd90e6 100644
--- a/application/FeedBuilder.php
+++ b/application/FeedBuilder.php
@@ -124,7 +124,8 @@ class FeedBuilder
124 $data['last_update'] = $this->getLatestDateFormatted(); 124 $data['last_update'] = $this->getLatestDateFormatted();
125 $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; 125 $data['show_dates'] = !$this->hideDates || $this->isLoggedIn;
126 // Remove leading slash from REQUEST_URI. 126 // Remove leading slash from REQUEST_URI.
127 $data['self_link'] = $pageaddr . escape(ltrim($this->serverInfo['REQUEST_URI'], '/')); 127 $data['self_link'] = escape(server_url($this->serverInfo))
128 . escape($this->serverInfo['REQUEST_URI']);
128 $data['index_url'] = $pageaddr; 129 $data['index_url'] = $pageaddr;
129 $data['usepermalinks'] = $this->usePermalinks === true; 130 $data['usepermalinks'] = $this->usePermalinks === true;
130 $data['links'] = $linkDisplayed; 131 $data['links'] = $linkDisplayed;
@@ -142,7 +143,7 @@ class FeedBuilder
142 */ 143 */
143 protected function buildItem($link, $pageaddr) 144 protected function buildItem($link, $pageaddr)
144 { 145 {
145 $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']); 146 $link['guid'] = $pageaddr .'?'. $link['shorturl'];
146 // Check for both signs of a note: starting with ? and 7 chars long. 147 // Check for both signs of a note: starting with ? and 7 chars long.
147 if ($link['url'][0] === '?' && strlen($link['url']) === 7) { 148 if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
148 $link['url'] = $pageaddr . $link['url']; 149 $link['url'] = $pageaddr . $link['url'];
@@ -152,19 +153,26 @@ class FeedBuilder
152 } else { 153 } else {
153 $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>'; 154 $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>';
154 } 155 }
155 $link['description'] = format_description($link['description']) . PHP_EOL .'<br>&#8212; '. $permalink; 156 $link['description'] = format_description($link['description'], '', $pageaddr);
157 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink;
156 158
157 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 159 $pubDate = $link['created'];
160 $link['pub_iso_date'] = $this->getIsoDate($pubDate);
158 161
159 if ($this->feedType == self::$FEED_RSS) { 162 // atom:entry elements MUST contain exactly one atom:updated element.
160 $link['iso_date'] = $date->format(DateTime::RSS); 163 if (!empty($link['updated'])) {
164 $upDate = $link['updated'];
165 $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
161 } else { 166 } else {
162 $link['iso_date'] = $date->format(DateTime::ATOM); 167 $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);;
163 } 168 }
164 169
165 // Save the more recent item. 170 // Save the more recent item.
166 if (empty($this->latestDate) || $this->latestDate < $date) { 171 if (empty($this->latestDate) || $this->latestDate < $pubDate) {
167 $this->latestDate = $date; 172 $this->latestDate = $pubDate;
173 }
174 if (!empty($upDate) && $this->latestDate < $upDate) {
175 $this->latestDate = $upDate;
168 } 176 }
169 177
170 $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); 178 $taglist = array_filter(explode(' ', $link['tags']), 'strlen');
@@ -250,6 +258,26 @@ class FeedBuilder
250 } 258 }
251 259
252 /** 260 /**
261 * Get ISO date from DateTime according to feed type.
262 *
263 * @param DateTime $date Date to format.
264 * @param string|bool $format Force format.
265 *
266 * @return string Formatted date.
267 */
268 protected function getIsoDate(DateTime $date, $format = false)
269 {
270 if ($format !== false) {
271 return $date->format($format);
272 }
273 if ($this->feedType == self::$FEED_RSS) {
274 return $date->format(DateTime::RSS);
275
276 }
277 return $date->format(DateTime::ATOM);
278 }
279
280 /**
253 * Returns the number of link to display according to 'nb' user input parameter. 281 * Returns the number of link to display according to 'nb' user input parameter.
254 * 282 *
255 * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. 283 * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
diff --git a/application/FileUtils.php b/application/FileUtils.php
index 6a12ef0e..6cac9825 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -9,11 +9,13 @@ class IOException extends Exception
9 /** 9 /**
10 * Construct a new IOException 10 * Construct a new IOException
11 * 11 *
12 * @param string $path path to the ressource that cannot be accessed 12 * @param string $path path to the resource that cannot be accessed
13 * @param string $message Custom exception message.
13 */ 14 */
14 public function __construct($path) 15 public function __construct($path, $message = '')
15 { 16 {
16 $this->path = $path; 17 $this->path = $path;
17 $this->message = 'Error accessing '.$this->path; 18 $this->message = empty($message) ? 'Error accessing' : $message;
19 $this->message .= PHP_EOL . $this->path;
18 } 20 }
19} 21}
diff --git a/application/HttpUtils.php b/application/HttpUtils.php
index 2e0792f9..e705cfd6 100644
--- a/application/HttpUtils.php
+++ b/application/HttpUtils.php
@@ -1,6 +1,7 @@
1<?php 1<?php
2/** 2/**
3 * GET an HTTP URL to retrieve its content 3 * GET an HTTP URL to retrieve its content
4 * Uses the cURL library or a fallback method
4 * 5 *
5 * @param string $url URL to get (http://...) 6 * @param string $url URL to get (http://...)
6 * @param int $timeout network timeout (in seconds) 7 * @param int $timeout network timeout (in seconds)
@@ -20,38 +21,177 @@
20 * echo 'There was an error: '.htmlspecialchars($headers[0]); 21 * echo 'There was an error: '.htmlspecialchars($headers[0]);
21 * } 22 * }
22 * 23 *
23 * @see http://php.net/manual/en/function.file-get-contents.php 24 * @see https://secure.php.net/manual/en/ref.curl.php
24 * @see http://php.net/manual/en/function.stream-context-create.php 25 * @see https://secure.php.net/manual/en/functions.anonymous.php
25 * @see http://php.net/manual/en/function.get-headers.php 26 * @see https://secure.php.net/manual/en/function.preg-split.php
27 * @see https://secure.php.net/manual/en/function.explode.php
28 * @see http://stackoverflow.com/q/17641073
29 * @see http://stackoverflow.com/q/9183178
30 * @see http://stackoverflow.com/q/1462720
26 */ 31 */
27function get_http_response($url, $timeout = 30, $maxBytes = 4194304) 32function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
28{ 33{
29 $urlObj = new Url($url); 34 $urlObj = new Url($url);
30 $cleanUrl = $urlObj->idnToAscii(); 35 $cleanUrl = $urlObj->idnToAscii();
31 36
32 if (! filter_var($cleanUrl, FILTER_VALIDATE_URL) || ! $urlObj->isHttp()) { 37 if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) {
33 return array(array(0 => 'Invalid HTTP Url'), false); 38 return array(array(0 => 'Invalid HTTP Url'), false);
34 } 39 }
35 40
41 $userAgent =
42 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:45.0)'
43 . ' Gecko/20100101 Firefox/45.0';
44 $acceptLanguage =
45 substr(setlocale(LC_COLLATE, 0), 0, 2) . ',en-US;q=0.7,en;q=0.3';
46 $maxRedirs = 3;
47
48 if (!function_exists('curl_init')) {
49 return get_http_response_fallback(
50 $cleanUrl,
51 $timeout,
52 $maxBytes,
53 $userAgent,
54 $acceptLanguage,
55 $maxRedirs
56 );
57 }
58
59 $ch = curl_init($cleanUrl);
60 if ($ch === false) {
61 return array(array(0 => 'curl_init() error'), false);
62 }
63
64 // General cURL settings
65 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
66 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
67 curl_setopt($ch, CURLOPT_HEADER, true);
68 curl_setopt(
69 $ch,
70 CURLOPT_HTTPHEADER,
71 array('Accept-Language: ' . $acceptLanguage)
72 );
73 curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
74 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
75 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
76 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
77
78 // Max download size management
79 curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024);
80 curl_setopt($ch, CURLOPT_NOPROGRESS, false);
81 curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
82 function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
83 {
84 if (version_compare(phpversion(), '5.5', '<')) {
85 // PHP version lower than 5.5
86 // Callback has 4 arguments
87 $downloaded = $arg1;
88 } else {
89 // Callback has 5 arguments
90 $downloaded = $arg2;
91 }
92 // Non-zero return stops downloading
93 return ($downloaded > $maxBytes) ? 1 : 0;
94 }
95 );
96
97 $response = curl_exec($ch);
98 $errorNo = curl_errno($ch);
99 $errorStr = curl_error($ch);
100 $headSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
101 curl_close($ch);
102
103 if ($response === false) {
104 if ($errorNo == CURLE_COULDNT_RESOLVE_HOST) {
105 /*
106 * Workaround to match fallback method behaviour
107 * Removing this would require updating
108 * GetHttpUrlTest::testGetInvalidRemoteUrl()
109 */
110 return array(false, false);
111 }
112 return array(array(0 => 'curl_exec() error: ' . $errorStr), false);
113 }
114
115 // Formatting output like the fallback method
116 $rawHeaders = substr($response, 0, $headSize);
117
118 // Keep only headers from latest redirection
119 $rawHeadersArrayRedirs = explode("\r\n\r\n", trim($rawHeaders));
120 $rawHeadersLastRedir = end($rawHeadersArrayRedirs);
121
122 $content = substr($response, $headSize);
123 $headers = array();
124 foreach (preg_split('~[\r\n]+~', $rawHeadersLastRedir) as $line) {
125 if (empty($line) or ctype_space($line)) {
126 continue;
127 }
128 $splitLine = explode(': ', $line, 2);
129 if (count($splitLine) > 1) {
130 $key = $splitLine[0];
131 $value = $splitLine[1];
132 if (array_key_exists($key, $headers)) {
133 if (!is_array($headers[$key])) {
134 $headers[$key] = array(0 => $headers[$key]);
135 }
136 $headers[$key][] = $value;
137 } else {
138 $headers[$key] = $value;
139 }
140 } else {
141 $headers[] = $splitLine[0];
142 }
143 }
144
145 return array($headers, $content);
146}
147
148/**
149 * GET an HTTP URL to retrieve its content (fallback method)
150 *
151 * @param string $cleanUrl URL to get (http://... valid and in ASCII form)
152 * @param int $timeout network timeout (in seconds)
153 * @param int $maxBytes maximum downloaded bytes
154 * @param string $userAgent "User-Agent" header
155 * @param string $acceptLanguage "Accept-Language" header
156 * @param int $maxRedr maximum amount of redirections followed
157 *
158 * @return array HTTP response headers, downloaded content
159 *
160 * Output format:
161 * [0] = associative array containing HTTP response headers
162 * [1] = URL content (downloaded data)
163 *
164 * @see http://php.net/manual/en/function.file-get-contents.php
165 * @see http://php.net/manual/en/function.stream-context-create.php
166 * @see http://php.net/manual/en/function.get-headers.php
167 */
168function get_http_response_fallback(
169 $cleanUrl,
170 $timeout,
171 $maxBytes,
172 $userAgent,
173 $acceptLanguage,
174 $maxRedr
175) {
36 $options = array( 176 $options = array(
37 'http' => array( 177 'http' => array(
38 'method' => 'GET', 178 'method' => 'GET',
39 'timeout' => $timeout, 179 'timeout' => $timeout,
40 'user_agent' => 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:45.0)' 180 'user_agent' => $userAgent,
41 .' Gecko/20100101 Firefox/45.0', 181 'header' => "Accept: */*\r\n"
42 'accept_language' => substr(setlocale(LC_COLLATE, 0), 0, 2) . ',en-US;q=0.7,en;q=0.3', 182 . 'Accept-Language: ' . $acceptLanguage
43 ) 183 )
44 ); 184 );
45 185
46 stream_context_set_default($options); 186 stream_context_set_default($options);
47 list($headers, $finalUrl) = get_redirected_headers($cleanUrl); 187 list($headers, $finalUrl) = get_redirected_headers($cleanUrl, $maxRedr);
48 if (! $headers || strpos($headers[0], '200 OK') === false) { 188 if (! $headers || strpos($headers[0], '200 OK') === false) {
49 $options['http']['request_fulluri'] = true; 189 $options['http']['request_fulluri'] = true;
50 stream_context_set_default($options); 190 stream_context_set_default($options);
51 list($headers, $finalUrl) = get_redirected_headers($cleanUrl); 191 list($headers, $finalUrl) = get_redirected_headers($cleanUrl, $maxRedr);
52 } 192 }
53 193
54 if (! $headers || strpos($headers[0], '200 OK') === false) { 194 if (! $headers) {
55 return array($headers, false); 195 return array($headers, false);
56 } 196 }
57 197
@@ -215,3 +355,29 @@ function page_url($server)
215 } 355 }
216 return index_url($server); 356 return index_url($server);
217} 357}
358
359/**
360 * Retrieve the initial IP forwarded by the reverse proxy.
361 *
362 * Inspired from: https://github.com/zendframework/zend-http/blob/master/src/PhpEnvironment/RemoteAddress.php
363 *
364 * @param array $server $_SERVER array which contains HTTP headers.
365 * @param array $trustedIps List of trusted IP from the configuration.
366 *
367 * @return string|bool The forwarded IP, or false if none could be extracted.
368 */
369function getIpAddressFromProxy($server, $trustedIps)
370{
371 $forwardedIpHeader = 'HTTP_X_FORWARDED_FOR';
372 if (empty($server[$forwardedIpHeader])) {
373 return false;
374 }
375
376 $ips = preg_split('/\s*,\s*/', $server[$forwardedIpHeader]);
377 $ips = array_diff($ips, $trustedIps);
378 if (empty($ips)) {
379 return false;
380 }
381
382 return array_pop($ips);
383}
diff --git a/application/Languages.php b/application/Languages.php
new file mode 100644
index 00000000..c8b0a25a
--- /dev/null
+++ b/application/Languages.php
@@ -0,0 +1,21 @@
1<?php
2
3/**
4 * Wrapper function for translation which match the API
5 * of gettext()/_() and ngettext().
6 *
7 * Not doing translation for now.
8 *
9 * @param string $text Text to translate.
10 * @param string $nText The plural message ID.
11 * @param int $nb The number of items for plural forms.
12 *
13 * @return String Text translated.
14 */
15function t($text, $nText = '', $nb = 0) {
16 if (empty($nText)) {
17 return $text;
18 }
19 $actualForm = $nb > 1 ? $nText : $text;
20 return sprintf($actualForm, $nb);
21}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 1cb70de0..1e13286a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -6,14 +6,15 @@
6 * 6 *
7 * Example: 7 * Example:
8 * $myLinks = new LinkDB(); 8 * $myLinks = new LinkDB();
9 * echo $myLinks['20110826_161819']['title']; 9 * echo $myLinks[350]['title'];
10 * foreach ($myLinks as $link) 10 * foreach ($myLinks as $link)
11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; 11 * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
12 * 12 *
13 * Available keys: 13 * Available keys:
14 * - id: primary key, incremental integer identifier (persistent)
14 * - description: description of the entry 15 * - description: description of the entry
15 * - linkdate: date of the creation of this entry, in the form YYYYMMDD_HHMMSS 16 * - created: creation date of this entry, DateTime object.
16 * (e.g.'20110914_192317') 17 * - updated: last modification date of this entry, DateTime object.
17 * - private: Is this link private? 0=no, other value=yes 18 * - private: Is this link private? 0=no, other value=yes
18 * - tags: tags attached to this entry (separated by spaces) 19 * - tags: tags attached to this entry (separated by spaces)
19 * - title Title of the link 20 * - title Title of the link
@@ -21,16 +22,30 @@
21 * Can be absolute or relative. 22 * Can be absolute or relative.
22 * Relative URLs are permalinks (e.g.'?m-ukcw') 23 * Relative URLs are permalinks (e.g.'?m-ukcw')
23 * - real_url Absolute processed URL. 24 * - real_url Absolute processed URL.
25 * - shorturl Permalink smallhash
24 * 26 *
25 * Implements 3 interfaces: 27 * Implements 3 interfaces:
26 * - ArrayAccess: behaves like an associative array; 28 * - ArrayAccess: behaves like an associative array;
27 * - Countable: there is a count() method; 29 * - Countable: there is a count() method;
28 * - Iterator: usable in foreach () loops. 30 * - Iterator: usable in foreach () loops.
31 *
32 * ID mechanism:
33 * ArrayAccess is implemented in a way that will allow to access a link
34 * with the unique identifier ID directly with $link[ID].
35 * Note that it's not the real key of the link array attribute.
36 * This mechanism is in place to have persistent link IDs,
37 * even though the internal array is reordered by date.
38 * Example:
39 * - DB: link #1 (2010-01-01) link #2 (2016-01-01)
40 * - Order: #2 #1
41 * - Import links containing: link #3 (2013-01-01)
42 * - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
43 * - Real order: #2 #3 #1
29 */ 44 */
30class LinkDB implements Iterator, Countable, ArrayAccess 45class LinkDB implements Iterator, Countable, ArrayAccess
31{ 46{
32 // Links are stored as a PHP serialized string 47 // Links are stored as a PHP serialized string
33 private $_datastore; 48 private $datastore;
34 49
35 // Link date storage format 50 // Link date storage format
36 const LINK_DATE_FORMAT = 'Ymd_His'; 51 const LINK_DATE_FORMAT = 'Ymd_His';
@@ -44,26 +59,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess
44 // List of links (associative array) 59 // List of links (associative array)
45 // - key: link date (e.g. "20110823_124546"), 60 // - key: link date (e.g. "20110823_124546"),
46 // - value: associative array (keys: title, description...) 61 // - value: associative array (keys: title, description...)
47 private $_links; 62 private $links;
63
64 // List of all recorded URLs (key=url, value=link offset)
65 // for fast reserve search (url-->link offset)
66 private $urls;
48 67
49 // List of all recorded URLs (key=url, value=linkdate) 68 /**
50 // for fast reserve search (url-->linkdate) 69 * @var array List of all links IDS mapped with their array offset.
51 private $_urls; 70 * Map: id->offset.
71 */
72 protected $ids;
52 73
53 // List of linkdate keys (for the Iterator interface implementation) 74 // List of offset keys (for the Iterator interface implementation)
54 private $_keys; 75 private $keys;
55 76
56 // Position in the $this->_keys array (for the Iterator interface) 77 // Position in the $this->keys array (for the Iterator interface)
57 private $_position; 78 private $position;
58 79
59 // Is the user logged in? (used to filter private links) 80 // Is the user logged in? (used to filter private links)
60 private $_loggedIn; 81 private $loggedIn;
61 82
62 // Hide public links 83 // Hide public links
63 private $_hidePublicLinks; 84 private $hidePublicLinks;
64 85
65 // link redirector set in user settings. 86 // link redirector set in user settings.
66 private $_redirector; 87 private $redirector;
67 88
68 /** 89 /**
69 * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched. 90 * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched.
@@ -86,7 +107,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
86 * @param string $redirector link redirector set in user settings. 107 * @param string $redirector link redirector set in user settings.
87 * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true). 108 * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true).
88 */ 109 */
89 function __construct( 110 public function __construct(
90 $datastore, 111 $datastore,
91 $isLoggedIn, 112 $isLoggedIn,
92 $hidePublicLinks, 113 $hidePublicLinks,
@@ -94,13 +115,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess
94 $redirectorEncode = true 115 $redirectorEncode = true
95 ) 116 )
96 { 117 {
97 $this->_datastore = $datastore; 118 $this->datastore = $datastore;
98 $this->_loggedIn = $isLoggedIn; 119 $this->loggedIn = $isLoggedIn;
99 $this->_hidePublicLinks = $hidePublicLinks; 120 $this->hidePublicLinks = $hidePublicLinks;
100 $this->_redirector = $redirector; 121 $this->redirector = $redirector;
101 $this->redirectorEncode = $redirectorEncode === true; 122 $this->redirectorEncode = $redirectorEncode === true;
102 $this->_checkDB(); 123 $this->check();
103 $this->_readDB(); 124 $this->read();
104 } 125 }
105 126
106 /** 127 /**
@@ -108,7 +129,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
108 */ 129 */
109 public function count() 130 public function count()
110 { 131 {
111 return count($this->_links); 132 return count($this->links);
112 } 133 }
113 134
114 /** 135 /**
@@ -117,17 +138,29 @@ class LinkDB implements Iterator, Countable, ArrayAccess
117 public function offsetSet($offset, $value) 138 public function offsetSet($offset, $value)
118 { 139 {
119 // TODO: use exceptions instead of "die" 140 // TODO: use exceptions instead of "die"
120 if (!$this->_loggedIn) { 141 if (!$this->loggedIn) {
121 die('You are not authorized to add a link.'); 142 die('You are not authorized to add a link.');
122 } 143 }
123 if (empty($value['linkdate']) || empty($value['url'])) { 144 if (!isset($value['id']) || empty($value['url'])) {
124 die('Internal Error: A link should always have a linkdate and URL.'); 145 die('Internal Error: A link should always have an id and URL.');
146 }
147 if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
148 die('You must specify an integer as a key.');
125 } 149 }
126 if (empty($offset)) { 150 if (! empty($offset) && $offset !== $value['id']) {
127 die('You must specify a key.'); 151 die('Array offset and link ID must be equal.');
128 } 152 }
129 $this->_links[$offset] = $value; 153
130 $this->_urls[$value['url']]=$offset; 154 // If the link exists, we reuse the real offset, otherwise new entry
155 $existing = $this->getLinkOffset($offset);
156 if ($existing !== null) {
157 $offset = $existing;
158 } else {
159 $offset = count($this->links);
160 }
161 $this->links[$offset] = $value;
162 $this->urls[$value['url']] = $offset;
163 $this->ids[$value['id']] = $offset;
131 } 164 }
132 165
133 /** 166 /**
@@ -135,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
135 */ 168 */
136 public function offsetExists($offset) 169 public function offsetExists($offset)
137 { 170 {
138 return array_key_exists($offset, $this->_links); 171 return array_key_exists($this->getLinkOffset($offset), $this->links);
139 } 172 }
140 173
141 /** 174 /**
@@ -143,13 +176,15 @@ class LinkDB implements Iterator, Countable, ArrayAccess
143 */ 176 */
144 public function offsetUnset($offset) 177 public function offsetUnset($offset)
145 { 178 {
146 if (!$this->_loggedIn) { 179 if (!$this->loggedIn) {
147 // TODO: raise an exception 180 // TODO: raise an exception
148 die('You are not authorized to delete a link.'); 181 die('You are not authorized to delete a link.');
149 } 182 }
150 $url = $this->_links[$offset]['url']; 183 $realOffset = $this->getLinkOffset($offset);
151 unset($this->_urls[$url]); 184 $url = $this->links[$realOffset]['url'];
152 unset($this->_links[$offset]); 185 unset($this->urls[$url]);
186 unset($this->ids[$realOffset]);
187 unset($this->links[$realOffset]);
153 } 188 }
154 189
155 /** 190 /**
@@ -157,31 +192,32 @@ class LinkDB implements Iterator, Countable, ArrayAccess
157 */ 192 */
158 public function offsetGet($offset) 193 public function offsetGet($offset)
159 { 194 {
160 return isset($this->_links[$offset]) ? $this->_links[$offset] : null; 195 $realOffset = $this->getLinkOffset($offset);
196 return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
161 } 197 }
162 198
163 /** 199 /**
164 * Iterator - Returns the current element 200 * Iterator - Returns the current element
165 */ 201 */
166 function current() 202 public function current()
167 { 203 {
168 return $this->_links[$this->_keys[$this->_position]]; 204 return $this[$this->keys[$this->position]];
169 } 205 }
170 206
171 /** 207 /**
172 * Iterator - Returns the key of the current element 208 * Iterator - Returns the key of the current element
173 */ 209 */
174 function key() 210 public function key()
175 { 211 {
176 return $this->_keys[$this->_position]; 212 return $this->keys[$this->position];
177 } 213 }
178 214
179 /** 215 /**
180 * Iterator - Moves forward to next element 216 * Iterator - Moves forward to next element
181 */ 217 */
182 function next() 218 public function next()
183 { 219 {
184 ++$this->_position; 220 ++$this->position;
185 } 221 }
186 222
187 /** 223 /**
@@ -189,19 +225,18 @@ class LinkDB implements Iterator, Countable, ArrayAccess
189 * 225 *
190 * Entries are sorted by date (latest first) 226 * Entries are sorted by date (latest first)
191 */ 227 */
192 function rewind() 228 public function rewind()
193 { 229 {
194 $this->_keys = array_keys($this->_links); 230 $this->keys = array_keys($this->ids);
195 rsort($this->_keys); 231 $this->position = 0;
196 $this->_position = 0;
197 } 232 }
198 233
199 /** 234 /**
200 * Iterator - Checks if current position is valid 235 * Iterator - Checks if current position is valid
201 */ 236 */
202 function valid() 237 public function valid()
203 { 238 {
204 return isset($this->_keys[$this->_position]); 239 return isset($this->keys[$this->position]);
205 } 240 }
206 241
207 /** 242 /**
@@ -209,15 +244,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
209 * 244 *
210 * If no DB file is found, creates a dummy DB. 245 * If no DB file is found, creates a dummy DB.
211 */ 246 */
212 private function _checkDB() 247 private function check()
213 { 248 {
214 if (file_exists($this->_datastore)) { 249 if (file_exists($this->datastore)) {
215 return; 250 return;
216 } 251 }
217 252
218 // Create a dummy database for example 253 // Create a dummy database for example
219 $this->_links = array(); 254 $this->links = array();
220 $link = array( 255 $link = array(
256 'id' => 1,
221 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 257 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
222 'url'=>'https://github.com/shaarli/Shaarli/wiki', 258 'url'=>'https://github.com/shaarli/Shaarli/wiki',
223 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 259 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@@ -226,77 +262,69 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
226 262
227You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 263You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
228 'private'=>0, 264 'private'=>0,
229 'linkdate'=> date('Ymd_His'), 265 'created'=> new DateTime(),
230 'tags'=>'opensource software' 266 'tags'=>'opensource software'
231 ); 267 );
232 $this->_links[$link['linkdate']] = $link; 268 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
269 $this->links[1] = $link;
233 270
234 $link = array( 271 $link = array(
272 'id' => 0,
235 'title'=>'My secret stuff... - Pastebin.com', 273 'title'=>'My secret stuff... - Pastebin.com',
236 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 274 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
237 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', 275 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
238 'private'=>1, 276 'private'=>1,
239 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 277 'created'=> new DateTime('1 minute ago'),
240 'tags'=>'secretstuff' 278 'tags'=>'secretstuff',
241 ); 279 );
242 $this->_links[$link['linkdate']] = $link; 280 $link['shorturl'] = link_small_hash($link['created'], $link['id']);
281 $this->links[0] = $link;
243 282
244 // Write database to disk 283 // Write database to disk
245 $this->writeDB(); 284 $this->write();
246 } 285 }
247 286
248 /** 287 /**
249 * Reads database from disk to memory 288 * Reads database from disk to memory
250 */ 289 */
251 private function _readDB() 290 private function read()
252 { 291 {
253
254 // Public links are hidden and user not logged in => nothing to show 292 // Public links are hidden and user not logged in => nothing to show
255 if ($this->_hidePublicLinks && !$this->_loggedIn) { 293 if ($this->hidePublicLinks && !$this->loggedIn) {
256 $this->_links = array(); 294 $this->links = array();
257 return; 295 return;
258 } 296 }
259 297
260 // Read data 298 // Read data
261 // Note that gzinflate is faster than gzuncompress. 299 // Note that gzinflate is faster than gzuncompress.
262 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 300 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
263 $this->_links = array(); 301 $this->links = array();
264 302
265 if (file_exists($this->_datastore)) { 303 if (file_exists($this->datastore)) {
266 $this->_links = unserialize(gzinflate(base64_decode( 304 $this->links = unserialize(gzinflate(base64_decode(
267 substr(file_get_contents($this->_datastore), 305 substr(file_get_contents($this->datastore),
268 strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); 306 strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
269 } 307 }
270 308
271 // If user is not logged in, filter private links. 309 $toremove = array();
272 if (!$this->_loggedIn) { 310 foreach ($this->links as $key => &$link) {
273 $toremove = array(); 311 if (! $this->loggedIn && $link['private'] != 0) {
274 foreach ($this->_links as $link) { 312 // Transition for not upgraded databases.
275 if ($link['private'] != 0) { 313 $toremove[] = $key;
276 $toremove[] = $link['linkdate']; 314 continue;
277 }
278 }
279 foreach ($toremove as $linkdate) {
280 unset($this->_links[$linkdate]);
281 } 315 }
282 }
283
284 $this->_urls = array();
285 foreach ($this->_links as &$link) {
286 // Keep the list of the mapping URLs-->linkdate up-to-date.
287 $this->_urls[$link['url']] = $link['linkdate'];
288 316
289 // Sanitize data fields. 317 // Sanitize data fields.
290 sanitizeLink($link); 318 sanitizeLink($link);
291 319
292 // Remove private tags if the user is not logged in. 320 // Remove private tags if the user is not logged in.
293 if (! $this->_loggedIn) { 321 if (! $this->loggedIn) {
294 $link['tags'] = preg_replace('/(^| )\.[^($| )]+/', '', $link['tags']); 322 $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
295 } 323 }
296 324
297 // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). 325 // Do not use the redirector for internal links (Shaarli note URL starting with a '?').
298 if (!empty($this->_redirector) && !startsWith($link['url'], '?')) { 326 if (!empty($this->redirector) && !startsWith($link['url'], '?')) {
299 $link['real_url'] = $this->_redirector; 327 $link['real_url'] = $this->redirector;
300 if ($this->redirectorEncode) { 328 if ($this->redirectorEncode) {
301 $link['real_url'] .= urlencode(unescape($link['url'])); 329 $link['real_url'] .= urlencode(unescape($link['url']));
302 } else { 330 } else {
@@ -306,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba
306 else { 334 else {
307 $link['real_url'] = $link['url']; 335 $link['real_url'] = $link['url'];
308 } 336 }
337
338 // To be able to load links before running the update, and prepare the update
339 if (! isset($link['created'])) {
340 $link['id'] = $link['linkdate'];
341 $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
342 if (! empty($link['updated'])) {
343 $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
344 }
345 $link['shorturl'] = smallHash($link['linkdate']);
346 }
309 } 347 }
348
349 // If user is not logged in, filter private links.
350 foreach ($toremove as $offset) {
351 unset($this->links[$offset]);
352 }
353
354 $this->reorder();
310 } 355 }
311 356
312 /** 357 /**
@@ -314,19 +359,19 @@ You use the community supported version of the original Shaarli project, by Seba
314 * 359 *
315 * @throws IOException the datastore is not writable 360 * @throws IOException the datastore is not writable
316 */ 361 */
317 private function writeDB() 362 private function write()
318 { 363 {
319 if (is_file($this->_datastore) && !is_writeable($this->_datastore)) { 364 if (is_file($this->datastore) && !is_writeable($this->datastore)) {
320 // The datastore exists but is not writeable 365 // The datastore exists but is not writeable
321 throw new IOException($this->_datastore); 366 throw new IOException($this->datastore);
322 } else if (!is_file($this->_datastore) && !is_writeable(dirname($this->_datastore))) { 367 } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
323 // The datastore does not exist and its parent directory is not writeable 368 // The datastore does not exist and its parent directory is not writeable
324 throw new IOException(dirname($this->_datastore)); 369 throw new IOException(dirname($this->datastore));
325 } 370 }
326 371
327 file_put_contents( 372 file_put_contents(
328 $this->_datastore, 373 $this->datastore,
329 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix 374 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
330 ); 375 );
331 376
332 } 377 }
@@ -336,14 +381,14 @@ You use the community supported version of the original Shaarli project, by Seba
336 * 381 *
337 * @param string $pageCacheDir page cache directory 382 * @param string $pageCacheDir page cache directory
338 */ 383 */
339 public function savedb($pageCacheDir) 384 public function save($pageCacheDir)
340 { 385 {
341 if (!$this->_loggedIn) { 386 if (!$this->loggedIn) {
342 // TODO: raise an Exception instead 387 // TODO: raise an Exception instead
343 die('You are not authorized to change the database.'); 388 die('You are not authorized to change the database.');
344 } 389 }
345 390
346 $this->writeDB(); 391 $this->write();
347 392
348 invalidateCaches($pageCacheDir); 393 invalidateCaches($pageCacheDir);
349 } 394 }
@@ -357,8 +402,8 @@ You use the community supported version of the original Shaarli project, by Seba
357 */ 402 */
358 public function getLinkFromUrl($url) 403 public function getLinkFromUrl($url)
359 { 404 {
360 if (isset($this->_urls[$url])) { 405 if (isset($this->urls[$url])) {
361 return $this->_links[$this->_urls[$url]]; 406 return $this->links[$this->urls[$url]];
362 } 407 }
363 return false; 408 return false;
364 } 409 }
@@ -375,7 +420,7 @@ You use the community supported version of the original Shaarli project, by Seba
375 public function filterHash($request) 420 public function filterHash($request)
376 { 421 {
377 $request = substr($request, 0, 6); 422 $request = substr($request, 0, 6);
378 $linkFilter = new LinkFilter($this->_links); 423 $linkFilter = new LinkFilter($this->links);
379 return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); 424 return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request);
380 } 425 }
381 426
@@ -387,7 +432,7 @@ You use the community supported version of the original Shaarli project, by Seba
387 * @return array list of shaare found. 432 * @return array list of shaare found.
388 */ 433 */
389 public function filterDay($request) { 434 public function filterDay($request) {
390 $linkFilter = new LinkFilter($this->_links); 435 $linkFilter = new LinkFilter($this->links);
391 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); 436 return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
392 } 437 }
393 438
@@ -409,7 +454,7 @@ You use the community supported version of the original Shaarli project, by Seba
409 $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; 454 $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
410 455
411 // Search tags + fullsearch. 456 // Search tags + fullsearch.
412 if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { 457 if (! empty($searchtags) && ! empty($searchterm)) {
413 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; 458 $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
414 $request = array($searchtags, $searchterm); 459 $request = array($searchtags, $searchterm);
415 } 460 }
@@ -429,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba
429 $request = ''; 474 $request = '';
430 } 475 }
431 476
432 $linkFilter = new LinkFilter($this->_links); 477 $linkFilter = new LinkFilter($this);
433 return $linkFilter->filter($type, $request, $casesensitive, $privateonly); 478 return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
434 } 479 }
435 480
@@ -440,11 +485,18 @@ You use the community supported version of the original Shaarli project, by Seba
440 public function allTags() 485 public function allTags()
441 { 486 {
442 $tags = array(); 487 $tags = array();
443 foreach ($this->_links as $link) { 488 $caseMapping = array();
444 foreach (explode(' ', $link['tags']) as $tag) { 489 foreach ($this->links as $link) {
445 if (!empty($tag)) { 490 foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
446 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); 491 if (empty($tag)) {
492 continue;
447 } 493 }
494 // The first case found will be displayed.
495 if (!isset($caseMapping[strtolower($tag)])) {
496 $caseMapping[strtolower($tag)] = $tag;
497 $tags[$caseMapping[strtolower($tag)]] = 0;
498 }
499 $tags[$caseMapping[strtolower($tag)]]++;
448 } 500 }
449 } 501 }
450 // Sort tags by usage (most used tag first) 502 // Sort tags by usage (most used tag first)
@@ -459,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba
459 public function days() 511 public function days()
460 { 512 {
461 $linkDays = array(); 513 $linkDays = array();
462 foreach (array_keys($this->_links) as $day) { 514 foreach ($this->links as $link) {
463 $linkDays[substr($day, 0, 8)] = 0; 515 $linkDays[$link['created']->format('Ymd')] = 0;
464 } 516 }
465 $linkDays = array_keys($linkDays); 517 $linkDays = array_keys($linkDays);
466 sort($linkDays); 518 sort($linkDays);
467 519
468 return $linkDays; 520 return $linkDays;
469 } 521 }
522
523 /**
524 * Reorder links by creation date (newest first).
525 *
526 * Also update the urls and ids mapping arrays.
527 *
528 * @param string $order ASC|DESC
529 */
530 public function reorder($order = 'DESC')
531 {
532 $order = $order === 'ASC' ? -1 : 1;
533 // Reorder array by dates.
534 usort($this->links, function($a, $b) use ($order) {
535 return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
536 });
537
538 $this->urls = array();
539 $this->ids = array();
540 foreach ($this->links as $key => $link) {
541 $this->urls[$link['url']] = $key;
542 $this->ids[$link['id']] = $key;
543 }
544 }
545
546 /**
547 * Return the next key for link creation.
548 * E.g. If the last ID is 597, the next will be 598.
549 *
550 * @return int next ID.
551 */
552 public function getNextId()
553 {
554 if (!empty($this->ids)) {
555 return max(array_keys($this->ids)) + 1;
556 }
557 return 0;
558 }
559
560 /**
561 * Returns a link offset in links array from its unique ID.
562 *
563 * @param int $id Persistent ID of a link.
564 *
565 * @return int Real offset in local array, or null if doesn't exist.
566 */
567 protected function getLinkOffset($id)
568 {
569 if (isset($this->ids[$id])) {
570 return $this->ids[$id];
571 }
572 return null;
573 }
470} 574}
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index e693b284..daa6d9cc 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -28,12 +28,17 @@ class LinkFilter
28 public static $FILTER_DAY = 'FILTER_DAY'; 28 public static $FILTER_DAY = 'FILTER_DAY';
29 29
30 /** 30 /**
31 * @var array all available links. 31 * @var string Allowed characters for hashtags (regex syntax).
32 */
33 public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
34
35 /**
36 * @var LinkDB all available links.
32 */ 37 */
33 private $links; 38 private $links;
34 39
35 /** 40 /**
36 * @param array $links initialization. 41 * @param LinkDB $links initialization.
37 */ 42 */
38 public function __construct($links) 43 public function __construct($links)
39 { 44 {
@@ -89,18 +94,16 @@ class LinkFilter
89 private function noFilter($privateonly = false) 94 private function noFilter($privateonly = false)
90 { 95 {
91 if (! $privateonly) { 96 if (! $privateonly) {
92 krsort($this->links);
93 return $this->links; 97 return $this->links;
94 } 98 }
95 99
96 $out = array(); 100 $out = array();
97 foreach ($this->links as $value) { 101 foreach ($this->links as $key => $value) {
98 if ($value['private']) { 102 if ($value['private']) {
99 $out[$value['linkdate']] = $value; 103 $out[$key] = $value;
100 } 104 }
101 } 105 }
102 106
103 krsort($out);
104 return $out; 107 return $out;
105 } 108 }
106 109
@@ -116,10 +119,10 @@ class LinkFilter
116 private function filterSmallHash($smallHash) 119 private function filterSmallHash($smallHash)
117 { 120 {
118 $filtered = array(); 121 $filtered = array();
119 foreach ($this->links as $l) { 122 foreach ($this->links as $key => $l) {
120 if ($smallHash == smallHash($l['linkdate'])) { 123 if ($smallHash == $l['shorturl']) {
121 // Yes, this is ugly and slow 124 // Yes, this is ugly and slow
122 $filtered[$l['linkdate']] = $l; 125 $filtered[$key] = $l;
123 return $filtered; 126 return $filtered;
124 } 127 }
125 } 128 }
@@ -183,7 +186,7 @@ class LinkFilter
183 $keys = array('title', 'description', 'url', 'tags'); 186 $keys = array('title', 'description', 'url', 'tags');
184 187
185 // Iterate over every stored link. 188 // Iterate over every stored link.
186 foreach ($this->links as $link) { 189 foreach ($this->links as $id => $link) {
187 190
188 // ignore non private links when 'privatonly' is on. 191 // ignore non private links when 'privatonly' is on.
189 if (! $link['private'] && $privateonly === true) { 192 if (! $link['private'] && $privateonly === true) {
@@ -217,11 +220,10 @@ class LinkFilter
217 } 220 }
218 221
219 if ($found) { 222 if ($found) {
220 $filtered[$link['linkdate']] = $link; 223 $filtered[$id] = $link;
221 } 224 }
222 } 225 }
223 226
224 krsort($filtered);
225 return $filtered; 227 return $filtered;
226 } 228 }
227 229
@@ -251,7 +253,7 @@ class LinkFilter
251 return $filtered; 253 return $filtered;
252 } 254 }
253 255
254 foreach ($this->links as $link) { 256 foreach ($this->links as $key => $link) {
255 // ignore non private links when 'privatonly' is on. 257 // ignore non private links when 'privatonly' is on.
256 if (! $link['private'] && $privateonly === true) { 258 if (! $link['private'] && $privateonly === true) {
257 continue; 259 continue;
@@ -263,18 +265,19 @@ class LinkFilter
263 for ($i = 0 ; $i < count($searchtags) && $found; $i++) { 265 for ($i = 0 ; $i < count($searchtags) && $found; $i++) {
264 // Exclusive search, quit if tag found. 266 // Exclusive search, quit if tag found.
265 // Or, tag not found in the link, quit. 267 // Or, tag not found in the link, quit.
266 if (($searchtags[$i][0] == '-' && in_array(substr($searchtags[$i], 1), $linktags)) 268 if (($searchtags[$i][0] == '-'
267 || ($searchtags[$i][0] != '-') && ! in_array($searchtags[$i], $linktags) 269 && $this->searchTagAndHashTag(substr($searchtags[$i], 1), $linktags, $link['description']))
270 || ($searchtags[$i][0] != '-')
271 && ! $this->searchTagAndHashTag($searchtags[$i], $linktags, $link['description'])
268 ) { 272 ) {
269 $found = false; 273 $found = false;
270 } 274 }
271 } 275 }
272 276
273 if ($found) { 277 if ($found) {
274 $filtered[$link['linkdate']] = $link; 278 $filtered[$key] = $link;
275 } 279 }
276 } 280 }
277 krsort($filtered);
278 return $filtered; 281 return $filtered;
279 } 282 }
280 283
@@ -297,13 +300,36 @@ class LinkFilter
297 } 300 }
298 301
299 $filtered = array(); 302 $filtered = array();
300 foreach ($this->links as $l) { 303 foreach ($this->links as $key => $l) {
301 if (startsWith($l['linkdate'], $day)) { 304 if ($l['created']->format('Ymd') == $day) {
302 $filtered[$l['linkdate']] = $l; 305 $filtered[$key] = $l;
303 } 306 }
304 } 307 }
305 ksort($filtered); 308
306 return $filtered; 309 // sort by date ASC
310 return array_reverse($filtered, true);
311 }
312
313 /**
314 * Check if a tag is found in the taglist, or as an hashtag in the link description.
315 *
316 * @param string $tag Tag to search.
317 * @param array $taglist List of tags for the current link.
318 * @param string $description Link description.
319 *
320 * @return bool True if found, false otherwise.
321 */
322 protected function searchTagAndHashTag($tag, $taglist, $description)
323 {
324 if (in_array($tag, $taglist)) {
325 return true;
326 }
327
328 if (preg_match('/(^| )#'. $tag .'([^'. self::$HASHTAG_CHARS .']|$)/mui', $description) > 0) {
329 return true;
330 }
331
332 return false;
307 } 333 }
308 334
309 /** 335 /**
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index da04ca97..cf58f808 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -81,7 +81,7 @@ function html_extract_charset($html)
81/** 81/**
82 * Count private links in given linklist. 82 * Count private links in given linklist.
83 * 83 *
84 * @param array $links Linklist. 84 * @param array|Countable $links Linklist.
85 * 85 *
86 * @return int Number of private links. 86 * @return int Number of private links.
87 */ 87 */
@@ -91,5 +91,94 @@ function count_private($links)
91 foreach ($links as $link) { 91 foreach ($links as $link) {
92 $cpt = $link['private'] == true ? $cpt + 1 : $cpt; 92 $cpt = $link['private'] == true ? $cpt + 1 : $cpt;
93 } 93 }
94
94 return $cpt; 95 return $cpt;
95} 96}
97
98/**
99 * In a string, converts URLs to clickable links.
100 *
101 * @param string $text input string.
102 * @param string $redirector if a redirector is set, use it to gerenate links.
103 *
104 * @return string returns $text with all links converted to HTML links.
105 *
106 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
107 */
108function text2clickable($text, $redirector = '')
109{
110 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
111
112 if (empty($redirector)) {
113 return preg_replace($regex, '<a href="$1">$1</a>', $text);
114 }
115 // Redirector is set, urlencode the final URL.
116 return preg_replace_callback(
117 $regex,
118 function ($matches) use ($redirector) {
119 return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
120 },
121 $text
122 );
123}
124
125/**
126 * Auto-link hashtags.
127 *
128 * @param string $description Given description.
129 * @param string $indexUrl Root URL.
130 *
131 * @return string Description with auto-linked hashtags.
132 */
133function hashtag_autolink($description, $indexUrl = '')
134{
135 /*
136 * To support unicode: http://stackoverflow.com/a/35498078/1484919
137 * \p{Pc} - to match underscore
138 * \p{N} - numeric character in any script
139 * \p{L} - letter from any language
140 * \p{Mn} - any non marking space (accents, umlauts, etc)
141 */
142 $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui';
143 $replacement = '$1<a href="'. $indexUrl .'?addtag=$2" title="Hashtag $2">#$2</a>';
144 return preg_replace($regex, $replacement, $description);
145}
146
147/**
148 * This function inserts &nbsp; where relevant so that multiple spaces are properly displayed in HTML
149 * even in the absence of <pre> (This is used in description to keep text formatting).
150 *
151 * @param string $text input text.
152 *
153 * @return string formatted text.
154 */
155function space2nbsp($text)
156{
157 return preg_replace('/(^| ) /m', '$1&nbsp;', $text);
158}
159
160/**
161 * Format Shaarli's description
162 *
163 * @param string $description shaare's description.
164 * @param string $redirector if a redirector is set, use it to gerenate links.
165 * @param string $indexUrl URL to Shaarli's index.
166 *
167 * @return string formatted description.
168 */
169function format_description($description, $redirector = '', $indexUrl = '') {
170 return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
171}
172
173/**
174 * Generate a small hash for a link.
175 *
176 * @param DateTime $date Link creation date.
177 * @param int $id Link ID.
178 *
179 * @return string the small hash generated from link data.
180 */
181function link_small_hash($date, $id)
182{
183 return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
184}
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index fdbb0ad7..e7148d00 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -38,7 +38,7 @@ class NetscapeBookmarkUtils
38 if ($link['private'] == 0 && $selection == 'private') { 38 if ($link['private'] == 0 && $selection == 'private') {
39 continue; 39 continue;
40 } 40 }
41 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 41 $date = $link['created'];
42 $link['timestamp'] = $date->getTimestamp(); 42 $link['timestamp'] = $date->getTimestamp();
43 $link['taglist'] = str_replace(' ', ',', $link['tags']); 43 $link['taglist'] = str_replace(' ', ',', $link['tags']);
44 44
@@ -51,4 +51,141 @@ class NetscapeBookmarkUtils
51 51
52 return $bookmarkLinks; 52 return $bookmarkLinks;
53 } 53 }
54
55 /**
56 * Generates an import status summary
57 *
58 * @param string $filename name of the file to import
59 * @param int $filesize size of the file to import
60 * @param int $importCount how many links were imported
61 * @param int $overwriteCount how many links were overwritten
62 * @param int $skipCount how many links were skipped
63 *
64 * @return string Summary of the bookmark import status
65 */
66 private static function importStatus(
67 $filename,
68 $filesize,
69 $importCount=0,
70 $overwriteCount=0,
71 $skipCount=0
72 )
73 {
74 $status = 'File '.$filename.' ('.$filesize.' bytes) ';
75 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
76 $status .= 'has an unknown file format. Nothing was imported.';
77 } else {
78 $status .= 'was successfully processed: '.$importCount.' links imported, ';
79 $status .= $overwriteCount.' links overwritten, ';
80 $status .= $skipCount.' links skipped.';
81 }
82 return $status;
83 }
84
85 /**
86 * Imports Web bookmarks from an uploaded Netscape bookmark dump
87 *
88 * @param array $post Server $_POST parameters
89 * @param array $files Server $_FILES parameters
90 * @param LinkDB $linkDb Loaded LinkDB instance
91 * @param string $pagecache Page cache
92 *
93 * @return string Summary of the bookmark import status
94 */
95 public static function import($post, $files, $linkDb, $pagecache)
96 {
97 $filename = $files['filetoupload']['name'];
98 $filesize = $files['filetoupload']['size'];
99 $data = file_get_contents($files['filetoupload']['tmp_name']);
100
101 if (strpos($data, '<!DOCTYPE NETSCAPE-Bookmark-file-1>') === false) {
102 return self::importStatus($filename, $filesize);
103 }
104
105 // Overwrite existing links?
106 $overwrite = ! empty($post['overwrite']);
107
108 // Add tags to all imported links?
109 if (empty($post['default_tags'])) {
110 $defaultTags = array();
111 } else {
112 $defaultTags = preg_split(
113 '/[\s,]+/',
114 escape($post['default_tags'])
115 );
116 }
117
118 // links are imported as public by default
119 $defaultPrivacy = 0;
120
121 $parser = new NetscapeBookmarkParser(
122 true, // nested tag support
123 $defaultTags, // additional user-specified tags
124 strval(1 - $defaultPrivacy) // defaultPub = 1 - defaultPrivacy
125 );
126 $bookmarks = $parser->parseString($data);
127
128 $importCount = 0;
129 $overwriteCount = 0;
130 $skipCount = 0;
131
132 foreach ($bookmarks as $bkm) {
133 $private = $defaultPrivacy;
134 if (empty($post['privacy']) || $post['privacy'] == 'default') {
135 // use value from the imported file
136 $private = $bkm['pub'] == '1' ? 0 : 1;
137 } else if ($post['privacy'] == 'private') {
138 // all imported links are private
139 $private = 1;
140 } else if ($post['privacy'] == 'public') {
141 // all imported links are public
142 $private = 0;
143 }
144
145 $newLink = array(
146 'title' => $bkm['title'],
147 'url' => $bkm['uri'],
148 'description' => $bkm['note'],
149 'private' => $private,
150 'tags' => $bkm['tags']
151 );
152
153 $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
154
155 if ($existingLink !== false) {
156 if ($overwrite === false) {
157 // Do not overwrite an existing link
158 $skipCount++;
159 continue;
160 }
161
162 // Overwrite an existing link, keep its date
163 $newLink['id'] = $existingLink['id'];
164 $newLink['created'] = $existingLink['created'];
165 $newLink['updated'] = new DateTime();
166 $linkDb[$existingLink['id']] = $newLink;
167 $importCount++;
168 $overwriteCount++;
169 continue;
170 }
171
172 // Add a new link - @ used for UNIX timestamps
173 $newLinkDate = new DateTime('@'.strval($bkm['time']));
174 $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
175 $newLink['created'] = $newLinkDate;
176 $newLink['id'] = $linkDb->getNextId();
177 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
178 $linkDb[$newLink['id']] = $newLink;
179 $importCount++;
180 }
181
182 $linkDb->save($pagecache);
183 return self::importStatus(
184 $filename,
185 $filesize,
186 $importCount,
187 $overwriteCount,
188 $skipCount
189 );
190 }
54} 191}
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 82580787..32c7f9f1 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -15,12 +15,20 @@ class PageBuilder
15 private $tpl; 15 private $tpl;
16 16
17 /** 17 /**
18 * @var ConfigManager $conf Configuration Manager instance.
19 */
20 protected $conf;
21
22 /**
18 * PageBuilder constructor. 23 * PageBuilder constructor.
19 * $tpl is initialized at false for lazy loading. 24 * $tpl is initialized at false for lazy loading.
25 *
26 * @param ConfigManager $conf Configuration Manager instance (reference).
20 */ 27 */
21 function __construct() 28 function __construct(&$conf)
22 { 29 {
23 $this->tpl = false; 30 $this->tpl = false;
31 $this->conf = $conf;
24 } 32 }
25 33
26 /** 34 /**
@@ -33,17 +41,17 @@ class PageBuilder
33 try { 41 try {
34 $version = ApplicationUtils::checkUpdate( 42 $version = ApplicationUtils::checkUpdate(
35 shaarli_version, 43 shaarli_version,
36 $GLOBALS['config']['UPDATECHECK_FILENAME'], 44 $this->conf->get('resource.update_check'),
37 $GLOBALS['config']['UPDATECHECK_INTERVAL'], 45 $this->conf->get('updates.check_updates_interval'),
38 $GLOBALS['config']['ENABLE_UPDATECHECK'], 46 $this->conf->get('updates.check_updates'),
39 isLoggedIn(), 47 isLoggedIn(),
40 $GLOBALS['config']['UPDATECHECK_BRANCH'] 48 $this->conf->get('updates.check_updates_branch')
41 ); 49 );
42 $this->tpl->assign('newVersion', escape($version)); 50 $this->tpl->assign('newVersion', escape($version));
43 $this->tpl->assign('versionError', ''); 51 $this->tpl->assign('versionError', '');
44 52
45 } catch (Exception $exc) { 53 } catch (Exception $exc) {
46 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage()); 54 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
47 $this->tpl->assign('newVersion', ''); 55 $this->tpl->assign('newVersion', '');
48 $this->tpl->assign('versionError', escape($exc->getMessage())); 56 $this->tpl->assign('versionError', escape($exc->getMessage()));
49 } 57 }
@@ -60,21 +68,18 @@ class PageBuilder
60 $this->tpl->assign('source', index_url($_SERVER)); 68 $this->tpl->assign('source', index_url($_SERVER));
61 $this->tpl->assign('version', shaarli_version); 69 $this->tpl->assign('version', shaarli_version);
62 $this->tpl->assign('scripturl', index_url($_SERVER)); 70 $this->tpl->assign('scripturl', index_url($_SERVER));
63 $this->tpl->assign('pagetitle', 'Shaarli');
64 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? 71 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
65 if (!empty($GLOBALS['title'])) { 72 $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli'));
66 $this->tpl->assign('pagetitle', $GLOBALS['title']); 73 if ($this->conf->exists('general.header_link')) {
67 } 74 $this->tpl->assign('titleLink', $this->conf->get('general.header_link'));
68 if (!empty($GLOBALS['titleLink'])) {
69 $this->tpl->assign('titleLink', $GLOBALS['titleLink']);
70 }
71 if (!empty($GLOBALS['pagetitle'])) {
72 $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']);
73 }
74 $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']);
75 if (!empty($GLOBALS['plugin_errors'])) {
76 $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']);
77 } 75 }
76 $this->tpl->assign('shaarlititle', $this->conf->get('general.title', 'Shaarli'));
77 $this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false));
78 $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', false));
79 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
80 $this->tpl->assign('token', getToken($this->conf));
81 // To be removed with a proper theme configuration.
82 $this->tpl->assign('conf', $this->conf);
78 } 83 }
79 84
80 /** 85 /**
@@ -85,7 +90,6 @@ class PageBuilder
85 */ 90 */
86 public function assign($placeholder, $value) 91 public function assign($placeholder, $value)
87 { 92 {
88 // Lazy initialization
89 if ($this->tpl === false) { 93 if ($this->tpl === false) {
90 $this->initialize(); 94 $this->initialize();
91 } 95 }
@@ -101,7 +105,6 @@ class PageBuilder
101 */ 105 */
102 public function assignAll($data) 106 public function assignAll($data)
103 { 107 {
104 // Lazy initialization
105 if ($this->tpl === false) { 108 if ($this->tpl === false) {
106 $this->initialize(); 109 $this->initialize();
107 } 110 }
@@ -113,6 +116,7 @@ class PageBuilder
113 foreach ($data as $key => $value) { 116 foreach ($data as $key => $value) {
114 $this->assign($key, $value); 117 $this->assign($key, $value);
115 } 118 }
119 return true;
116 } 120 }
117 121
118 /** 122 /**
@@ -123,10 +127,10 @@ class PageBuilder
123 */ 127 */
124 public function renderPage($page) 128 public function renderPage($page)
125 { 129 {
126 // Lazy initialization 130 if ($this->tpl === false) {
127 if ($this->tpl===false) {
128 $this->initialize(); 131 $this->initialize();
129 } 132 }
133
130 $this->tpl->draw($page); 134 $this->tpl->draw($page);
131 } 135 }
132 136
diff --git a/application/PluginManager.php b/application/PluginManager.php
index 787ac6a9..59ece4fa 100644
--- a/application/PluginManager.php
+++ b/application/PluginManager.php
@@ -4,18 +4,10 @@
4 * Class PluginManager 4 * Class PluginManager
5 * 5 *
6 * Use to manage, load and execute plugins. 6 * Use to manage, load and execute plugins.
7 *
8 * Using Singleton design pattern.
9 */ 7 */
10class PluginManager 8class PluginManager
11{ 9{
12 /** 10 /**
13 * PluginManager singleton instance.
14 * @var PluginManager $instance
15 */
16 private static $instance;
17
18 /**
19 * List of authorized plugins from configuration file. 11 * List of authorized plugins from configuration file.
20 * @var array $authorizedPlugins 12 * @var array $authorizedPlugins
21 */ 13 */
@@ -28,45 +20,36 @@ class PluginManager
28 private $loadedPlugins = array(); 20 private $loadedPlugins = array();
29 21
30 /** 22 /**
31 * Plugins subdirectory. 23 * @var ConfigManager Configuration Manager instance.
32 * @var string $PLUGINS_PATH
33 */ 24 */
34 public static $PLUGINS_PATH = 'plugins'; 25 protected $conf;
35 26
36 /** 27 /**
37 * Plugins meta files extension. 28 * @var array List of plugin errors.
38 * @var string $META_EXT
39 */ 29 */
40 public static $META_EXT = 'meta'; 30 protected $errors;
41 31
42 /** 32 /**
43 * Private constructor: new instances not allowed. 33 * Plugins subdirectory.
34 * @var string $PLUGINS_PATH
44 */ 35 */
45 private function __construct() 36 public static $PLUGINS_PATH = 'plugins';
46 {
47 }
48 37
49 /** 38 /**
50 * Cloning isn't allowed either. 39 * Plugins meta files extension.
51 * 40 * @var string $META_EXT
52 * @return void
53 */ 41 */
54 private function __clone() 42 public static $META_EXT = 'meta';
55 {
56 }
57 43
58 /** 44 /**
59 * Return existing instance of PluginManager, or create it. 45 * Constructor.
60 * 46 *
61 * @return PluginManager instance. 47 * @param ConfigManager $conf Configuration Manager instance.
62 */ 48 */
63 public static function getInstance() 49 public function __construct(&$conf)
64 { 50 {
65 if (!(self::$instance instanceof self)) { 51 $this->conf = $conf;
66 self::$instance = new self(); 52 $this->errors = array();
67 }
68
69 return self::$instance;
70 } 53 }
71 54
72 /** 55 /**
@@ -102,9 +85,9 @@ class PluginManager
102 /** 85 /**
103 * Execute all plugins registered hook. 86 * Execute all plugins registered hook.
104 * 87 *
105 * @param string $hook name of the hook to trigger. 88 * @param string $hook name of the hook to trigger.
106 * @param array $data list of data to manipulate passed by reference. 89 * @param array $data list of data to manipulate passed by reference.
107 * @param array $params additional parameters such as page target. 90 * @param array $params additional parameters such as page target.
108 * 91 *
109 * @return void 92 * @return void
110 */ 93 */
@@ -122,13 +105,14 @@ class PluginManager
122 $hookFunction = $this->buildHookName($hook, $plugin); 105 $hookFunction = $this->buildHookName($hook, $plugin);
123 106
124 if (function_exists($hookFunction)) { 107 if (function_exists($hookFunction)) {
125 $data = call_user_func($hookFunction, $data); 108 $data = call_user_func($hookFunction, $data, $this->conf);
126 } 109 }
127 } 110 }
128 } 111 }
129 112
130 /** 113 /**
131 * Load a single plugin from its files. 114 * Load a single plugin from its files.
115 * Call the init function if it exists, and collect errors.
132 * Add them in $loadedPlugins if successful. 116 * Add them in $loadedPlugins if successful.
133 * 117 *
134 * @param string $dir plugin's directory. 118 * @param string $dir plugin's directory.
@@ -148,8 +132,17 @@ class PluginManager
148 throw new PluginFileNotFoundException($pluginName); 132 throw new PluginFileNotFoundException($pluginName);
149 } 133 }
150 134
135 $conf = $this->conf;
151 include_once $pluginFilePath; 136 include_once $pluginFilePath;
152 137
138 $initFunction = $pluginName . '_init';
139 if (function_exists($initFunction)) {
140 $errors = call_user_func($initFunction, $this->conf);
141 if (!empty($errors)) {
142 $this->errors = array_merge($this->errors, $errors);
143 }
144 }
145
153 $this->loadedPlugins[] = $pluginName; 146 $this->loadedPlugins[] = $pluginName;
154 } 147 }
155 148
@@ -207,12 +200,26 @@ class PluginManager
207 continue; 200 continue;
208 } 201 }
209 202
210 $metaData[$plugin]['parameters'][$param] = ''; 203 $metaData[$plugin]['parameters'][$param]['value'] = '';
204 // Optional parameter description in parameter.PARAM_NAME=
205 if (isset($metaData[$plugin]['parameter.'. $param])) {
206 $metaData[$plugin]['parameters'][$param]['desc'] = $metaData[$plugin]['parameter.'. $param];
207 }
211 } 208 }
212 } 209 }
213 210
214 return $metaData; 211 return $metaData;
215 } 212 }
213
214 /**
215 * Return the list of encountered errors.
216 *
217 * @return array List of errors (empty array if none exists).
218 */
219 public function getErrors()
220 {
221 return $this->errors;
222 }
216} 223}
217 224
218/** 225/**
@@ -232,4 +239,4 @@ class PluginFileNotFoundException extends Exception
232 { 239 {
233 $this->message = 'Plugin "'. $pluginName .'" files not found.'; 240 $this->message = 'Plugin "'. $pluginName .'" files not found.';
234 } 241 }
235} \ No newline at end of file 242}
diff --git a/application/Router.php b/application/Router.php
index 2c3934b0..caed4a28 100644
--- a/application/Router.php
+++ b/application/Router.php
@@ -138,4 +138,4 @@ class Router
138 138
139 return self::$PAGE_LINKLIST; 139 return self::$PAGE_LINKLIST;
140 } 140 }
141} \ No newline at end of file 141}
diff --git a/application/TimeZone.php b/application/TimeZone.php
index 26f2232d..36a8fb12 100644
--- a/application/TimeZone.php
+++ b/application/TimeZone.php
@@ -7,9 +7,9 @@
7 * Example: preselect Europe/Paris 7 * Example: preselect Europe/Paris
8 * list($htmlform, $js) = generateTimeZoneForm('Europe/Paris'); 8 * list($htmlform, $js) = generateTimeZoneForm('Europe/Paris');
9 * 9 *
10 * @param string $preselected_timezone preselected timezone (optional) 10 * @param string $preselectedTimezone preselected timezone (optional)
11 * 11 *
12 * @return an array containing the generated HTML form and Javascript code 12 * @return array containing the generated HTML form and Javascript code
13 **/ 13 **/
14function generateTimeZoneForm($preselectedTimezone='') 14function generateTimeZoneForm($preselectedTimezone='')
15{ 15{
@@ -27,10 +27,6 @@ function generateTimeZoneForm($preselectedTimezone='')
27 $pcity = substr($preselectedTimezone, $spos+1); 27 $pcity = substr($preselectedTimezone, $spos+1);
28 } 28 }
29 29
30 // Display config form:
31 $timezoneForm = '';
32 $timezoneJs = '';
33
34 // The list is in the form 'Europe/Paris', 'America/Argentina/Buenos_Aires' 30 // The list is in the form 'Europe/Paris', 'America/Argentina/Buenos_Aires'
35 // We split the list in continents/cities. 31 // We split the list in continents/cities.
36 $continents = array(); 32 $continents = array();
@@ -97,7 +93,7 @@ function generateTimeZoneForm($preselectedTimezone='')
97 * @param string $continent the timezone continent 93 * @param string $continent the timezone continent
98 * @param string $city the timezone city 94 * @param string $city the timezone city
99 * 95 *
100 * @return whether continent/city is a valid timezone 96 * @return bool whether continent/city is a valid timezone
101 */ 97 */
102function isTimeZoneValid($continent, $city) 98function isTimeZoneValid($continent, $city)
103{ 99{
diff --git a/application/Updater.php b/application/Updater.php
index 58c13c07..555d4c25 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -13,14 +13,14 @@ class Updater
13 protected $doneUpdates; 13 protected $doneUpdates;
14 14
15 /** 15 /**
16 * @var array Shaarli's configuration array. 16 * @var LinkDB instance.
17 */ 17 */
18 protected $config; 18 protected $linkDB;
19 19
20 /** 20 /**
21 * @var LinkDB instance. 21 * @var ConfigManager $conf Configuration Manager instance.
22 */ 22 */
23 protected $linkDB; 23 protected $conf;
24 24
25 /** 25 /**
26 * @var bool True if the user is logged in, false otherwise. 26 * @var bool True if the user is logged in, false otherwise.
@@ -35,16 +35,16 @@ class Updater
35 /** 35 /**
36 * Object constructor. 36 * Object constructor.
37 * 37 *
38 * @param array $doneUpdates Updates which are already done. 38 * @param array $doneUpdates Updates which are already done.
39 * @param array $config Shaarli's configuration array. 39 * @param LinkDB $linkDB LinkDB instance.
40 * @param LinkDB $linkDB LinkDB instance. 40 * @param ConfigManager $conf Configuration Manager instance.
41 * @param boolean $isLoggedIn True if the user is logged in. 41 * @param boolean $isLoggedIn True if the user is logged in.
42 */ 42 */
43 public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn) 43 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
44 { 44 {
45 $this->doneUpdates = $doneUpdates; 45 $this->doneUpdates = $doneUpdates;
46 $this->config = $config;
47 $this->linkDB = $linkDB; 46 $this->linkDB = $linkDB;
47 $this->conf = $conf;
48 $this->isLoggedIn = $isLoggedIn; 48 $this->isLoggedIn = $isLoggedIn;
49 49
50 // Retrieve all update methods. 50 // Retrieve all update methods.
@@ -114,19 +114,19 @@ class Updater
114 */ 114 */
115 public function updateMethodMergeDeprecatedConfigFile() 115 public function updateMethodMergeDeprecatedConfigFile()
116 { 116 {
117 $config_file = $this->config['config']['CONFIG_FILE']; 117 if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
118 118 include $this->conf->get('resource.data_dir') . '/options.php';
119 if (is_file($this->config['config']['DATADIR'].'/options.php')) {
120 include $this->config['config']['DATADIR'].'/options.php';
121 119
122 // Load GLOBALS into config 120 // Load GLOBALS into config
121 $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
122 $allowedKeys[] = 'config';
123 foreach ($GLOBALS as $key => $value) { 123 foreach ($GLOBALS as $key => $value) {
124 $this->config[$key] = $value; 124 if (in_array($key, $allowedKeys)) {
125 $this->conf->set($key, $value);
126 }
125 } 127 }
126 $this->config['config']['CONFIG_FILE'] = $config_file; 128 $this->conf->write($this->isLoggedIn);
127 writeConfig($this->config, $this->isLoggedIn); 129 unlink($this->conf->get('resource.data_dir').'/options.php');
128
129 unlink($this->config['config']['DATADIR'].'/options.php');
130 } 130 }
131 131
132 return true; 132 return true;
@@ -138,12 +138,144 @@ class Updater
138 public function updateMethodRenameDashTags() 138 public function updateMethodRenameDashTags()
139 { 139 {
140 $linklist = $this->linkDB->filterSearch(); 140 $linklist = $this->linkDB->filterSearch();
141 foreach ($linklist as $link) { 141 foreach ($linklist as $key => $link) {
142 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); 142 $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); 143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
144 $this->linkDB[$link['linkdate']] = $link; 144 $this->linkDB[$key] = $link;
145 } 145 }
146 $this->linkDB->savedb($this->config['config']['PAGECACHE']); 146 $this->linkDB->save($this->conf->get('resource.page_cache'));
147 return true;
148 }
149
150 /**
151 * Move old configuration in PHP to the new config system in JSON format.
152 *
153 * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
154 * It will also convert legacy setting keys to the new ones.
155 */
156 public function updateMethodConfigToJson()
157 {
158 // JSON config already exists, nothing to do.
159 if ($this->conf->getConfigIO() instanceof ConfigJson) {
160 return true;
161 }
162
163 $configPhp = new ConfigPhp();
164 $configJson = new ConfigJson();
165 $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
166 rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
167 $this->conf->setConfigIO($configJson);
168 $this->conf->reload();
169
170 $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
171 foreach (ConfigPhp::$ROOT_KEYS as $key) {
172 $this->conf->set($legacyMap[$key], $oldConfig[$key]);
173 }
174
175 // Set sub config keys (config and plugins)
176 $subConfig = array('config', 'plugins');
177 foreach ($subConfig as $sub) {
178 foreach ($oldConfig[$sub] as $key => $value) {
179 if (isset($legacyMap[$sub .'.'. $key])) {
180 $configKey = $legacyMap[$sub .'.'. $key];
181 } else {
182 $configKey = $sub .'.'. $key;
183 }
184 $this->conf->set($configKey, $value);
185 }
186 }
187
188 try{
189 $this->conf->write($this->isLoggedIn);
190 return true;
191 } catch (IOException $e) {
192 error_log($e->getMessage());
193 return false;
194 }
195 }
196
197 /**
198 * Escape settings which have been manually escaped in every request in previous versions:
199 * - general.title
200 * - general.header_link
201 * - redirector.url
202 *
203 * @return bool true if the update is successful, false otherwise.
204 */
205 public function updateMethodEscapeUnescapedConfig()
206 {
207 try {
208 $this->conf->set('general.title', escape($this->conf->get('general.title')));
209 $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
210 $this->conf->set('redirector.url', escape($this->conf->get('redirector.url')));
211 $this->conf->write($this->isLoggedIn);
212 } catch (Exception $e) {
213 error_log($e->getMessage());
214 return false;
215 }
216 return true;
217 }
218
219 /**
220 * Update the database to use the new ID system, which replaces linkdate primary keys.
221 * Also, creation and update dates are now DateTime objects (done by LinkDB).
222 *
223 * Since this update is very sensitve (changing the whole database), the datastore will be
224 * automatically backed up into the file datastore.<datetime>.php.
225 *
226 * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
227 * which will be saved by this method.
228 *
229 * @return bool true if the update is successful, false otherwise.
230 */
231 public function updateMethodDatastoreIds()
232 {
233 // up to date database
234 if (isset($this->linkDB[0])) {
235 return true;
236 }
237
238 $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php';
239 copy($this->conf->get('resource.datastore'), $save);
240
241 $links = array();
242 foreach ($this->linkDB as $offset => $value) {
243 $links[] = $value;
244 unset($this->linkDB[$offset]);
245 }
246 $links = array_reverse($links);
247 $cpt = 0;
248 foreach ($links as $l) {
249 unset($l['linkdate']);
250 $l['id'] = $cpt;
251 $this->linkDB[$cpt++] = $l;
252 }
253
254 $this->linkDB->save($this->conf->get('resource.page_cache'));
255 $this->linkDB->reorder();
256
257 return true;
258 }
259
260 /**
261 * * `markdown_escape` is a new setting, set to true as default.
262 *
263 * If the markdown plugin was already enabled, escaping is disabled to avoid
264 * breaking existing entries.
265 */
266 public function updateMethodEscapeMarkdown()
267 {
268 if ($this->conf->exists('security.markdown_escape')) {
269 return true;
270 }
271
272 if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) {
273 $this->conf->set('security.markdown_escape', false);
274 } else {
275 $this->conf->set('security.markdown_escape', true);
276 }
277 $this->conf->write($this->isLoggedIn);
278
147 return true; 279 return true;
148 } 280 }
149} 281}
@@ -203,7 +335,6 @@ class UpdaterException extends Exception
203 } 335 }
204} 336}
205 337
206
207/** 338/**
208 * Read the updates file, and return already done updates. 339 * Read the updates file, and return already done updates.
209 * 340 *
diff --git a/application/Url.php b/application/Url.php
index 77447c8d..c5c7dd18 100644
--- a/application/Url.php
+++ b/application/Url.php
@@ -62,21 +62,7 @@ function add_trailing_slash($url)
62{ 62{
63 return $url . (!endsWith($url, '/') ? '/' : ''); 63 return $url . (!endsWith($url, '/') ? '/' : '');
64} 64}
65/**
66 * Converts an URL with an IDN host to a ASCII one.
67 *
68 * @param string $url Input URL.
69 *
70 * @return string converted URL.
71 */
72function url_with_idn_to_ascii($url)
73{
74 $parts = parse_url($url);
75 $parts['host'] = idn_to_ascii($parts['host']);
76 65
77 $httpUrl = new \http\Url($parts);
78 return $httpUrl->toString();
79}
80/** 66/**
81 * URL representation and cleanup utilities 67 * URL representation and cleanup utilities
82 * 68 *
@@ -99,6 +85,7 @@ class Url
99 'action_type_map=', 85 'action_type_map=',
100 'fb_', 86 'fb_',
101 'fb=', 87 'fb=',
88 'PHPSESSID=',
102 89
103 // Scoop.it 90 // Scoop.it
104 '__scoop', 91 '__scoop',
diff --git a/application/Utils.php b/application/Utils.php
index da521cce..0a5b476e 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -31,7 +31,15 @@ function logm($logFile, $clientIp, $message)
31 * - are NOT cryptographically secure (they CAN be forged) 31 * - are NOT cryptographically secure (they CAN be forged)
32 * 32 *
33 * In Shaarli, they are used as a tinyurl-like link to individual entries, 33 * In Shaarli, they are used as a tinyurl-like link to individual entries,
34 * e.g. smallHash('20111006_131924') --> yZH23w 34 * built once with the combination of the date and item ID.
35 * e.g. smallHash('20111006_131924' . 142) --> eaWxtQ
36 *
37 * @warning before v0.8.1, smallhashes were built only with the date,
38 * and their value has been preserved.
39 *
40 * @param string $text Create a hash from this text.
41 *
42 * @return string generated small hash.
35 */ 43 */
36function smallHash($text) 44function smallHash($text)
37{ 45{
@@ -106,7 +114,9 @@ function unescape($str)
106} 114}
107 115
108/** 116/**
109 * Link sanitization before templating 117 * Sanitize link before rendering.
118 *
119 * @param array $link Link to escape.
110 */ 120 */
111function sanitizeLink(&$link) 121function sanitizeLink(&$link)
112{ 122{
@@ -198,59 +208,6 @@ function is_session_id_valid($sessionId)
198} 208}
199 209
200/** 210/**
201 * In a string, converts URLs to clickable links.
202 *
203 * @param string $text input string.
204 * @param string $redirector if a redirector is set, use it to gerenate links.
205 *
206 * @return string returns $text with all links converted to HTML links.
207 *
208 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
209 */
210function text2clickable($text, $redirector)
211{
212 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
213
214 if (empty($redirector)) {
215 return preg_replace($regex, '<a href="$1">$1</a>', $text);
216 }
217 // Redirector is set, urlencode the final URL.
218 return preg_replace_callback(
219 $regex,
220 function ($matches) use ($redirector) {
221 return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
222 },
223 $text
224 );
225}
226
227/**
228 * This function inserts &nbsp; where relevant so that multiple spaces are properly displayed in HTML
229 * even in the absence of <pre> (This is used in description to keep text formatting).
230 *
231 * @param string $text input text.
232 *
233 * @return string formatted text.
234 */
235function space2nbsp($text)
236{
237 return preg_replace('/(^| ) /m', '$1&nbsp;', $text);
238}
239
240/**
241 * Format Shaarli's description
242 * TODO: Move me to ApplicationUtils when it's ready.
243 *
244 * @param string $description shaare's description.
245 * @param string $redirector if a redirector is set, use it to gerenate links.
246 *
247 * @return string formatted description.
248 */
249function format_description($description, $redirector = false) {
250 return nl2br(space2nbsp(text2clickable($description, $redirector)));
251}
252
253/**
254 * Sniff browser language to set the locale automatically. 211 * Sniff browser language to set the locale automatically.
255 * Note that is may not work on your server if the corresponding locale is not installed. 212 * Note that is may not work on your server if the corresponding locale is not installed.
256 * 213 *
@@ -273,4 +230,4 @@ function autoLocale($headerLocale)
273 } 230 }
274 } 231 }
275 setlocale(LC_ALL, $attempts); 232 setlocale(LC_ALL, $attempts);
276} \ No newline at end of file 233}
diff --git a/application/config/ConfigIO.php b/application/config/ConfigIO.php
new file mode 100644
index 00000000..2b68fe6a
--- /dev/null
+++ b/application/config/ConfigIO.php
@@ -0,0 +1,33 @@
1<?php
2
3/**
4 * Interface ConfigIO
5 *
6 * This describes how Config types should store their configuration.
7 */
8interface ConfigIO
9{
10 /**
11 * Read configuration.
12 *
13 * @param string $filepath Config file absolute path.
14 *
15 * @return array All configuration in an array.
16 */
17 function read($filepath);
18
19 /**
20 * Write configuration.
21 *
22 * @param string $filepath Config file absolute path.
23 * @param array $conf All configuration in an array.
24 */
25 function write($filepath, $conf);
26
27 /**
28 * Get config file extension according to config type.
29 *
30 * @return string Config file extension.
31 */
32 function getExtension();
33}
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php
new file mode 100644
index 00000000..30007eb4
--- /dev/null
+++ b/application/config/ConfigJson.php
@@ -0,0 +1,78 @@
1<?php
2
3/**
4 * Class ConfigJson (ConfigIO implementation)
5 *
6 * Handle Shaarli's JSON configuration file.
7 */
8class ConfigJson implements ConfigIO
9{
10 /**
11 * @inheritdoc
12 */
13 function read($filepath)
14 {
15 if (! is_readable($filepath)) {
16 return array();
17 }
18 $data = file_get_contents($filepath);
19 $data = str_replace(self::getPhpHeaders(), '', $data);
20 $data = str_replace(self::getPhpSuffix(), '', $data);
21 $data = json_decode($data, true);
22 if ($data === null) {
23 $error = json_last_error();
24 throw new Exception('An error occurred while parsing JSON file: error code #'. $error);
25 }
26 return $data;
27 }
28
29 /**
30 * @inheritdoc
31 */
32 function write($filepath, $conf)
33 {
34 // JSON_PRETTY_PRINT is available from PHP 5.4.
35 $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
36 $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix();
37 if (!file_put_contents($filepath, $data)) {
38 throw new IOException(
39 $filepath,
40 'Shaarli could not create the config file.
41 Please make sure Shaarli has the right to write in the folder is it installed in.'
42 );
43 }
44 }
45
46 /**
47 * @inheritdoc
48 */
49 function getExtension()
50 {
51 return '.json.php';
52 }
53
54 /**
55 * The JSON data is wrapped in a PHP file for security purpose.
56 * This way, even if the file is accessible, credentials and configuration won't be exposed.
57 *
58 * Note: this isn't a static field because concatenation isn't supported in field declaration before PHP 5.6.
59 *
60 * @return string PHP start tag and comment tag.
61 */
62 public static function getPhpHeaders()
63 {
64 return '<?php /*'. PHP_EOL;
65 }
66
67 /**
68 * Get PHP comment closing tags.
69 *
70 * Static method for consistency with getPhpHeaders.
71 *
72 * @return string PHP comment closing.
73 */
74 public static function getPhpSuffix()
75 {
76 return PHP_EOL . '*/ ?>';
77 }
78}
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
new file mode 100644
index 00000000..f5f753f8
--- /dev/null
+++ b/application/config/ConfigManager.php
@@ -0,0 +1,394 @@
1<?php
2
3// FIXME! Namespaces...
4require_once 'ConfigIO.php';
5require_once 'ConfigJson.php';
6require_once 'ConfigPhp.php';
7
8/**
9 * Class ConfigManager
10 *
11 * Manages all Shaarli's settings.
12 * See the documentation for more information on settings:
13 * - doc/Shaarli-configuration.html
14 * - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration
15 */
16class ConfigManager
17{
18 /**
19 * @var string Flag telling a setting is not found.
20 */
21 protected static $NOT_FOUND = 'NOT_FOUND';
22
23 /**
24 * @var string Config folder.
25 */
26 protected $configFile;
27
28 /**
29 * @var array Loaded config array.
30 */
31 protected $loadedConfig;
32
33 /**
34 * @var ConfigIO implementation instance.
35 */
36 protected $configIO;
37
38 /**
39 * Constructor.
40 *
41 * @param string $configFile Configuration file path without extension.
42 */
43 public function __construct($configFile = 'data/config')
44 {
45 $this->configFile = $configFile;
46 $this->initialize();
47 }
48
49 /**
50 * Reset the ConfigManager instance.
51 */
52 public function reset()
53 {
54 $this->initialize();
55 }
56
57 /**
58 * Rebuild the loaded config array from config files.
59 */
60 public function reload()
61 {
62 $this->load();
63 }
64
65 /**
66 * Initialize the ConfigIO and loaded the conf.
67 */
68 protected function initialize()
69 {
70 if (file_exists($this->configFile . '.php')) {
71 $this->configIO = new ConfigPhp();
72 } else {
73 $this->configIO = new ConfigJson();
74 }
75 $this->load();
76 }
77
78 /**
79 * Load configuration in the ConfigurationManager.
80 */
81 protected function load()
82 {
83 $this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
84 $this->setDefaultValues();
85 }
86
87 /**
88 * Get a setting.
89 *
90 * Supports nested settings with dot separated keys.
91 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
92 * or in JSON:
93 * { "config": { "stuff": {"option": "mysetting" } } } }
94 *
95 * @param string $setting Asked setting, keys separated with dots.
96 * @param string $default Default value if not found.
97 *
98 * @return mixed Found setting, or the default value.
99 */
100 public function get($setting, $default = '')
101 {
102 // During the ConfigIO transition, map legacy settings to the new ones.
103 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
104 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
105 }
106
107 $settings = explode('.', $setting);
108 $value = self::getConfig($settings, $this->loadedConfig);
109 if ($value === self::$NOT_FOUND) {
110 return $default;
111 }
112 return $value;
113 }
114
115 /**
116 * Set a setting, and eventually write it.
117 *
118 * Supports nested settings with dot separated keys.
119 *
120 * @param string $setting Asked setting, keys separated with dots.
121 * @param string $value Value to set.
122 * @param bool $write Write the new setting in the config file, default false.
123 * @param bool $isLoggedIn User login state, default false.
124 *
125 * @throws Exception Invalid
126 */
127 public function set($setting, $value, $write = false, $isLoggedIn = false)
128 {
129 if (empty($setting) || ! is_string($setting)) {
130 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
131 }
132
133 // During the ConfigIO transition, map legacy settings to the new ones.
134 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
135 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
136 }
137
138 $settings = explode('.', $setting);
139 self::setConfig($settings, $value, $this->loadedConfig);
140 if ($write) {
141 $this->write($isLoggedIn);
142 }
143 }
144
145 /**
146 * Check if a settings exists.
147 *
148 * Supports nested settings with dot separated keys.
149 *
150 * @param string $setting Asked setting, keys separated with dots.
151 *
152 * @return bool true if the setting exists, false otherwise.
153 */
154 public function exists($setting)
155 {
156 // During the ConfigIO transition, map legacy settings to the new ones.
157 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
158 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
159 }
160
161 $settings = explode('.', $setting);
162 $value = self::getConfig($settings, $this->loadedConfig);
163 if ($value === self::$NOT_FOUND) {
164 return false;
165 }
166 return true;
167 }
168
169 /**
170 * Call the config writer.
171 *
172 * @param bool $isLoggedIn User login state.
173 *
174 * @return bool True if the configuration has been successfully written, false otherwise.
175 *
176 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
177 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
178 * @throws IOException: an error occurred while writing the new config file.
179 */
180 public function write($isLoggedIn)
181 {
182 // These fields are required in configuration.
183 $mandatoryFields = array(
184 'credentials.login',
185 'credentials.hash',
186 'credentials.salt',
187 'security.session_protection_disabled',
188 'general.timezone',
189 'general.title',
190 'general.header_link',
191 'privacy.default_private_links',
192 'redirector.url',
193 );
194
195 // Only logged in user can alter config.
196 if (is_file($this->getConfigFileExt()) && !$isLoggedIn) {
197 throw new UnauthorizedConfigException();
198 }
199
200 // Check that all mandatory fields are provided in $conf.
201 foreach ($mandatoryFields as $field) {
202 if (! $this->exists($field)) {
203 throw new MissingFieldConfigException($field);
204 }
205 }
206
207 return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig);
208 }
209
210 /**
211 * Set the config file path (without extension).
212 *
213 * @param string $configFile File path.
214 */
215 public function setConfigFile($configFile)
216 {
217 $this->configFile = $configFile;
218 }
219
220 /**
221 * Return the configuration file path (without extension).
222 *
223 * @return string Config path.
224 */
225 public function getConfigFile()
226 {
227 return $this->configFile;
228 }
229
230 /**
231 * Get the configuration file path with its extension.
232 *
233 * @return string Config file path.
234 */
235 public function getConfigFileExt()
236 {
237 return $this->configFile . $this->configIO->getExtension();
238 }
239
240 /**
241 * Recursive function which find asked setting in the loaded config.
242 *
243 * @param array $settings Ordered array which contains keys to find.
244 * @param array $conf Loaded settings, then sub-array.
245 *
246 * @return mixed Found setting or NOT_FOUND flag.
247 */
248 protected static function getConfig($settings, $conf)
249 {
250 if (!is_array($settings) || count($settings) == 0) {
251 return self::$NOT_FOUND;
252 }
253
254 $setting = array_shift($settings);
255 if (!isset($conf[$setting])) {
256 return self::$NOT_FOUND;
257 }
258
259 if (count($settings) > 0) {
260 return self::getConfig($settings, $conf[$setting]);
261 }
262 return $conf[$setting];
263 }
264
265 /**
266 * Recursive function which find asked setting in the loaded config.
267 *
268 * @param array $settings Ordered array which contains keys to find.
269 * @param mixed $value
270 * @param array $conf Loaded settings, then sub-array.
271 *
272 * @return mixed Found setting or NOT_FOUND flag.
273 */
274 protected static function setConfig($settings, $value, &$conf)
275 {
276 if (!is_array($settings) || count($settings) == 0) {
277 return self::$NOT_FOUND;
278 }
279
280 $setting = array_shift($settings);
281 if (count($settings) > 0) {
282 return self::setConfig($settings, $value, $conf[$setting]);
283 }
284 $conf[$setting] = $value;
285 }
286
287 /**
288 * Set a bunch of default values allowing Shaarli to start without a config file.
289 */
290 protected function setDefaultValues()
291 {
292 $this->setEmpty('resource.data_dir', 'data');
293 $this->setEmpty('resource.config', 'data/config.php');
294 $this->setEmpty('resource.datastore', 'data/datastore.php');
295 $this->setEmpty('resource.ban_file', 'data/ipbans.php');
296 $this->setEmpty('resource.updates', 'data/updates.txt');
297 $this->setEmpty('resource.log', 'data/log.txt');
298 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
299 $this->setEmpty('resource.raintpl_tpl', 'tpl/');
300 $this->setEmpty('resource.raintpl_tmp', 'tmp/');
301 $this->setEmpty('resource.thumbnails_cache', 'cache');
302 $this->setEmpty('resource.page_cache', 'pagecache');
303
304 $this->setEmpty('security.ban_after', 4);
305 $this->setEmpty('security.ban_duration', 1800);
306 $this->setEmpty('security.session_protection_disabled', false);
307 $this->setEmpty('security.open_shaarli', false);
308
309 $this->setEmpty('general.header_link', '?');
310 $this->setEmpty('general.links_per_page', 20);
311 $this->setEmpty('general.enabled_plugins', array('qrcode'));
312
313 $this->setEmpty('updates.check_updates', false);
314 $this->setEmpty('updates.check_updates_branch', 'stable');
315 $this->setEmpty('updates.check_updates_interval', 86400);
316
317 $this->setEmpty('feed.rss_permalinks', true);
318 $this->setEmpty('feed.show_atom', false);
319
320 $this->setEmpty('privacy.default_private_links', false);
321 $this->setEmpty('privacy.hide_public_links', false);
322 $this->setEmpty('privacy.hide_timestamps', false);
323
324 $this->setEmpty('thumbnail.enable_thumbnails', true);
325 $this->setEmpty('thumbnail.enable_localcache', true);
326
327 $this->setEmpty('redirector.url', '');
328 $this->setEmpty('redirector.encode_url', true);
329
330 $this->setEmpty('plugins', array());
331 }
332
333 /**
334 * Set only if the setting does not exists.
335 *
336 * @param string $key Setting key.
337 * @param mixed $value Setting value.
338 */
339 public function setEmpty($key, $value)
340 {
341 if (! $this->exists($key)) {
342 $this->set($key, $value);
343 }
344 }
345
346 /**
347 * @return ConfigIO
348 */
349 public function getConfigIO()
350 {
351 return $this->configIO;
352 }
353
354 /**
355 * @param ConfigIO $configIO
356 */
357 public function setConfigIO($configIO)
358 {
359 $this->configIO = $configIO;
360 }
361}
362
363/**
364 * Exception used if a mandatory field is missing in given configuration.
365 */
366class MissingFieldConfigException extends Exception
367{
368 public $field;
369
370 /**
371 * Construct exception.
372 *
373 * @param string $field field name missing.
374 */
375 public function __construct($field)
376 {
377 $this->field = $field;
378 $this->message = 'Configuration value is required for '. $this->field;
379 }
380}
381
382/**
383 * Exception used if an unauthorized attempt to edit configuration has been made.
384 */
385class UnauthorizedConfigException extends Exception
386{
387 /**
388 * Construct exception.
389 */
390 public function __construct()
391 {
392 $this->message = 'You are not authorized to alter config.';
393 }
394}
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
new file mode 100644
index 00000000..27187b66
--- /dev/null
+++ b/application/config/ConfigPhp.php
@@ -0,0 +1,132 @@
1<?php
2
3/**
4 * Class ConfigPhp (ConfigIO implementation)
5 *
6 * Handle Shaarli's legacy PHP configuration file.
7 * Note: this is only designed to support the transition to JSON configuration.
8 */
9class ConfigPhp implements ConfigIO
10{
11 /**
12 * @var array List of config key without group.
13 */
14 public static $ROOT_KEYS = array(
15 'login',
16 'hash',
17 'salt',
18 'timezone',
19 'title',
20 'titleLink',
21 'redirector',
22 'disablesessionprotection',
23 'privateLinkByDefault',
24 );
25
26 /**
27 * Map legacy config keys with the new ones.
28 * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>.
29 * The Updater will use this array to transform keys when switching to JSON.
30 *
31 * @var array current key => legacy key.
32 */
33 public static $LEGACY_KEYS_MAPPING = array(
34 'credentials.login' => 'login',
35 'credentials.hash' => 'hash',
36 'credentials.salt' => 'salt',
37 'resource.data_dir' => 'config.DATADIR',
38 'resource.config' => 'config.CONFIG_FILE',
39 'resource.datastore' => 'config.DATASTORE',
40 'resource.updates' => 'config.UPDATES_FILE',
41 'resource.log' => 'config.LOG_FILE',
42 'resource.update_check' => 'config.UPDATECHECK_FILENAME',
43 'resource.raintpl_tpl' => 'config.RAINTPL_TPL',
44 'resource.raintpl_tmp' => 'config.RAINTPL_TMP',
45 'resource.thumbnails_cache' => 'config.CACHEDIR',
46 'resource.page_cache' => 'config.PAGECACHE',
47 'resource.ban_file' => 'config.IPBANS_FILENAME',
48 'security.session_protection_disabled' => 'disablesessionprotection',
49 'security.ban_after' => 'config.BAN_AFTER',
50 'security.ban_duration' => 'config.BAN_DURATION',
51 'general.title' => 'title',
52 'general.timezone' => 'timezone',
53 'general.header_link' => 'titleLink',
54 'updates.check_updates' => 'config.ENABLE_UPDATECHECK',
55 'updates.check_updates_branch' => 'config.UPDATECHECK_BRANCH',
56 'updates.check_updates_interval' => 'config.UPDATECHECK_INTERVAL',
57 'privacy.default_private_links' => 'privateLinkByDefault',
58 'feed.rss_permalinks' => 'config.ENABLE_RSS_PERMALINKS',
59 'general.links_per_page' => 'config.LINKS_PER_PAGE',
60 'thumbnail.enable_thumbnails' => 'config.ENABLE_THUMBNAILS',
61 'thumbnail.enable_localcache' => 'config.ENABLE_LOCALCACHE',
62 'general.enabled_plugins' => 'config.ENABLED_PLUGINS',
63 'redirector.url' => 'redirector',
64 'redirector.encode_url' => 'config.REDIRECTOR_URLENCODE',
65 'feed.show_atom' => 'config.SHOW_ATOM',
66 'privacy.hide_public_links' => 'config.HIDE_PUBLIC_LINKS',
67 'privacy.hide_timestamps' => 'config.HIDE_TIMESTAMPS',
68 'security.open_shaarli' => 'config.OPEN_SHAARLI',
69 );
70
71 /**
72 * @inheritdoc
73 */
74 function read($filepath)
75 {
76 if (! file_exists($filepath) || ! is_readable($filepath)) {
77 return array();
78 }
79
80 include $filepath;
81
82 $out = array();
83 foreach (self::$ROOT_KEYS as $key) {
84 $out[$key] = $GLOBALS[$key];
85 }
86 $out['config'] = $GLOBALS['config'];
87 $out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array();
88 return $out;
89 }
90
91 /**
92 * @inheritdoc
93 */
94 function write($filepath, $conf)
95 {
96 $configStr = '<?php '. PHP_EOL;
97 foreach (self::$ROOT_KEYS as $key) {
98 if (isset($conf[$key])) {
99 $configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL;
100 }
101 }
102
103 // Store all $conf['config']
104 foreach ($conf['config'] as $key => $value) {
105 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL;
106 }
107
108 if (isset($conf['plugins'])) {
109 foreach ($conf['plugins'] as $key => $value) {
110 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL;
111 }
112 }
113
114 if (!file_put_contents($filepath, $configStr)
115 || strcmp(file_get_contents($filepath), $configStr) != 0
116 ) {
117 throw new IOException(
118 $filepath,
119 'Shaarli could not create the config file.
120 Please make sure Shaarli has the right to write in the folder is it installed in.'
121 );
122 }
123 }
124
125 /**
126 * @inheritdoc
127 */
128 function getExtension()
129 {
130 return '.php';
131 }
132}
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php
new file mode 100644
index 00000000..cb0b6fce
--- /dev/null
+++ b/application/config/ConfigPlugin.php
@@ -0,0 +1,124 @@
1<?php
2/**
3 * Plugin configuration helper functions.
4 *
5 * Note: no access to configuration files here.
6 */
7
8/**
9 * Process plugin administration form data and save it in an array.
10 *
11 * @param array $formData Data sent by the plugin admin form.
12 *
13 * @return array New list of enabled plugin, ordered.
14 *
15 * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
16 */
17function save_plugin_config($formData)
18{
19 // Make sure there are no duplicates in orders.
20 if (!validate_plugin_order($formData)) {
21 throw new PluginConfigOrderException();
22 }
23
24 $plugins = array();
25 $newEnabledPlugins = array();
26 foreach ($formData as $key => $data) {
27 if (startsWith($key, 'order')) {
28 continue;
29 }
30
31 // If there is no order, it means a disabled plugin has been enabled.
32 if (isset($formData['order_' . $key])) {
33 $plugins[(int) $formData['order_' . $key]] = $key;
34 }
35 else {
36 $newEnabledPlugins[] = $key;
37 }
38 }
39
40 // New enabled plugins will be added at the end of order.
41 $plugins = array_merge($plugins, $newEnabledPlugins);
42
43 // Sort plugins by order.
44 if (!ksort($plugins)) {
45 throw new PluginConfigOrderException();
46 }
47
48 $finalPlugins = array();
49 // Make plugins order continuous.
50 foreach ($plugins as $plugin) {
51 $finalPlugins[] = $plugin;
52 }
53
54 return $finalPlugins;
55}
56
57/**
58 * Validate plugin array submitted.
59 * Will fail if there is duplicate orders value.
60 *
61 * @param array $formData Data from submitted form.
62 *
63 * @return bool true if ok, false otherwise.
64 */
65function validate_plugin_order($formData)
66{
67 $orders = array();
68 foreach ($formData as $key => $value) {
69 // No duplicate order allowed.
70 if (in_array($value, $orders)) {
71 return false;
72 }
73
74 if (startsWith($key, 'order')) {
75 $orders[] = $value;
76 }
77 }
78
79 return true;
80}
81
82/**
83 * Affect plugin parameters values from the ConfigManager into plugins array.
84 *
85 * @param mixed $plugins Plugins array:
86 * $plugins[<plugin_name>]['parameters'][<param_name>] = [
87 * 'value' => <value>,
88 * 'desc' => <description>
89 * ]
90 * @param mixed $conf Plugins configuration.
91 *
92 * @return mixed Updated $plugins array.
93 */
94function load_plugin_parameter_values($plugins, $conf)
95{
96 $out = $plugins;
97 foreach ($plugins as $name => $plugin) {
98 if (empty($plugin['parameters'])) {
99 continue;
100 }
101
102 foreach ($plugin['parameters'] as $key => $param) {
103 if (!empty($conf[$key])) {
104 $out[$name]['parameters'][$key]['value'] = $conf[$key];
105 }
106 }
107 }
108
109 return $out;
110}
111
112/**
113 * Exception used if an error occur while saving plugin configuration.
114 */
115class PluginConfigOrderException extends Exception
116{
117 /**
118 * Construct exception.
119 */
120 public function __construct()
121 {
122 $this->message = 'An error occurred while trying to save plugins loading order.';
123 }
124}
diff --git a/cache/.htaccess b/cache/.htaccess
index b584d98c..f601c1ee 100644
--- a/cache/.htaccess
+++ b/cache/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/composer.json b/composer.json
index 2ded0920..f7d26a31 100644
--- a/composer.json
+++ b/composer.json
@@ -1,11 +1,19 @@
1{ 1{
2 "name": "shaarli/shaarli", 2 "name": "shaarli/shaarli",
3 "description": "The personal, minimalist, super-fast, no-database delicious clone", 3 "description": "The personal, minimalist, super-fast, database-free bookmarking service",
4 "type": "project",
4 "license": "MIT", 5 "license": "MIT",
6 "homepage": "https://github.com/shaarli/Shaarli",
5 "support": { 7 "support": {
6 "issues": "https://github.com/shaarli/Shaarli/issues" 8 "issues": "https://github.com/shaarli/Shaarli/issues",
9 "wiki": "https://github.com/shaarli/Shaarli/wiki"
10 },
11 "keywords": ["bookmark", "link", "share", "web"],
12 "require": {
13 "php": ">=5.3.4",
14 "shaarli/netscape-bookmark-parser": "1.*",
15 "erusev/parsedown": "1.6"
7 }, 16 },
8 "require": {},
9 "require-dev": { 17 "require-dev": {
10 "phpmd/phpmd" : "@stable", 18 "phpmd/phpmd" : "@stable",
11 "phpunit/phpunit": "4.8.*", 19 "phpunit/phpunit": "4.8.*",
diff --git a/data/.htaccess b/data/.htaccess
index b584d98c..f601c1ee 100644
--- a/data/.htaccess
+++ b/data/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/doc/3rd-party-libraries.html b/doc/3rd-party-libraries.html
index f6ff4763..946ca037 100644
--- a/doc/3rd-party-libraries.html
+++ b/doc/3rd-party-libraries.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
@@ -81,6 +79,7 @@
81</ul> 79</ul>
82<h2 id="php">PHP</h2> 80<h2 id="php">PHP</h2>
83<ul> 81<ul>
82<li><a href="https://github.com/shaarli/netscape-bookmark-parser">shaarli/netscape-bookmark-parser</a> - Netscape bookmark parser<a href=".html"></a></li>
84<li><a href="https://github.com/rainphp/raintpl">RainTPL</a> - HTML templating for PHP<a href=".html"></a></li> 83<li><a href="https://github.com/rainphp/raintpl">RainTPL</a> - HTML templating for PHP<a href=".html"></a></li>
85</ul> 84</ul>
86</body> 85</body>
diff --git a/doc/3rd-party-libraries.md b/doc/3rd-party-libraries.md
index 3101c90a..e6370549 100644
--- a/doc/3rd-party-libraries.md
+++ b/doc/3rd-party-libraries.md
@@ -10,4 +10,5 @@
10- [qr.js](http://neocotic.com/qr.js/) ([GitHub](https://github.com/neocotic/qr.js)) - QR code generation[](.html) 10- [qr.js](http://neocotic.com/qr.js/) ([GitHub](https://github.com/neocotic/qr.js)) - QR code generation[](.html)
11 11
12## PHP 12## PHP
13- [shaarli/netscape-bookmark-parser](https://github.com/shaarli/netscape-bookmark-parser) - Netscape bookmark parser[](.html)
13- [RainTPL](https://github.com/rainphp/raintpl) - HTML templating for PHP[](.html) 14- [RainTPL](https://github.com/rainphp/raintpl) - HTML templating for PHP[](.html)
diff --git a/doc/Backup,-restore,-import-and-export.html b/doc/Backup,-restore,-import-and-export.html
index 4d72728e..a4a48ad7 100644
--- a/doc/Backup,-restore,-import-and-export.html
+++ b/doc/Backup,-restore,-import-and-export.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,15 +96,21 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="backup-restore-import-and-export">Backup, restore, import and export</h1> 102<h1 id="backup-restore-import-and-export">Backup, restore, import and export</h1>
103<ul>
104<li><a href="#backup-and-restore-the-datastore-file">Backup and restore the datastore file</a><a href=".html"></a></li>
105<li><a href="#export-links-as">Export links as...</a><a href=".html"></a></li>
106<li><a href="#import-links-from">Import links from...</a><a href=".html"></a></li>
107<li><a href="#import-shaarli-links-to-firefox">Import Shaarli links to Firefox</a><a href=".html"></a></li>
108</ul>
109<hr />
105<h2 id="backup-and-restore-the-datastore-file">Backup and restore the datastore file</h2> 110<h2 id="backup-and-restore-the-datastore-file">Backup and restore the datastore file</h2>
106<p>Backup the file <code>data/datastore.php</code> (by FTP or SSH). Restore by putting the file back in place.</p> 111<p>Backup the file <code>data/datastore.php</code> (by FTP or SSH). Restore by putting the file back in place.</p>
107<p>Example command:</p> 112<p>Example command:</p>
108<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="kw">rsync</span> -avzP my.server.com:/var/www/shaarli/data/datastore.php datastore-<span class="ot">$(</span><span class="kw">date</span> +%Y-%m-%d_%H%M<span class="ot">)</span>.php</code></pre></div> 113<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">rsync</span> -avzP my.server.com:/var/www/shaarli/data/datastore.php datastore-<span class="va">$(</span><span class="fu">date</span> +%Y-%m-%d_%H%M<span class="va">)</span>.php</code></pre></div>
109<h2 id="export-links-as...">Export links as...</h2> 114<h2 id="export-links-as...">Export links as...</h2>
110<p>To export links as an HTML file, under <em>Tools &gt; Export</em>, choose:</p> 115<p>To export links as an HTML file, under <em>Tools &gt; Export</em>, choose:</p>
111<ul> 116<ul>
@@ -118,7 +123,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
118<li>This can be done using the <a href="https://github.com/nodiscc/shaarchiver">shaarchiver</a> tool.<a href=".html"></a></li> 123<li>This can be done using the <a href="https://github.com/nodiscc/shaarchiver">shaarchiver</a> tool.<a href=".html"></a></li>
119</ul> 124</ul>
120<p>Example command:</p> 125<p>Example command:</p>
121<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="kw">./export-bookmarks.py</span> --url=https://my.server.com/shaarli --username=myusername --password=mysupersecretpassword --download-dir=./ --type=all</code></pre></div> 126<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">./export-bookmarks.py</span> --url=https://my.server.com/shaarli --username=myusername --password=mysupersecretpassword --download-dir=./ --type=all</code></pre></div>
122<h2 id="import-links-from...">Import links from...</h2> 127<h2 id="import-links-from...">Import links from...</h2>
123<h3 id="diigo">Diigo</h3> 128<h3 id="diigo">Diigo</h3>
124<p>If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.)</p> 129<p>If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.)</p>
@@ -126,5 +131,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
126<p>See <a href="https://github.com/sebsauvage/Shaarli/issues/146">this issue</a> for import tweaks.<a href=".html"></a></p> 131<p>See <a href="https://github.com/sebsauvage/Shaarli/issues/146">this issue</a> for import tweaks.<a href=".html"></a></p>
127<h3 id="semanticscuttle">SemanticScuttle</h3> 132<h3 id="semanticscuttle">SemanticScuttle</h3>
128<p>To correctly import the tags from a <a href="http://semanticscuttle.sourceforge.net/">SemanticScuttle</a> HTML export, edit the HTML file before importing and replace all occurences of <code>tags=</code> (lowercase) to <code>TAGS=</code> (uppercase).<a href=".html"></a></p> 133<p>To correctly import the tags from a <a href="http://semanticscuttle.sourceforge.net/">SemanticScuttle</a> HTML export, edit the HTML file before importing and replace all occurences of <code>tags=</code> (lowercase) to <code>TAGS=</code> (uppercase).<a href=".html"></a></p>
134<h3 id="scuttle">Scuttle</h3>
135<p>Shaarli cannot import data directly from <a href="https://github.com/scronide/scuttle">Scuttle</a>. However, you can use this third party tool: <a href="https://github.com/q2apro/scuttle-to-shaarli" class="uri">https://github.com/q2apro/scuttle-to-shaarli</a> to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.<a href=".html"></a></p>
136<h2 id="import-shaarli-links-to-firefox">Import Shaarli links to Firefox</h2>
137<ul>
138<li>Export your Shaarli links as described above.</li>
139<li>For compatibility reasons, check <code>Prepend note permalinks with this Shaarli instance's URL (useful to import bookmarks in a web browser)</code></li>
140<li>In Firefox, open the bookmark manager (not the sidebar! <code>Bookmarks menu &gt; Show all bookmarks</code> or <code>Ctrl+Shift+B</code>)</li>
141<li>Select <code>Import and Backup &gt; Import bookmarks in HTML format</code></li>
142</ul>
143<p>Your bookmarks will be imported in Firefox, ready to use, with tags and descriptions retained. &quot;Self&quot; (notes) shaares will still point to the Shaarli instance you exported them from, but the note text can be viewed directly in the bookmark properties inside your browser. Depending on the number of bookmarks, the import can take some time.</p>
144<p>You may be interested in these Firefox addons to manage links imported from Shaarli</p>
145<ul>
146<li><a href="https://addons.mozilla.org/en-US/firefox/addon/bookmark-deduplicator/">Bookmark Deduplicator</a> - provides an easy way to deduplicate your bookmarks<a href=".html"></a></li>
147<li><a href="https://addons.mozilla.org/en-US/firefox/addon/tagsieve/">TagSieve</a> - browse your bookmarks by their tags<a href=".html"></a></li>
148</ul>
129</body> 149</body>
130</html> 150</html>
diff --git a/doc/Backup,-restore,-import-and-export.md b/doc/Backup,-restore,-import-and-export.md
index cf6b9f48..9f5598ef 100644
--- a/doc/Backup,-restore,-import-and-export.md
+++ b/doc/Backup,-restore,-import-and-export.md
@@ -1,4 +1,12 @@
1#Backup, restore, import and export 1#Backup, restore, import and export
2 * [Backup and restore the datastore file](#backup-and-restore-the-datastore-file)[](.html)
3 * [Export links as...](#export-links-as)[](.html)
4 * [Import links from...](#import-links-from)[](.html)
5 * [Import Shaarli links to Firefox](#import-shaarli-links-to-firefox)[](.html)
6
7
8----------------------
9
2## Backup and restore the datastore file 10## Backup and restore the datastore file
3 11
4Backup the file `data/datastore.php` (by FTP or SSH). Restore by putting the file back in place. 12Backup the file `data/datastore.php` (by FTP or SSH). Restore by putting the file back in place.
@@ -9,6 +17,7 @@ rsync -avzP my.server.com:/var/www/shaarli/data/datastore.php datastore-$(date +
9``` 17```
10 18
11## Export links as... 19## Export links as...
20
12To export links as an HTML file, under _Tools > Export_, choose: 21To export links as an HTML file, under _Tools > Export_, choose:
13- _Export all_ to export both public and private links 22- _Export all_ to export both public and private links
14- _Export public_ to export public links only 23- _Export public_ to export public links only
@@ -23,13 +32,35 @@ Example command:
23``` 32```
24 33
25## Import links from... 34## Import links from...
35
36
26### Diigo 37### Diigo
27 38
28If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.) 39If you export your bookmark from Diigo, make sure you use the Delicious export, not the Netscape export. (Their Netscape export is broken, and they don't seem to be interested in fixing it.)
29 40
41
30### Mister Wong 42### Mister Wong
43
31See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks.[](.html) 44See [this issue](https://github.com/sebsauvage/Shaarli/issues/146) for import tweaks.[](.html)
32 45
33### SemanticScuttle 46### SemanticScuttle
34 47
35To correctly import the tags from a [SemanticScuttle](http://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase).[](.html) 48To correctly import the tags from a [SemanticScuttle](http://semanticscuttle.sourceforge.net/) HTML export, edit the HTML file before importing and replace all occurences of `tags=` (lowercase) to `TAGS=` (uppercase).[](.html)
49
50### Scuttle
51
52Shaarli cannot import data directly from [Scuttle](https://github.com/scronide/scuttle). However, you can use this third party tool: https://github.com/q2apro/scuttle-to-shaarli to export the Scuttle database to the Netscape HTML format compatible with the Shaarli importer.[](.html)
53
54## Import Shaarli links to Firefox
55
56 * Export your Shaarli links as described above.
57 * For compatibility reasons, check `Prepend note permalinks with this Shaarli instance's URL (useful to import bookmarks in a web browser)`
58 * In Firefox, open the bookmark manager (not the sidebar! `Bookmarks menu > Show all bookmarks` or `Ctrl+Shift+B`)
59 * Select `Import and Backup > Import bookmarks in HTML format`
60
61Your bookmarks will be imported in Firefox, ready to use, with tags and descriptions retained. "Self" (notes) shaares will still point to the Shaarli instance you exported them from, but the note text can be viewed directly in the bookmark properties inside your browser. Depending on the number of bookmarks, the import can take some time.
62
63You may be interested in these Firefox addons to manage links imported from Shaarli
64
65 * [Bookmark Deduplicator](https://addons.mozilla.org/en-US/firefox/addon/bookmark-deduplicator/) - provides an easy way to deduplicate your bookmarks[](.html)
66 * [TagSieve](https://addons.mozilla.org/en-US/firefox/addon/tagsieve/) - browse your bookmarks by their tags[](.html)
diff --git a/doc/Browsing-and-searching.html b/doc/Browsing-and-searching.html
index 39806128..23001bcb 100644
--- a/doc/Browsing-and-searching.html
+++ b/doc/Browsing-and-searching.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Coding-guidelines.html b/doc/Coding-guidelines.html
index add69631..1a2a9351 100644
--- a/doc/Coding-guidelines.html
+++ b/doc/Coding-guidelines.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Community-&-Related-software.html b/doc/Community-&-Related-software.html
index 77b9793f..cbc73d54 100644
--- a/doc/Community-&-Related-software.html
+++ b/doc/Community-&-Related-software.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
@@ -83,9 +81,11 @@
83<ul> 81<ul>
84<li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li> 82<li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li>
85<li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li> 83<li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li>
86<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li> 84<li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li>
87<li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie/emojione">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li> 85<li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li>
88<li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li> 86<li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li>
87<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li>
88<li><a href="https://github.com/ArthurHoaro/shaarli2twitter">shaarli2twitter</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a> - Automatically tweet your shared links from Shaarli<a href=".html"></a></li>
89</ul> 89</ul>
90<h3 id="themes">Themes</h3> 90<h3 id="themes">Themes</h3>
91<p>See <a href="Theming.html">Theming</a> for the list of community-contributed themes, and an installation guide.</p> 91<p>See <a href="Theming.html">Theming</a> for the list of community-contributed themes, and an installation guide.</p>
@@ -96,7 +96,7 @@
96<li><a href="https://github.com/DMeloni/shaarlo">Shaarlo</a> - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: <a href="http://shaarli.fr/">shaarli.fr</a>)<a href=".html"></a></li> 96<li><a href="https://github.com/DMeloni/shaarlo">Shaarlo</a> - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: <a href="http://shaarli.fr/">shaarli.fr</a>)<a href=".html"></a></li>
97<li><a href="https://github.com/BoboTiG/shaarlimages">Shaarlimages</a> - An image-oriented aggregator for Shaarlis<a href=".html"></a></li> 97<li><a href="https://github.com/BoboTiG/shaarlimages">Shaarlimages</a> - An image-oriented aggregator for Shaarlis<a href=".html"></a></li>
98<li><a href="https://github.com/mknexen/shaarli-api">mknexen/shaarli-api</a> - A REST API for Shaarli<a href=".html"></a></li> 98<li><a href="https://github.com/mknexen/shaarli-api">mknexen/shaarli-api</a> - A REST API for Shaarli<a href=".html"></a></li>
99<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. An <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">another version</a>, can be used for others shaarli (but use most ressources).<a href=".html"></a></li> 99<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">Another version</a>, can be used for other shaarli instances (but is more resource consuming).<a href=".html"></a></li>
100</ul> 100</ul>
101<h3 id="mobile-apps">Mobile Apps</h3> 101<h3 id="mobile-apps">Mobile Apps</h3>
102<ul> 102<ul>
@@ -108,6 +108,7 @@
108<ul> 108<ul>
109<li><a href="https://github.com/jcsaaddupuy/tt-rss-shaarli">tt-rss-shaarli</a> - <a href="http://tt-rss.org/">TinyTiny RSS</a> plugin that adds support for sharing articles with Shaarli<a href=".html"></a></li> 109<li><a href="https://github.com/jcsaaddupuy/tt-rss-shaarli">tt-rss-shaarli</a> - <a href="http://tt-rss.org/">TinyTiny RSS</a> plugin that adds support for sharing articles with Shaarli<a href=".html"></a></li>
110<li><a href="https://github.com/ahmet2mir/octopress-shaarli">octopress-shaarli</a> - Octopress plugin to retrieve Shaarli links on the sidebar<a href=".html"></a></li> 110<li><a href="https://github.com/ahmet2mir/octopress-shaarli">octopress-shaarli</a> - Octopress plugin to retrieve Shaarli links on the sidebar<a href=".html"></a></li>
111<li><a href="https://github.com/q2apro/scuttle-to-shaarli">Scuttle to Shaarli</a> - Import bookmarks from Scuttle<a href=".html"></a></li>
111</ul> 112</ul>
112<h2 id="alternatives-to-shaarli">Alternatives to Shaarli</h2> 113<h2 id="alternatives-to-shaarli">Alternatives to Shaarli</h2>
113<ul> 114<ul>
diff --git a/doc/Community-&-Related-software.md b/doc/Community-&-Related-software.md
index 03a3dea9..291bf643 100644
--- a/doc/Community-&-Related-software.md
+++ b/doc/Community-&-Related-software.md
@@ -20,9 +20,11 @@ _TODO: contact repos owners to see if they'd like to standardize their work with
20 20
21 * [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html) 21 * [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html)
22 * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) 22 * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html)
23 * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) 23 * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html)
24 * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie/emojione): Add colorful emojis to your Shaarli.[](.html) 24 * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html)
25 * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) 25 * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html)
26 * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
27 * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html)
26 28
27 29
28### Themes 30### Themes
@@ -34,7 +36,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
34- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html) 36- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html)
35- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html) 37- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html)
36- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html) 38- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html)
37- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. An [another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for others shaarli (but use most ressources).[](.html) 39- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).[](.html)
38 40
39### Mobile Apps 41### Mobile Apps
40- [Shaarli💫](http://app.mro.name/Shaarli💫) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html) 42- [Shaarli💫](http://app.mro.name/Shaarli💫) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html)
@@ -44,6 +46,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
44## Integration with other platforms 46## Integration with other platforms
45- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html) 47- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html)
46- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html) 48- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html)
49- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle[](.html)
47 50
48## Alternatives to Shaarli 51## Alternatives to Shaarli
49- [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html) 52- [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html)
diff --git a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html
index edb1555f..9efb1ad6 100644
--- a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html
+++ b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -111,55 +109,55 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
111<span class="co">#Usage: ./local-shaarli.sh</span> 109<span class="co">#Usage: ./local-shaarli.sh</span>
112<span class="co">#Author: nodiscc (nodiscc@gmail.com)</span> 110<span class="co">#Author: nodiscc (nodiscc@gmail.com)</span>
113<span class="co">#License: MIT (http://opensource.org/licenses/MIT)</span> 111<span class="co">#License: MIT (http://opensource.org/licenses/MIT)</span>
114<span class="kw">set</span> <span class="kw">-o</span> errexit 112<span class="kw">set</span> <span class="ex">-o</span> errexit
115<span class="kw">set</span> <span class="kw">-o</span> nounset 113<span class="kw">set</span> <span class="ex">-o</span> nounset
116 114
117<span class="co">##### CONFIG #################</span> 115<span class="co">##### CONFIG #################</span>
118<span class="co">#The port used by php&#39;s local server</span> 116<span class="co">#The port used by php&#39;s local server</span>
119<span class="ot">php_local_port=</span>7431 117<span class="va">php_local_port=</span>7431
120 118
121<span class="co">#Name of the SSH server and path where Shaarli is installed</span> 119<span class="co">#Name of the SSH server and path where Shaarli is installed</span>
122<span class="co">#TODO: pass these as command-line arguments</span> 120<span class="co">#TODO: pass these as command-line arguments</span>
123<span class="ot">remotehost=</span><span class="st">&quot;my.ssh.server&quot;</span> 121<span class="va">remotehost=</span><span class="st">&quot;my.ssh.server&quot;</span>
124<span class="ot">remote_shaarli_dir=</span><span class="st">&quot;/var/www/shaarli&quot;</span> 122<span class="va">remote_shaarli_dir=</span><span class="st">&quot;/var/www/shaarli&quot;</span>
125 123
126 124
127<span class="co">###### FUNCTIONS #############</span> 125<span class="co">###### FUNCTIONS #############</span>
128<span class="fu">_main()</span> <span class="kw">{</span> 126<span class="fu">_main()</span> <span class="kw">{</span>
129 <span class="kw">_CBSyncShaarli</span> 127 <span class="ex">_CBSyncShaarli</span>
130 <span class="kw">_CBServeShaarli</span> 128 <span class="ex">_CBServeShaarli</span>
131<span class="kw">}</span> 129<span class="kw">}</span>
132 130
133<span class="fu">_CBSyncShaarli()</span> <span class="kw">{</span> 131<span class="fu">_CBSyncShaarli()</span> <span class="kw">{</span>
134 <span class="ot">remote_temp_dir=$(</span><span class="kw">ssh</span> <span class="ot">$remotehost</span> mktemp -d<span class="ot">)</span> 132 <span class="va">remote_temp_dir=$(</span><span class="fu">ssh</span> <span class="va">$remotehost</span> mktemp -d<span class="va">)</span>
135 <span class="ot">remote_ssh_user=$(</span><span class="kw">ssh</span> <span class="ot">$remotehost</span> whoami<span class="ot">)</span> 133 <span class="va">remote_ssh_user=$(</span><span class="fu">ssh</span> <span class="va">$remotehost</span> whoami<span class="va">)</span>
136 <span class="kw">ssh</span> -t <span class="st">&quot;</span><span class="ot">$remotehost</span><span class="st">&quot;</span> sudo cp -r <span class="st">&quot;</span><span class="ot">$remote_shaarli_dir</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="ot">$remote_temp_dir</span><span class="st">&quot;</span> 134 <span class="fu">ssh</span> -t <span class="st">&quot;</span><span class="va">$remotehost</span><span class="st">&quot;</span> sudo cp -r <span class="st">&quot;</span><span class="va">$remote_shaarli_dir</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="va">$remote_temp_dir</span><span class="st">&quot;</span>
137 <span class="kw">ssh</span> -t <span class="st">&quot;</span><span class="ot">$remotehost</span><span class="st">&quot;</span> sudo chown -R <span class="st">&quot;</span><span class="ot">$remote_ssh_user</span><span class="st">&quot;</span>:<span class="st">&quot;</span><span class="ot">$remote_ssh_user</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="ot">$remote_temp_dir</span><span class="st">&quot;</span> 135 <span class="fu">ssh</span> -t <span class="st">&quot;</span><span class="va">$remotehost</span><span class="st">&quot;</span> sudo chown -R <span class="st">&quot;</span><span class="va">$remote_ssh_user</span><span class="st">&quot;</span>:<span class="st">&quot;</span><span class="va">$remote_ssh_user</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="va">$remote_temp_dir</span><span class="st">&quot;</span>
138 <span class="kw">scp</span> -rq <span class="st">&quot;</span><span class="ot">$remotehost</span><span class="st">&quot;</span>:<span class="st">&quot;</span><span class="ot">$remote_temp_dir</span><span class="st">&quot;</span> local-shaarli 136 <span class="fu">scp</span> -rq <span class="st">&quot;</span><span class="va">$remotehost</span><span class="st">&quot;</span>:<span class="st">&quot;</span><span class="va">$remote_temp_dir</span><span class="st">&quot;</span> local-shaarli
139 <span class="kw">ssh</span> <span class="st">&quot;</span><span class="ot">$remotehost</span><span class="st">&quot;</span> rm -r <span class="st">&quot;</span><span class="ot">$remote_temp_dir</span><span class="st">&quot;</span> 137 <span class="fu">ssh</span> <span class="st">&quot;</span><span class="va">$remotehost</span><span class="st">&quot;</span> rm -r <span class="st">&quot;</span><span class="va">$remote_temp_dir</span><span class="st">&quot;</span>
140<span class="kw">}</span> 138<span class="kw">}</span>
141 139
142<span class="fu">_CBServeShaarli()</span> <span class="kw">{</span> 140<span class="fu">_CBServeShaarli()</span> <span class="kw">{</span>
143 <span class="co">#TODO: allow serving a previously downloaded Shaarli</span> 141 <span class="co">#TODO: allow serving a previously downloaded Shaarli</span>
144 <span class="co">#TODO: ask before overwriting local copy, if it exists</span> 142 <span class="co">#TODO: ask before overwriting local copy, if it exists</span>
145 <span class="kw">cd</span> local-shaarli/ 143 <span class="bu">cd</span> local-shaarli/
146 <span class="kw">php</span> -S localhost:<span class="ot">${php_local_port}</span> 144 <span class="ex">php</span> -S localhost:<span class="va">${php_local_port}</span>
147 <span class="kw">echo</span> <span class="st">&quot;Please go to http://localhost:</span><span class="ot">${php_local_port}</span><span class="st">&quot;</span> 145 <span class="bu">echo</span> <span class="st">&quot;Please go to http://localhost:</span><span class="va">${php_local_port}</span><span class="st">&quot;</span>
148<span class="kw">}</span> 146<span class="kw">}</span>
149 147
150 148
151<span class="co">##### MAIN #################</span> 149<span class="co">##### MAIN #################</span>
152 150
153<span class="kw">_main</span></code></pre></div> 151<span class="ex">_main</span></code></pre></div>
154<p>This outputs:</p> 152<p>This outputs:</p>
155<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">./local-shaarli.sh</span> 153<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">./local-shaarli.sh</span>
156<span class="kw">PHP</span> 5.6.0RC4 Development Server started at Mon Sep 1 21:56:19 2014 154<span class="ex">PHP</span> 5.6.0RC4 Development Server started at Mon Sep 1 21:56:19 2014
157<span class="kw">Listening</span> on http://localhost:7431 155<span class="ex">Listening</span> on http://localhost:7431
158<span class="kw">Document</span> root is /home/user/local-shaarli/shaarli 156<span class="ex">Document</span> root is /home/user/local-shaarli/shaarli
159<span class="kw">Press</span> Ctrl-C to quit. 157<span class="ex">Press</span> Ctrl-C to quit.
160 158
161[<span class="kw">Mon</span> Sep 1 21:56:27 2014] ::1:57868 [200]: /[](.html) 159[<span class="ex">Mon</span> Sep 1 21:56:27 2014] ::1:57868 [200]: /[](.html)
162[<span class="kw">Mon</span> Sep 1 21:56:27 2014] ::1:57869 [200]: /index.html[](.html) 160[<span class="ex">Mon</span> Sep 1 21:56:27 2014] ::1:57869 [200]: /index.html[](.html)
163[<span class="kw">Mon</span> Sep 1 21:56:37 2014] ::1:57881 [200]: /...[](.html)</code></pre></div> 161[<span class="ex">Mon</span> Sep 1 21:56:37 2014] ::1:57881 [200]: /...[](.html)</code></pre></div>
164</body> 162</body>
165</html> 163</html>
diff --git a/doc/Create-and-serve-multiple-Shaarlis-(farm).html b/doc/Create-and-serve-multiple-Shaarlis-(farm).html
index 933144e4..672e4bf3 100644
--- a/doc/Create-and-serve-multiple-Shaarlis-(farm).html
+++ b/doc/Create-and-serve-multiple-Shaarlis-(farm).html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,33 +96,32 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="create-and-serve-multiple-shaarlis-farm">Create and serve multiple Shaarlis (farm)</h1> 102<h1 id="create-and-serve-multiple-shaarlis-farm">Create and serve multiple Shaarlis (farm)</h1>
105<p>Example bash script (creates multiple shaarli instances and generates an HTML index of them)</p> 103<p>Example bash script (creates multiple shaarli instances and generates an HTML index of them)</p>
106<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co">#!/bin/bash</span> 104<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co">#!/bin/bash</span>
107<span class="kw">set</span> <span class="kw">-o</span> errexit 105<span class="kw">set</span> <span class="ex">-o</span> errexit
108<span class="kw">set</span> <span class="kw">-o</span> nounset 106<span class="kw">set</span> <span class="ex">-o</span> nounset
109 107
110<span class="co">#config</span> 108<span class="co">#config</span>
111<span class="ot">shaarli_base_dir=</span><span class="st">&#39;/var/www/shaarli&#39;</span> 109<span class="va">shaarli_base_dir=</span><span class="st">&#39;/var/www/shaarli&#39;</span>
112<span class="ot">accounts=</span><span class="st">&#39;bob john whatever username&#39;</span> 110<span class="va">accounts=</span><span class="st">&#39;bob john whatever username&#39;</span>
113<span class="ot">shaarli_repo_url=</span><span class="st">&#39;https://github.com/shaarli/Shaarli&#39;</span> 111<span class="va">shaarli_repo_url=</span><span class="st">&#39;https://github.com/shaarli/Shaarli&#39;</span>
114<span class="ot">ref=</span><span class="st">&quot;master&quot;</span> 112<span class="va">ref=</span><span class="st">&quot;master&quot;</span>
115 113
116<span class="co">#clone multiple shaarli instances</span> 114<span class="co">#clone multiple shaarli instances</span>
117<span class="kw">if [</span> <span class="ot">!</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">&quot;</span><span class="kw"> ]</span>; <span class="kw">then</span> <span class="kw">mkdir</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">&quot;</span><span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(.html)</span> 115<span class="kw">if</span><span class="bu"> [</span> <span class="ot">!</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">&quot;</span><span class="bu"> ]</span>; <span class="kw">then</span> <span class="fu">mkdir</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">&quot;</span><span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(</span><span class="ex">.html</span><span class="kw">)</span>
118 116
119<span class="kw">for</span> <span class="kw">account</span> in <span class="ot">$accounts</span><span class="kw">;</span> <span class="kw">do</span> 117<span class="kw">for</span> <span class="ex">account</span> in <span class="va">$accounts</span><span class="kw">;</span> <span class="kw">do</span>
120 <span class="kw">if [</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">/</span><span class="ot">$account</span><span class="st">&quot;</span><span class="kw"> ]</span>;[]<span class="kw">(.html)</span> 118 <span class="kw">if</span><span class="bu"> [</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">/</span><span class="va">$account</span><span class="st">&quot;</span><span class="bu"> ]</span>;[]<span class="kw">(</span><span class="ex">.html</span><span class="kw">)</span>
121 <span class="kw">then</span> <span class="kw">echo</span> <span class="st">&quot;[info] account </span><span class="ot">$account</span><span class="st"> already exists, skipping&quot;</span><span class="kw">;</span>[]<span class="kw">(.html)</span> 119 <span class="kw">then</span> <span class="bu">echo</span> <span class="st">&quot;[info] account </span><span class="va">$account</span><span class="st"> already exists, skipping&quot;</span><span class="kw">;</span>[]<span class="kw">(</span><span class="ex">.html</span><span class="kw">)</span>
122 <span class="kw">else</span> <span class="kw">echo</span> <span class="st">&quot;[info] creating new account </span><span class="ot">$account</span><span class="st"> ...&quot;</span><span class="kw">;</span> <span class="kw">git</span> clone --quiet <span class="st">&quot;</span><span class="ot">$shaarli_repo_url</span><span class="st">&quot;</span> -b <span class="st">&quot;</span><span class="ot">$ref</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">/</span><span class="ot">$account</span><span class="st">&quot;</span><span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(.html)</span> 120 <span class="kw">else</span> <span class="bu">echo</span> <span class="st">&quot;[info] creating new account </span><span class="va">$account</span><span class="st"> ...&quot;</span><span class="kw">;</span> <span class="fu">git</span> clone --quiet <span class="st">&quot;</span><span class="va">$shaarli_repo_url</span><span class="st">&quot;</span> -b <span class="st">&quot;</span><span class="va">$ref</span><span class="st">&quot;</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">/</span><span class="va">$account</span><span class="st">&quot;</span><span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(</span><span class="ex">.html</span><span class="kw">)</span>
123<span class="kw">done</span> 121<span class="kw">done</span>
124 122
125<span class="co">#generate html index of shaarlis</span> 123<span class="co">#generate html index of shaarlis</span>
126<span class="ot">htmlhead=</span><span class="st">&#39;&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot; &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&gt;</span> 124<span class="va">htmlhead=</span><span class="st">&#39;&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot; &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&gt;</span>
127<span class="st">&lt;!-- Minimal html template thanks to http://www.sitepoint.com/a-minimal-html-document/ --&gt;</span> 125<span class="st">&lt;!-- Minimal html template thanks to http://www.sitepoint.com/a-minimal-html-document/ --&gt;</span>
128<span class="st">&lt;html lang=&quot;en&quot;&gt;</span> 126<span class="st">&lt;html lang=&quot;en&quot;&gt;</span>
129<span class="st"> &lt;head&gt;</span> 127<span class="st"> &lt;head&gt;</span>
@@ -136,9 +134,9 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
136<span class="st"> &lt;h1&gt;My Shaarli farm&lt;/h1&gt;</span> 134<span class="st"> &lt;h1&gt;My Shaarli farm&lt;/h1&gt;</span>
137<span class="st"> &lt;ul style=&quot;list-style-type: none;&quot;&gt;&#39;</span> 135<span class="st"> &lt;ul style=&quot;list-style-type: none;&quot;&gt;&#39;</span>
138 136
139<span class="ot">accountlinks=</span><span class="st">&#39;&#39;</span> 137<span class="va">accountlinks=</span><span class="st">&#39;&#39;</span>
140 138
141<span class="ot">htmlfooter=</span><span class="st">&#39;</span> 139<span class="va">htmlfooter=</span><span class="st">&#39;</span>
142<span class="st"> &lt;/ul&gt;</span> 140<span class="st"> &lt;/ul&gt;</span>
143<span class="st"> &lt;/div&gt;</span> 141<span class="st"> &lt;/div&gt;</span>
144<span class="st"> &lt;/body&gt;</span> 142<span class="st"> &lt;/body&gt;</span>
@@ -146,14 +144,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
146 144
147 145
148 146
149<span class="kw">for</span> <span class="kw">account</span> in <span class="ot">$accounts</span><span class="kw">;</span> <span class="kw">do</span> <span class="ot">accountlinks=</span><span class="st">&quot;</span><span class="ot">$accountlinks</span><span class="st">\n&lt;li&gt;&lt;a href=</span><span class="dt">\&quot;</span><span class="ot">$account</span><span class="dt">\&quot;</span><span class="st">&gt;</span><span class="ot">$account</span><span class="st">&lt;/a&gt;&lt;/li&gt;&quot;</span>; <span class="kw">done</span> 147<span class="kw">for</span> <span class="ex">account</span> in <span class="va">$accounts</span><span class="kw">;</span> <span class="kw">do</span> <span class="va">accountlinks=</span><span class="st">&quot;</span><span class="va">$accountlinks</span><span class="st">\n&lt;li&gt;&lt;a href=</span><span class="dt">\&quot;</span><span class="va">$account</span><span class="dt">\&quot;</span><span class="st">&gt;</span><span class="va">$account</span><span class="st">&lt;/a&gt;&lt;/li&gt;&quot;</span>; <span class="kw">done</span>
150<span class="kw">if [</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">/index.html&quot;</span><span class="kw"> ]</span>; <span class="kw">then</span> <span class="kw">echo</span> <span class="st">&quot;[removing old index.html]&quot;</span><span class="kw">;</span> <span class="kw">rm</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">/index.html&quot;</span> ]<span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(.html)</span> 148<span class="kw">if</span><span class="bu"> [</span> <span class="ot">-d</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">/index.html&quot;</span><span class="bu"> ]</span>; <span class="kw">then</span> <span class="bu">echo</span> <span class="st">&quot;[removing old index.html]&quot;</span><span class="kw">;</span> <span class="fu">rm</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">/index.html&quot;</span> ]<span class="kw">;</span> <span class="kw">fi</span>[]<span class="kw">(</span><span class="ex">.html</span><span class="kw">)</span>
151<span class="kw">echo</span> <span class="st">&quot;[info] generating new index of shaarlis&quot;</span>[](.html) 149<span class="bu">echo</span> <span class="st">&quot;[info] generating new index of shaarlis&quot;</span>[](.html)
152<span class="kw">echo</span> -e <span class="st">&quot;</span><span class="ot">$htmlhead</span><span class="st"> </span><span class="ot">$accountlinks</span><span class="st"> </span><span class="ot">$htmlfooter</span><span class="st">&quot;</span> <span class="kw">&gt;</span> <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">/index.html&quot;</span> 150<span class="bu">echo</span> -e <span class="st">&quot;</span><span class="va">$htmlhead</span><span class="st"> </span><span class="va">$accountlinks</span><span class="st"> </span><span class="va">$htmlfooter</span><span class="st">&quot;</span> <span class="op">&gt;</span> <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">/index.html&quot;</span>
153<span class="kw">echo</span> <span class="st">&#39;[info] done.&#39;</span>[](.html) 151<span class="bu">echo</span> <span class="st">&#39;[info] done.&#39;</span>[](.html)
154<span class="kw">echo</span> <span class="st">&quot;[info] list of accounts: </span><span class="ot">$accounts</span><span class="st">&quot;</span>[](.html) 152<span class="bu">echo</span> <span class="st">&quot;[info] list of accounts: </span><span class="va">$accounts</span><span class="st">&quot;</span>[](.html)
155<span class="kw">echo</span> <span class="st">&quot;[info] contents of </span><span class="ot">$shaarli_base_dir</span><span class="st">:&quot;</span>[](.html) 153<span class="bu">echo</span> <span class="st">&quot;[info] contents of </span><span class="va">$shaarli_base_dir</span><span class="st">:&quot;</span>[](.html)
156<span class="kw">tree</span> -a -L 1 <span class="st">&quot;</span><span class="ot">$shaarli_base_dir</span><span class="st">&quot;</span></code></pre></div> 154<span class="ex">tree</span> -a -L 1 <span class="st">&quot;</span><span class="va">$shaarli_base_dir</span><span class="st">&quot;</span></code></pre></div>
157<p>This script just serves as an example. More precise or complex (applying custom configuration, etc) automation is possible using configuration management software like <a href="https://www.ansible.com/">Ansible</a><a href=".html"></a></p> 155<p>This script just serves as an example. More precise or complex (applying custom configuration, etc) automation is possible using configuration management software like <a href="https://www.ansible.com/">Ansible</a><a href=".html"></a></p>
158</body> 156</body>
159</html> 157</html>
diff --git a/doc/Datastore-hacks.html b/doc/Datastore-hacks.html
index 88639402..15da09d4 100644
--- a/doc/Datastore-hacks.html
+++ b/doc/Datastore-hacks.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
diff --git a/doc/Development.html b/doc/Development.html
index 2eacff94..c5776413 100644
--- a/doc/Development.html
+++ b/doc/Development.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Directory-structure.html b/doc/Directory-structure.html
index 003d4d94..404ff7c8 100644
--- a/doc/Directory-structure.html
+++ b/doc/Directory-structure.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,39 +96,38 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="directory-structure">Directory structure</h1> 102<h1 id="directory-structure">Directory structure</h1>
105<p>Here is the directory structure of Shaarli and the purpose of the different files:</p> 103<p>Here is the directory structure of Shaarli and the purpose of the different files:</p>
106<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="kw">index.php</span> <span class="co"># Main program</span> 104<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="ex">index.php</span> <span class="co"># Main program</span>
107 <span class="kw">application/</span> <span class="co"># Shaarli classes</span> 105 <span class="ex">application/</span> <span class="co"># Shaarli classes</span>
108 ├── <span class="kw">LinkDB.php</span> 106 ├── <span class="ex">LinkDB.php</span>
109 └── <span class="kw">Utils.php</span> 107 └── <span class="ex">Utils.php</span>
110 <span class="kw">tests/</span> <span class="co"># Shaarli unitary &amp; functional tests</span> 108 <span class="ex">tests/</span> <span class="co"># Shaarli unitary &amp; functional tests</span>
111 ├── <span class="kw">LinkDBTest.php</span> 109 ├── <span class="ex">LinkDBTest.php</span>
112 ├── <span class="kw">utils</span> <span class="co"># utilities to ease testing</span> 110 ├── <span class="ex">utils</span> <span class="co"># utilities to ease testing</span>
113 │ └── <span class="kw">ReferenceLinkDB.php</span> 111 │ └── <span class="ex">ReferenceLinkDB.php</span>
114 └── <span class="kw">UtilsTest.php</span> 112 └── <span class="ex">UtilsTest.php</span>
115 <span class="kw">COPYING</span> <span class="co"># Shaarli license</span> 113 <span class="ex">COPYING</span> <span class="co"># Shaarli license</span>
116 <span class="kw">inc/</span> <span class="co"># static assets and 3rd party libraries</span> 114 <span class="ex">inc/</span> <span class="co"># static assets and 3rd party libraries</span>
117 ├── <span class="kw">awesomplete.*</span> <span class="co"># tags autocompletion library</span> 115 ├── <span class="ex">awesomplete.*</span> <span class="co"># tags autocompletion library</span>
118 ├── <span class="kw">blazy.*</span> <span class="co"># picture wall lazy image loading library</span> 116 ├── <span class="ex">blazy.*</span> <span class="co"># picture wall lazy image loading library</span>
119 ├── <span class="kw">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span> 117 ├── <span class="ex">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span>
120 ├── <span class="kw">qr.*</span> <span class="co"># qr code generation library</span> 118 ├── <span class="ex">qr.*</span> <span class="co"># qr code generation library</span>
121 └──<span class="kw">rain.tpl.class.php</span> <span class="co"># RainTPL templating library</span> 119 └──<span class="ex">rain.tpl.class.php</span> <span class="co"># RainTPL templating library</span>
122 <span class="kw">tpl/</span> <span class="co"># RainTPL templates for Shaarli. They are used to build the pages.</span> 120 <span class="ex">tpl/</span> <span class="co"># RainTPL templates for Shaarli. They are used to build the pages.</span>
123 <span class="kw">images/</span> <span class="co"># Images and icons used in Shaarli</span> 121 <span class="ex">images/</span> <span class="co"># Images and icons used in Shaarli</span>
124 <span class="kw">data/</span> <span class="co"># data storage: bookmark database, configuration, logs, banlist…</span> 122 <span class="ex">data/</span> <span class="co"># data storage: bookmark database, configuration, logs, banlist…</span>
125 ├── <span class="kw">config.php</span> <span class="co"># Shaarli configuration (login, password, timezone, title…)</span> 123 ├── <span class="ex">config.php</span> <span class="co"># Shaarli configuration (login, password, timezone, title…)</span>
126 ├── <span class="kw">datastore.php</span> <span class="co"># Your link database (compressed).</span> 124 ├── <span class="ex">datastore.php</span> <span class="co"># Your link database (compressed).</span>
127 ├── <span class="kw">ipban.php</span> <span class="co"># IP address ban system data</span> 125 ├── <span class="ex">ipban.php</span> <span class="co"># IP address ban system data</span>
128 ├── <span class="kw">lastupdatecheck.txt</span> <span class="co"># Update check timestamp file</span> 126 ├── <span class="ex">lastupdatecheck.txt</span> <span class="co"># Update check timestamp file</span>
129 └──<span class="kw">log.txt</span> <span class="co"># login/IPban log.</span> 127 └──<span class="ex">log.txt</span> <span class="co"># login/IPban log.</span>
130 <span class="kw">cache/</span> <span class="co"># thumbnails cache</span> 128 <span class="ex">cache/</span> <span class="co"># thumbnails cache</span>
131 <span class="co"># This directory is automatically created. You can erase it anytime you want.</span> 129 <span class="co"># This directory is automatically created. You can erase it anytime you want.</span>
132 <span class="kw">tmp/</span> <span class="co"># Temporary directory for compiled RainTPL templates.</span> 130 <span class="ex">tmp/</span> <span class="co"># Temporary directory for compiled RainTPL templates.</span>
133 <span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div> 131 <span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div>
134</body> 132</body>
135</html> 133</html>
diff --git a/doc/Docker.html b/doc/Docker.html
index a443d100..e89c90fb 100644
--- a/doc/Docker.html
+++ b/doc/Docker.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -112,18 +110,18 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
112<p>Install <a href="https://www.docker.com/">Docker</a>, by following the instructions relevant<a href=".html"></a><br /> 110<p>Install <a href="https://www.docker.com/">Docker</a>, by following the instructions relevant<a href=".html"></a><br />
113to your OS / distribution, and start the service.</p> 111to your OS / distribution, and start the service.</p>
114<h4 id="search-an-image-on-dockerhub">Search an image on <a href="https://hub.docker.com/">DockerHub</a><a href=".html"></a></h4> 112<h4 id="search-an-image-on-dockerhub">Search an image on <a href="https://hub.docker.com/">DockerHub</a><a href=".html"></a></h4>
115<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">docker</span> search debian 113<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> search debian
116 114
117<span class="kw">NAME</span> DESCRIPTION STARS OFFICIAL AUTOMATED 115<span class="ex">NAME</span> DESCRIPTION STARS OFFICIAL AUTOMATED
118<span class="kw">ubuntu</span> Ubuntu is a Debian-based Linux operating s... 2065 [OK][](.html) 116<span class="ex">ubuntu</span> Ubuntu is a Debian-based Linux operating s... 2065 [OK][](.html)
119<span class="kw">debian</span> Debian is a Linux distribution that<span class="st">&#39;s comp... 603 [OK][](.html)</span> 117<span class="ex">debian</span> Debian is a Linux distribution that<span class="st">&#39;s comp... 603 [OK][](.html)</span>
120<span class="st">google/debian 47 [OK][](.html)</span></code></pre></div> 118<span class="st">google/debian 47 [OK][](.html)</span></code></pre></div>
121<h4 id="show-available-tags-for-a-repository">Show available tags for a repository</h4> 119<h4 id="show-available-tags-for-a-repository">Show available tags for a repository</h4>
122<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">curl</span> https://index.docker.io/v1/repositories/debian/tags <span class="kw">|</span> <span class="kw">python</span> -m json.tool 120<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">curl</span> https://index.docker.io/v1/repositories/debian/tags <span class="kw">|</span> <span class="ex">python</span> -m json.tool
123 121
124<span class="kw">%</span> Total % Received % Xferd Average Speed Time Time Time Current 122<span class="ex">%</span> Total % Received % Xferd Average Speed Time Time Time Current
125<span class="kw">Dload</span> Upload Total Spent Left Speed 123<span class="ex">Dload</span> Upload Total Spent Left Speed
126<span class="kw">100</span> 1283 0 1283 0 0 433 0 --:--:-- 0:00:02 --:--:-- 433</code></pre></div> 124<span class="ex">100</span> 1283 0 1283 0 0 433 0 --:--:-- 0:00:02 --:--:-- 433</code></pre></div>
127<p>Sample output:</p> 125<p>Sample output:</p>
128<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="ot">[[]</span><span class="er">(.html)</span> 126<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="ot">[[]</span><span class="er">(.html)</span>
129 <span class="fu">{</span> 127 <span class="fu">{</span>
@@ -148,14 +146,14 @@ to your OS / distribution, and start the service.</p>
148 <span class="fu">}</span> 146 <span class="fu">}</span>
149<span class="ot">]</span></code></pre></div> 147<span class="ot">]</span></code></pre></div>
150<h4 id="pull-an-image-from-dockerhub">Pull an image from DockerHub</h4> 148<h4 id="pull-an-image-from-dockerhub">Pull an image from DockerHub</h4>
151<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">docker</span> pull repository[:tag][](.html) 149<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> pull repository[:tag][](.html)
152 150
153$ <span class="kw">docker</span> pull debian:wheezy 151$ <span class="ex">docker</span> pull debian:wheezy
154<span class="kw">wheezy</span>: Pulling from debian 152<span class="ex">wheezy</span>: Pulling from debian
155<span class="kw">4c8cbfd2973e</span>: Pull complete 153<span class="ex">4c8cbfd2973e</span>: Pull complete
156<span class="kw">60c52dbe9d91</span>: Pull complete 154<span class="ex">60c52dbe9d91</span>: Pull complete
157<span class="kw">Digest</span>: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe 155<span class="ex">Digest</span>: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
158<span class="kw">Status</span>: Downloaded newer image for debian:wheezy</code></pre></div> 156<span class="ex">Status</span>: Downloaded newer image for debian:wheezy</code></pre></div>
159<h2 id="get-and-run-a-shaarli-image">Get and run a Shaarli image</h2> 157<h2 id="get-and-run-a-shaarli-image">Get and run a Shaarli image</h2>
160<h3 id="dockerhub-repository">DockerHub repository</h3> 158<h3 id="dockerhub-repository">DockerHub repository</h3>
161<p>The images can be found in the <a href="https://hub.docker.com/r/shaarli/shaarli/"><code>shaarli/shaarli</code></a><a href=".html"></a><br /> 159<p>The images can be found in the <a href="https://hub.docker.com/r/shaarli/shaarli/"><code>shaarli/shaarli</code></a><a href=".html"></a><br />
@@ -173,53 +171,53 @@ repository.</p>
173<li><a href="http://nginx.org/">Nginx</a><a href=".html"></a></li> 171<li><a href="http://nginx.org/">Nginx</a><a href=".html"></a></li>
174</ul> 172</ul>
175<h3 id="download-from-dockerhub">Download from DockerHub</h3> 173<h3 id="download-from-dockerhub">Download from DockerHub</h3>
176<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">docker</span> pull shaarli/shaarli 174<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> pull shaarli/shaarli
177<span class="kw">latest</span>: Pulling from shaarli/shaarli 175<span class="ex">latest</span>: Pulling from shaarli/shaarli
178<span class="kw">32716d9fcddb</span>: Pull complete 176<span class="ex">32716d9fcddb</span>: Pull complete
179<span class="kw">84899d045435</span>: Pull complete 177<span class="ex">84899d045435</span>: Pull complete
180<span class="kw">4b6ad7444763</span>: Pull complete 178<span class="ex">4b6ad7444763</span>: Pull complete
181<span class="kw">e0345ef7a3e0</span>: Pull complete 179<span class="ex">e0345ef7a3e0</span>: Pull complete
182<span class="kw">5c1dd344094f</span>: Pull complete 180<span class="ex">5c1dd344094f</span>: Pull complete
183<span class="kw">6422305a200b</span>: Pull complete 181<span class="ex">6422305a200b</span>: Pull complete
184<span class="kw">7d63f861dbef</span>: Pull complete 182<span class="ex">7d63f861dbef</span>: Pull complete
185<span class="kw">3eb97210645c</span>: Pull complete 183<span class="ex">3eb97210645c</span>: Pull complete
186<span class="kw">869319d746ff</span>: Already exists 184<span class="ex">869319d746ff</span>: Already exists
187<span class="kw">869319d746ff</span>: Pulling fs layer 185<span class="ex">869319d746ff</span>: Pulling fs layer
188<span class="kw">902b87aaaec9</span>: Already exists 186<span class="ex">902b87aaaec9</span>: Already exists
189<span class="kw">Digest</span>: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98 187<span class="ex">Digest</span>: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98
190<span class="kw">Status</span>: Downloaded newer image for shaarli/shaarli:latest</code></pre></div> 188<span class="ex">Status</span>: Downloaded newer image for shaarli/shaarli:latest</code></pre></div>
191<h3 id="create-and-start-a-new-container-from-the-image">Create and start a new container from the image</h3> 189<h3 id="create-and-start-a-new-container-from-the-image">Create and start a new container from the image</h3>
192<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># map the host&#39;s :8000 port to the container&#39;s :80 port</span> 190<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># map the host&#39;s :8000 port to the container&#39;s :80 port</span>
193$ <span class="kw">docker</span> create -p 8000:80 shaarli/shaarli 191$ <span class="ex">docker</span> create -p 8000:80 shaarli/shaarli
194<span class="kw">d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101</span> 192<span class="ex">d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101</span>
195 193
196<span class="co"># launch the container in the background</span> 194<span class="co"># launch the container in the background</span>
197$ <span class="kw">docker</span> start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101 195$ <span class="ex">docker</span> start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
198<span class="kw">d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101</span> 196<span class="ex">d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101</span>
199 197
200<span class="co"># list active containers</span> 198<span class="co"># list active containers</span>
201$ <span class="kw">docker</span> ps 199$ <span class="ex">docker</span> ps
202<span class="kw">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES 200<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES
203<span class="kw">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000-<span class="kw">&gt;</span>80/tcp backstabbing_galileo</code></pre></div> 201<span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000-<span class="op">&gt;</span>80/tcp backstabbing_galileo</code></pre></div>
204<h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3> 202<h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3>
205<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">docker</span> stop backstabbing_galileo <span class="co"># those docker guys are really rude to physicists!</span> 203<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> stop backstabbing_galileo <span class="co"># those docker guys are really rude to physicists!</span>
206<span class="kw">backstabbing_galileo</span> 204<span class="ex">backstabbing_galileo</span>
207 205
208<span class="co"># check the container is stopped</span> 206<span class="co"># check the container is stopped</span>
209$ <span class="kw">docker</span> ps 207$ <span class="ex">docker</span> ps
210<span class="kw">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES 208<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES
211 209
212<span class="co"># list ALL containers</span> 210<span class="co"># list ALL containers</span>
213$ <span class="kw">docker</span> ps -a 211$ <span class="ex">docker</span> ps -a
214<span class="kw">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES 212<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES
215<span class="kw">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="kw">48</span> seconds ago backstabbing_galileo 213<span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="ex">48</span> seconds ago backstabbing_galileo
216 214
217<span class="co"># destroy the container</span> 215<span class="co"># destroy the container</span>
218$ <span class="kw">docker</span> rm backstabbing_galileo <span class="co"># let&#39;s put an end to these barbarian practices</span> 216$ <span class="ex">docker</span> rm backstabbing_galileo <span class="co"># let&#39;s put an end to these barbarian practices</span>
219<span class="kw">backstabbing_galileo</span> 217<span class="ex">backstabbing_galileo</span>
220 218
221$ <span class="kw">docker</span> ps -a 219$ <span class="ex">docker</span> ps -a
222<span class="kw">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES</code></pre></div> 220<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES</code></pre></div>
223<h2 id="resources">Resources</h2> 221<h2 id="resources">Resources</h2>
224<h3 id="docker-1">Docker</h3> 222<h3 id="docker-1">Docker</h3>
225<ul> 223<ul>
diff --git a/doc/Download-CSS-styles-from-an-OPML-list.html b/doc/Download-CSS-styles-from-an-OPML-list.html
index 22771502..a4f68ac6 100644
--- a/doc/Download-CSS-styles-from-an-OPML-list.html
+++ b/doc/Download-CSS-styles-from-an-OPML-list.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -209,8 +207,8 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
209 207
210<span class="co">/**</span> 208<span class="co">/**</span>
211<span class="co"> * Reading directory list, courtesy of http://www.laughing-buddha.net/php/dirlist/</span> 209<span class="co"> * Reading directory list, courtesy of http://www.laughing-buddha.net/php/dirlist/</span>
212<span class="co"> * </span><span class="kw">@param</span><span class="co"> </span><span class="kw">directory</span><span class="co"> the directory we want to list files of</span> 210<span class="co"> * </span><span class="an">@param</span><span class="co"> </span><span class="cv">directory</span><span class="co"> the directory we want to list files of</span>
213<span class="co"> * </span><span class="kw">@return</span><span class="co"> a simple array containing the list of absolute file paths. Notice that current file (&quot;.&quot;) and parent one(&quot;..&quot;)</span> 211<span class="co"> * </span><span class="an">@return</span><span class="co"> a simple array containing the list of absolute file paths. Notice that current file (&quot;.&quot;) and parent one(&quot;..&quot;)</span>
214<span class="co"> * are not listed here</span> 212<span class="co"> * are not listed here</span>
215<span class="co"> */</span> 213<span class="co"> */</span>
216<span class="kw">function</span> getDirectoryList <span class="ot">(</span><span class="kw">$directory</span><span class="ot">)</span> { 214<span class="kw">function</span> getDirectoryList <span class="ot">(</span><span class="kw">$directory</span><span class="ot">)</span> {
diff --git a/doc/Download.html b/doc/Download-and-Installation.html
index 9f9f5117..b9cac360 100644
--- a/doc/Download.html
+++ b/doc/Download-and-Installation.html
@@ -4,7 +4,7 @@
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta name="generator" content="pandoc"> 5 <meta name="generator" content="pandoc">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
7 <title>Shaarli – Download</title> 7 <title>Shaarli – Download and Installation</title>
8 <style type="text/css">code{white-space: pre;}</style> 8 <style type="text/css">code{white-space: pre;}</style>
9 <style type="text/css"> 9 <style type="text/css">
10div.sourceCode { overflow-x: auto; } 10div.sourceCode { overflow-x: auto; }
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,27 +96,75 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="download">Download</h1> 102<h1 id="download-and-installation">Download and Installation</h1>
105<h2 id="get-shaarli">Get Shaarli!</h2> 103<h1 id="get-shaarli">Get Shaarli!</h1>
106<h3 id="latest-stable-revision">Latest stable revision</h3> 104<p>To install Shaarli, simply place the files in a directory under your webserver's Document Root (or directly at the document root). Make sure your <a href="Server-requirements">server</a> is properly <a href="Server-configuration">configured</a>.<a href=".html"></a></p>
107<p>This revision has been <a href="https://github.com/shaarli/Shaarli/releases">released</a> and tested.<a href=".html"></a></p> 105<p>Several releases are available:</p>
108<h4 id="clone-with-git-recommended">Clone with Git (recommended)</h4> 106<hr />
109<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">git</span> clone https://github.com/shaarli/Shaarli.git -b stable shaarli</code></pre></div> 107<h2 id="latest-release-recommended">Latest release (recommended)</h2>
110<h4 id="download-as-an-archive">Download as an archive</h4> 108<h3 id="download-as-an-archive">Download as an archive</h3>
111<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">wget</span> https://github.com/shaarli/Shaarli/archive/stable.zip 109<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
112$ <span class="kw">unzip</span> stable.zip 110<p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p>
113$ <span class="kw">mv</span> Shaarli-stable shaarli</code></pre></div> 111<p>The current latest released version is <code>v0.8.0</code></p>
114<p>Tarballs are also available:</p> 112<p>Or in command lines:</p>
115<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">wget</span> https://github.com/shaarli/Shaarli/archive/stable.tar.gz 113<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
116$ <span class="kw">tar</span> xvf stable.tar.gz 114$ <span class="fu">unzip</span> shaarli-v0.8.0-full.zip
117$ <span class="kw">mv</span> Shaarli-stable shaarli</code></pre></div> 115$ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div>
118<h3 id="development-mainline">Development (mainline)</h3> 116<table style="width:46%;">
117<colgroup>
118<col style="width: 8%" />
119<col style="width: 37%" />
120</colgroup>
121<thead>
122<tr class="header">
123<th>!</th>
124<th>In most cases, download Shaarli from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page. Cloning using <code>git</code> or downloading Github branches as zip files requires additional steps (see below).</th>
125</tr>
126</thead>
127<tbody>
128</tbody>
129</table>
130<h3 id="using-git">Using git</h3>
131<pre><code>mkdir -p /path/to/shaarli &amp;&amp; cd /path/to/shaarli/
132git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
133composer update --no-dev</code></pre>
134<hr />
135<h2 id="stable-version">Stable version</h2>
136<p>The stable version has been experienced by Shaarli users, and will receive security updates.</p>
137<h3 id="download-as-an-archive-1">Download as an archive</h3>
138<p>As a .zip archive:</p>
139<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/archive/stable.zip
140$ <span class="fu">unzip</span> stable.zip
141$ <span class="fu">mv</span> Shaarli-stable /path/to/shaarli/</code></pre></div>
142<p>As a .tar.gz archive :</p>
143<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/archive/stable.tar.gz
144$ <span class="fu">tar</span> xvf stable.tar.gz
145$ <span class="fu">mv</span> Shaarli-stable /path/to/shaarli/</code></pre></div>
146<h3 id="clone-with-git">Clone with Git</h3>
147<p><a href="https://getcomposer.org/">Composer</a> is required to build a functional Shaarli installation when pulling from git.<a href=".html"></a></p>
148<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
149<span class="co"># install/update third-party dependencies</span>
150$ <span class="bu">cd</span> /path/to/shaarli/
151$ <span class="ex">composer</span> update --no-dev</code></pre></div>
152<hr />
153<h2 id="development-version-mainline">Development version (mainline)</h2>
119<p><em>Use at your own risk!</em></p> 154<p><em>Use at your own risk!</em></p>
120<p>To get the latest changes:</p> 155<p>To get the latest changes from the <code>master</code> branch:</p>
121<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">git</span> clone https://github.com/shaarli/Shaarli.git shaarli</code></pre></div> 156<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># clone the repository </span>
157$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/
158<span class="co"># install/update third-party dependencies</span>
159$ <span class="bu">cd</span> /path/to/shaarli
160$ <span class="ex">composer</span> update --no-dev</code></pre></div>
161<hr />
162<h2 id="finish-installation">Finish Installation</h2>
163<p>Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.</p>
164<p><img src="http://i.imgur.com/wuMpDSN.png" alt="install screenshot" /><a href=".html"></a></p>
165<p>Setup your Shaarli installation, and it's ready to use!</p>
166<hr />
167<h2 id="updating-shaarli">Updating Shaarli</h2>
168<p>See <a href="Upgrade-and-migration">Upgrade and Migration</a><a href=".html"></a></p>
122</body> 169</body>
123</html> 170</html>
diff --git a/doc/Download-and-Installation.md b/doc/Download-and-Installation.md
new file mode 100644
index 00000000..32df8984
--- /dev/null
+++ b/doc/Download-and-Installation.md
@@ -0,0 +1,102 @@
1#Download and Installation
2# Get Shaarli!
3
4To install Shaarli, simply place the files in a directory under your webserver's Document Root (or directly at the document root). Make sure your [server](Server-requirements) is properly [configured](Server-configuration).[](.html)
5
6Several releases are available:
7
8--------------------------------------------------------
9
10## Latest release (recommended)
11### Download as an archive
12Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html)
13
14**Download our *shaarli-full* archive** to include dependencies.
15
16The current latest released version is `v0.8.0`
17
18Or in command lines:
19
20```bash
21$ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
22$ unzip shaarli-v0.8.0-full.zip
23$ mv Shaarli /path/to/shaarli/
24```
25
26| ! |In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|[](.html)
27|-----|--------------------------|
28
29### Using git
30
31```
32mkdir -p /path/to/shaarli && cd /path/to/shaarli/
33git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
34composer update --no-dev
35```
36
37--------------------------------------------------------
38
39## Stable version
40
41The stable version has been experienced by Shaarli users, and will receive security updates.
42
43### Download as an archive
44
45As a .zip archive:
46
47```bash
48$ wget https://github.com/shaarli/Shaarli/archive/stable.zip
49$ unzip stable.zip
50$ mv Shaarli-stable /path/to/shaarli/
51```
52
53As a .tar.gz archive :
54
55```bash
56$ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz
57$ tar xvf stable.tar.gz
58$ mv Shaarli-stable /path/to/shaarli/
59```
60
61### Clone with Git
62
63[Composer](https://getcomposer.org/) is required to build a functional Shaarli installation when pulling from git.[](.html)
64
65```bash
66$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
67# install/update third-party dependencies
68$ cd /path/to/shaarli/
69$ composer update --no-dev
70```
71
72--------------------------------------------------------
73
74## Development version (mainline)
75
76_Use at your own risk!_
77
78To get the latest changes from the `master` branch:
79
80```bash
81# clone the repository
82$ git clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/
83# install/update third-party dependencies
84$ cd /path/to/shaarli
85$ composer update --no-dev
86```
87
88--------------------------------------------------------
89
90## Finish Installation
91
92Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.
93
94![install screenshot](http://i.imgur.com/wuMpDSN.png)[](.html)
95
96Setup your Shaarli installation, and it's ready to use!
97
98--------------------------------------------------------
99
100## Updating Shaarli
101
102See [Upgrade and Migration](Upgrade-and-migration)[](.html)
diff --git a/doc/Download.md b/doc/Download.md
deleted file mode 100644
index 7930f541..00000000
--- a/doc/Download.md
+++ /dev/null
@@ -1,31 +0,0 @@
1#Download
2## Get Shaarli!
3### Latest stable revision
4This revision has been [released](https://github.com/shaarli/Shaarli/releases) and tested.[](.html)
5
6#### Clone with Git (recommended)
7```bash
8$ git clone https://github.com/shaarli/Shaarli.git -b stable shaarli
9```
10
11#### Download as an archive
12```bash
13$ wget https://github.com/shaarli/Shaarli/archive/stable.zip
14$ unzip stable.zip
15$ mv Shaarli-stable shaarli
16```
17
18Tarballs are also available:
19```bash
20$ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz
21$ tar xvf stable.tar.gz
22$ mv Shaarli-stable shaarli
23```
24
25### Development (mainline)
26_Use at your own risk!_
27
28To get the latest changes:
29```bash
30$ git clone https://github.com/shaarli/Shaarli.git shaarli
31```
diff --git a/doc/Example-patch---add-new-via-field-for-links.html b/doc/Example-patch---add-new-via-field-for-links.html
index 7db43107..133224e2 100644
--- a/doc/Example-patch---add-new-via-field-for-links.html
+++ b/doc/Example-patch---add-new-via-field-for-links.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/FAQ.html b/doc/FAQ.html
index 3b6b956d..61f3475f 100644
--- a/doc/FAQ.html
+++ b/doc/FAQ.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Firefox-share.html b/doc/Firefox-share.html
index add6d4e8..d7dcc282 100644
--- a/doc/Firefox-share.html
+++ b/doc/Firefox-share.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/GnuPG-signature.html b/doc/GnuPG-signature.html
index c431f9ad..50b904d5 100644
--- a/doc/GnuPG-signature.html
+++ b/doc/GnuPG-signature.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -129,26 +127,26 @@ Keys</a></li>
129<li><a href="https://help.github.com/articles/generating-a-gpg-key/">Generating a GPG key</a> (GitHub)<a href=".html"></a></li> 127<li><a href="https://help.github.com/articles/generating-a-gpg-key/">Generating a GPG key</a> (GitHub)<a href=".html"></a></li>
130</ul> 128</ul>
131<h3 id="gpg---provide-identity-information">gpg - provide identity information</h3> 129<h3 id="gpg---provide-identity-information">gpg - provide identity information</h3>
132<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">gpg</span> --gen-key 130<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">gpg</span> --gen-key
133 131
134<span class="kw">gpg</span> (GnuPG) <span class="kw">2.1.6;</span> <span class="kw">Copyright</span> (C) <span class="kw">2015</span> Free Software Foundation, Inc. 132<span class="ex">gpg</span> (GnuPG) <span class="ex">2.1.6</span><span class="kw">;</span> <span class="ex">Copyright</span> (C) <span class="ex">2015</span> Free Software Foundation, Inc.
135<span class="kw">This</span> is free software: you are free to change and redistribute it. 133<span class="ex">This</span> is free software: you are free to change and redistribute it.
136<span class="kw">There</span> is NO WARRANTY, to the extent permitted by law. 134<span class="ex">There</span> is NO WARRANTY, to the extent permitted by law.
137 135
138<span class="kw">Note</span>: Use <span class="st">&quot;gpg2 --full-gen-key&quot;</span> for a full featured key generation dialog. 136<span class="ex">Note</span>: Use <span class="st">&quot;gpg2 --full-gen-key&quot;</span> for a full featured key generation dialog.
139 137
140<span class="kw">GnuPG</span> needs to construct a user ID to identify your key. 138<span class="ex">GnuPG</span> needs to construct a user ID to identify your key.
141 139
142<span class="kw">Real</span> name: Marvin the Paranoid Android 140<span class="ex">Real</span> name: Marvin the Paranoid Android
143<span class="kw">Email</span> address: marvin@h2g2.net 141<span class="ex">Email</span> address: marvin@h2g2.net
144<span class="kw">You</span> selected this USER-ID: 142<span class="ex">You</span> selected this USER-ID:
145 <span class="st">&quot;Marvin the Paranoid Android &lt;marvin@h2g2.net&gt;&quot;</span> 143 <span class="st">&quot;Marvin the Paranoid Android &lt;marvin@h2g2.net&gt;&quot;</span>
146 144
147<span class="kw">Change</span> (N)<span class="kw">ame</span>, (E)<span class="kw">mail</span>, or (O)<span class="kw">kay</span>/<span class="kw">(Q)uit?</span> o 145<span class="ex">Change</span> (N)<span class="ex">ame</span>, (E)<span class="ex">mail</span>, or (O)<span class="ex">kay</span>/<span class="kw">(</span><span class="ex">Q</span><span class="kw">)</span><span class="ex">uit?</span> o
148<span class="kw">We</span> need to generate a lot of random bytes. It is a good idea to perform 146<span class="ex">We</span> need to generate a lot of random bytes. It is a good idea to perform
149<span class="kw">some</span> other action (type on the keyboard, move the mouse, utilize the 147<span class="ex">some</span> other action (type on the keyboard, move the mouse, utilize the
150<span class="kw">disks</span>) <span class="kw">during</span> the prime generation<span class="kw">;</span> <span class="kw">this</span> gives the random number 148<span class="ex">disks</span>) <span class="ex">during</span> the prime generation<span class="kw">;</span> <span class="ex">this</span> gives the random number
151<span class="kw">generator</span> a better chance to gain enough entropy.</code></pre></div> 149<span class="ex">generator</span> a better chance to gain enough entropy.</code></pre></div>
152<h3 id="gpg---entropy-interlude">gpg - entropy interlude</h3> 150<h3 id="gpg---entropy-interlude">gpg - entropy interlude</h3>
153<p>At this point, you will:</p> 151<p>At this point, you will:</p>
154<ul> 152<ul>
@@ -156,19 +154,19 @@ Keys</a></li>
156<li>be asked to use your machine's input devices (mouse, keyboard, etc.) to generate random entropy; this step <em>may take some time</em></li> 154<li>be asked to use your machine's input devices (mouse, keyboard, etc.) to generate random entropy; this step <em>may take some time</em></li>
157</ul> 155</ul>
158<h3 id="gpg---key-creation-confirmation">gpg - key creation confirmation</h3> 156<h3 id="gpg---key-creation-confirmation">gpg - key creation confirmation</h3>
159<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="kw">gpg</span>: key A9D53A3E marked as ultimately trusted 157<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gpg</span>: key A9D53A3E marked as ultimately trusted
160<span class="kw">public</span> and secret key created and signed. 158<span class="ex">public</span> and secret key created and signed.
161 159
162<span class="kw">gpg</span>: checking the trustdb 160<span class="ex">gpg</span>: checking the trustdb
163<span class="kw">gpg</span>: 3 marginal(s) <span class="kw">needed</span>, 1 complete(s) <span class="kw">needed</span>, PGP trust model 161<span class="ex">gpg</span>: 3 marginal(s) <span class="ex">needed</span>, 1 complete(s) <span class="ex">needed</span>, PGP trust model
164<span class="kw">gpg</span>: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u 162<span class="ex">gpg</span>: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
165<span class="kw">pub</span> rsa2048/A9D53A3E 2015-07-31 163<span class="ex">pub</span> rsa2048/A9D53A3E 2015-07-31
166 <span class="kw">Key</span> fingerprint = AF2A 5381 E54B 2FD2 14C4 A9A3 0E35 ACA4 A9D5 3A3E 164 <span class="ex">Key</span> fingerprint = AF2A 5381 E54B 2FD2 14C4 A9A3 0E35 ACA4 A9D5 3A3E
167<span class="kw">uid</span> [ultimate] Marvin the Paranoid Android <span class="kw">&lt;</span>marvin@h2g2.net<span class="kw">&gt;</span>[](.html) 165<span class="ex">uid</span> [ultimate] Marvin the Paranoid Android <span class="op">&lt;</span>marvin@h2g2.net<span class="op">&gt;</span>[](.html)
168<span class="kw">sub</span> rsa2048/8C0EACF1 2015-07-31</code></pre></div> 166<span class="ex">sub</span> rsa2048/8C0EACF1 2015-07-31</code></pre></div>
169<h3 id="gpg---submit-your-public-key-to-a-pgp-server-optional">gpg - submit your public key to a PGP server (Optional)</h3> 167<h3 id="gpg---submit-your-public-key-to-a-pgp-server-optional">gpg - submit your public key to a PGP server (Optional)</h3>
170<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">gpg</span> --keyserver pgp.mit.edu --send-keys A9D53A3E 168<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">gpg</span> --keyserver pgp.mit.edu --send-keys A9D53A3E
171<span class="kw">gpg</span>: sending key A9D53A3E to hkp server pgp.mit.edu</code></pre></div> 169<span class="ex">gpg</span>: sending key A9D53A3E to hkp server pgp.mit.edu</code></pre></div>
172<h2 id="create-and-push-a-gpg-signed-tag">Create and push a GPG-signed tag</h2> 170<h2 id="create-and-push-a-gpg-signed-tag">Create and push a GPG-signed tag</h2>
173<p>See <a href="Release-Shaarli.html">Release Shaarli</a>.</p> 171<p>See <a href="Release-Shaarli.html">Release Shaarli</a>.</p>
174</body> 172</body>
diff --git a/doc/Home.html b/doc/Home.html
index 442503c5..970f547e 100644
--- a/doc/Home.html
+++ b/doc/Home.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Plugin-System.html b/doc/Plugin-System.html
index 37b26152..655536c6 100644
--- a/doc/Plugin-System.html
+++ b/doc/Plugin-System.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
diff --git a/doc/Plugins.html b/doc/Plugins.html
index e7df6aed..435a836f 100644
--- a/doc/Plugins.html
+++ b/doc/Plugins.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
diff --git a/doc/RSS-feeds.html b/doc/RSS-feeds.html
index 1b38e4e8..0f332b3d 100644
--- a/doc/RSS-feeds.html
+++ b/doc/RSS-feeds.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Release-Shaarli.html b/doc/Release-Shaarli.html
index cfaa663b..0d9fa3e1 100644
--- a/doc/Release-Shaarli.html
+++ b/doc/Release-Shaarli.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,14 +96,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="release-shaarli">Release Shaarli</h1> 102<h1 id="release-shaarli">Release Shaarli</h1>
105<p>See <a href="http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases">Git - Maintaining a project - Tagging your [](.html)<br /> 103<p>See <a href="http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases">Git - Maintaining a project - Tagging your [](.html)<br />
106releases</a>.</p> 104releases</a>.</p>
107<h3 id="prerequisites">Prerequisites</h3> 105<h2 id="prerequisites">Prerequisites</h2>
108<p>This guide assumes that you have:</p> 106<p>This guide assumes that you have:</p>
109<ul> 107<ul>
110<li>a GPG key matching your GitHub authentication credentials 108<li>a GPG key matching your GitHub authentication credentials
@@ -117,54 +115,106 @@ releases</a>.</p>
117<li><code>origin</code> pointing to your GitHub fork</li> 115<li><code>origin</code> pointing to your GitHub fork</li>
118<li><code>upstream</code> pointing to the main Shaarli repository</li> 116<li><code>upstream</code> pointing to the main Shaarli repository</li>
119</ul></li> 117</ul></li>
120<li>maintainer permissions on the main Shaarli repository (to push the signed tag)</li> 118<li>maintainer permissions on the main Shaarli repository, to:
121<li><a href="http://pandoc.org/">Pandoc</a> needs to be installed.<a href=".html"></a></li> 119<ul>
120<li>push the signed tag</li>
121<li>create a new release</li>
122</ul></li>
123<li><a href="https://getcomposer.org/">Composer</a> and <a href="http://pandoc.org/">Pandoc</a> need to be installed<a href=".html"></a></li>
124</ul>
125<h2 id="github-release-draft-and-changelog.md">GitHub release draft and <code>CHANGELOG.md</code></h2>
126<p>See <a href="http://keepachangelog.com/en/0.3.0/" class="uri">http://keepachangelog.com/en/0.3.0/</a> for changelog formatting.</p>
127<h3 id="github-release-draft">GitHub release draft</h3>
128<p>GitHub allows drafting the release note for the upcoming release, from the <a href="https://github.com/shaarli/Shaarli/releases">Releases</a> page. This way, the release note can be drafted while contributions are merged to <code>master</code>.<a href=".html"></a></p>
129<h3 id="changelog.md"><code>CHANGELOG.md</code></h3>
130<p>This file should contain the same information as the release note draft for the upcoming version.</p>
131<p>Update it to:</p>
132<ul>
133<li>add new entries (additions, fixes, etc.)</li>
134<li>mark the current version as released by setting its date and link</li>
135<li>add a new section for the future unreleased version</li>
122</ul> 136</ul>
137<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
138
139$ <span class="fu">nano</span> CHANGELOG.md
140
141[<span class="ex">...</span>][](.html)
142<span class="co">## vA.B.C - UNRELEASED</span>
143<span class="ex">TBA</span>
144
145<span class="co">## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)</span>
146[<span class="ex">...</span>][](.html)</code></pre></div>
147<h2 id="increment-the-version-code-create-and-push-a-signed-tag">Increment the version code, create and push a signed tag</h2>
123<h3 id="bump-shaarlis-version">Bump Shaarli's version</h3> 148<h3 id="bump-shaarlis-version">Bump Shaarli's version</h3>
124<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">cd</span> /path/to/shaarli 149<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
125 150
126<span class="co"># create a new branch</span> 151<span class="co"># create a new branch</span>
127$ <span class="kw">git</span> fetch upstream 152$ <span class="fu">git</span> fetch upstream
128$ <span class="kw">git</span> checkout upstream/master -b v0.5.0 153$ <span class="fu">git</span> checkout upstream/master -b v0.5.0
129 154
130<span class="co"># bump the version number</span> 155<span class="co"># bump the version number</span>
131$ <span class="kw">vim</span> index.php shaarli_version.php 156$ <span class="ex">vim</span> index.php shaarli_version.php
132 157
133<span class="co"># rebuild the documentation from the wiki</span> 158<span class="co"># rebuild the documentation from the wiki</span>
134$ <span class="kw">make</span> htmldoc 159$ <span class="fu">make</span> htmldoc
135 160
136<span class="co"># commit the changes</span> 161<span class="co"># commit the changes</span>
137$ <span class="kw">git</span> add index.php shaarli_version.php doc 162$ <span class="fu">git</span> add index.php shaarli_version.php doc
138$ <span class="kw">git</span> commit -s -m <span class="st">&quot;Bump version to v0.5.0&quot;</span> 163$ <span class="fu">git</span> commit -s -m <span class="st">&quot;Bump version to v0.5.0&quot;</span>
139 164
140<span class="co"># push the commit on your GitHub fork</span> 165<span class="co"># push the commit on your GitHub fork</span>
141$ <span class="kw">git</span> push origin v0.5.0</code></pre></div> 166$ <span class="fu">git</span> push origin v0.5.0</code></pre></div>
142<h3 id="create-and-merge-a-pull-request">Create and merge a Pull Request</h3> 167<h3 id="create-and-merge-a-pull-request">Create and merge a Pull Request</h3>
143<p>This one is pretty straightforward ;-)</p> 168<p>This one is pretty straightforward ;-)</p>
144<h3 id="create-and-push-a-signed-tag">Create and push a signed tag</h3> 169<h3 id="create-and-push-a-signed-tag">Create and push a signed tag</h3>
145<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># update your local copy</span> 170<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># update your local copy</span>
146$ <span class="kw">git</span> checkout master 171$ <span class="fu">git</span> checkout master
147$ <span class="kw">git</span> fetch upstream 172$ <span class="fu">git</span> fetch upstream
148$ <span class="kw">git</span> pull upstream master 173$ <span class="fu">git</span> pull upstream master
149 174
150<span class="co"># create a signed tag</span> 175<span class="co"># create a signed tag</span>
151$ <span class="kw">git</span> tag -s -m <span class="st">&quot;Release v0.5.0&quot;</span> v0.5.0 176$ <span class="fu">git</span> tag -s -m <span class="st">&quot;Release v0.5.0&quot;</span> v0.5.0
152 177
153<span class="co"># push it to &quot;upstream&quot;</span> 178<span class="co"># push it to &quot;upstream&quot;</span>
154$ <span class="kw">git</span> push --tags upstream</code></pre></div> 179$ <span class="fu">git</span> push --tags upstream</code></pre></div>
155<h3 id="verify-a-signed-tag">Verify a signed tag</h3> 180<h3 id="verify-a-signed-tag">Verify a signed tag</h3>
156<p><a href="https://github.com/shaarli/Shaarli/releases/tag/v0.5.0"><code>v0.5.0</code></a> is the first GPG-signed tag pushed on the Community Shaarli.<a href=".html"></a></p> 181<p><a href="https://github.com/shaarli/Shaarli/releases/tag/v0.5.0"><code>v0.5.0</code></a> is the first GPG-signed tag pushed on the Community Shaarli.<a href=".html"></a></p>
157<p>Let's have a look at its signature!</p> 182<p>Let's have a look at its signature!</p>
158<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">cd</span> /path/to/shaarli 183<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
159$ <span class="kw">git</span> fetch upstream 184$ <span class="fu">git</span> fetch upstream
160 185
161<span class="co"># get the SHA1 reference of the tag</span> 186<span class="co"># get the SHA1 reference of the tag</span>
162$ <span class="kw">git</span> show-ref tags/v0.5.0 187$ <span class="fu">git</span> show-ref tags/v0.5.0
163<span class="kw">f7762cf803f03f5caf4b8078359a63783d0090c1</span> refs/tags/v0.5.0 188<span class="ex">f7762cf803f03f5caf4b8078359a63783d0090c1</span> refs/tags/v0.5.0
164 189
165<span class="co"># verify the tag signature information</span> 190<span class="co"># verify the tag signature information</span>
166$ <span class="kw">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 191$ <span class="fu">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
167<span class="kw">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F 192<span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
168<span class="kw">gpg</span>: Good signature from <span class="st">&quot;VirtualTam &lt;virtualtam@flibidi.net&gt;&quot;</span> [ultimate][](.html)</code></pre></div> 193<span class="ex">gpg</span>: Good signature from <span class="st">&quot;VirtualTam &lt;virtualtam@flibidi.net&gt;&quot;</span> [ultimate][](.html)</code></pre></div>
194<h2 id="publish-the-github-release">Publish the GitHub release</h2>
195<h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3>
196<p>From the previously drafted release:</p>
197<ul>
198<li>edit the release notes (if needed)</li>
199<li>specify the appropriate Git tag</li>
200<li>publish the release</li>
201<li>profit!</li>
202</ul>
203<h3 id="generate-and-upload-all-in-one-release-archives">Generate and upload all-in-one release archives</h3>
204<p>Users with a shared hosting may have:</p>
205<ul>
206<li>no SSH access</li>
207<li>no possibility to install PHP packages or server extensions</li>
208<li>no possibility to run scripts</li>
209</ul>
210<p>To ease Shaarli installations, it is possible to generate and upload additional release archives,<br />
211that will contain Shaarli code plus all required third-party libraries:</p>
212<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">make</span> release_archive</code></pre></div>
213<p>This will create the following archives:</p>
214<ul>
215<li><code>shaarli-vX.Y.Z-full.tar</code></li>
216<li><code>shaarli-vX.Y.Z-full.zip</code></li>
217</ul>
218<p>The archives need to be manually uploaded on the previously created GitHub release.</p>
169</body> 219</body>
170</html> 220</html>
diff --git a/doc/Release-Shaarli.md b/doc/Release-Shaarli.md
index d5044fe9..556a96ee 100644
--- a/doc/Release-Shaarli.md
+++ b/doc/Release-Shaarli.md
@@ -2,7 +2,7 @@
2See [Git - Maintaining a project - Tagging your [](.html) 2See [Git - Maintaining a project - Tagging your [](.html)
3releases](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases). 3releases](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases).
4 4
5### Prerequisites 5## Prerequisites
6This guide assumes that you have: 6This guide assumes that you have:
7- a GPG key matching your GitHub authentication credentials 7- a GPG key matching your GitHub authentication credentials
8 - i.e., the email address identified by the GPG key is the same as the one in your `~/.gitconfig` 8 - i.e., the email address identified by the GPG key is the same as the one in your `~/.gitconfig`
@@ -10,9 +10,40 @@ This guide assumes that you have:
10- a local clone of your Shaarli fork, with the following remotes: 10- a local clone of your Shaarli fork, with the following remotes:
11 - `origin` pointing to your GitHub fork 11 - `origin` pointing to your GitHub fork
12 - `upstream` pointing to the main Shaarli repository 12 - `upstream` pointing to the main Shaarli repository
13- maintainer permissions on the main Shaarli repository (to push the signed tag) 13- maintainer permissions on the main Shaarli repository, to:
14- [Pandoc](http://pandoc.org/) needs to be installed.[](.html) 14 - push the signed tag
15 - create a new release
16- [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html)
15 17
18## GitHub release draft and `CHANGELOG.md`
19See http://keepachangelog.com/en/0.3.0/ for changelog formatting.
20
21### GitHub release draft
22GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`.[](.html)
23
24### `CHANGELOG.md`
25This file should contain the same information as the release note draft for the upcoming version.
26
27Update it to:
28- add new entries (additions, fixes, etc.)
29- mark the current version as released by setting its date and link
30- add a new section for the future unreleased version
31
32```bash
33$ cd /path/to/shaarli
34
35$ nano CHANGELOG.md
36
37[...][](.html)
38## vA.B.C - UNRELEASED
39TBA
40
41## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)
42[...][](.html)
43```
44
45
46## Increment the version code, create and push a signed tag
16### Bump Shaarli's version 47### Bump Shaarli's version
17```bash 48```bash
18$ cd /path/to/shaarli 49$ cd /path/to/shaarli
@@ -70,3 +101,30 @@ $ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
70gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F 101gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
71gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html) 102gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html)
72``` 103```
104
105## Publish the GitHub release
106### Create a GitHub release from a Git tag
107From the previously drafted release:
108- edit the release notes (if needed)
109- specify the appropriate Git tag
110- publish the release
111- profit!
112
113### Generate and upload all-in-one release archives
114Users with a shared hosting may have:
115- no SSH access
116- no possibility to install PHP packages or server extensions
117- no possibility to run scripts
118
119To ease Shaarli installations, it is possible to generate and upload additional release archives,
120that will contain Shaarli code plus all required third-party libraries:
121
122```bash
123$ make release_archive
124```
125
126This will create the following archives:
127- `shaarli-vX.Y.Z-full.tar`
128- `shaarli-vX.Y.Z-full.zip`
129
130The archives need to be manually uploaded on the previously created GitHub release.
diff --git a/doc/Security.html b/doc/Security.html
index b1969a4c..cec20590 100644
--- a/doc/Security.html
+++ b/doc/Security.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
diff --git a/doc/Server-configuration.html b/doc/Server-configuration.html
index 1d2276df..2f1c25b5 100644
--- a/doc/Server-configuration.html
+++ b/doc/Server-configuration.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -133,7 +131,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
133<p>See also <a href="https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&amp;q=label%3Aproxy+">proxy-related</a> issues.<a href=".html"></a></p> 131<p>See also <a href="https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&amp;q=label%3Aproxy+">proxy-related</a> issues.<a href=".html"></a></p>
134<h2 id="apache">Apache</h2> 132<h2 id="apache">Apache</h2>
135<h3 id="minimal">Minimal</h3> 133<h3 id="minimal">Minimal</h3>
136<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="ot"> *:80</span><span class="fu">&gt;</span> 134<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="at"> *:80</span><span class="fu">&gt;</span>
137 ServerName<span class="st"> shaarli.my-domain.org</span> 135 ServerName<span class="st"> shaarli.my-domain.org</span>
138 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span> 136 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span>
139<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div> 137<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
@@ -144,11 +142,11 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
144<li><a href="http://stackoverflow.com/q/176">Apache/PHP - error log per VirtualHost</a> (StackOverflow)<a href=".html"></a></li> 142<li><a href="http://stackoverflow.com/q/176">Apache/PHP - error log per VirtualHost</a> (StackOverflow)<a href=".html"></a></li>
145<li><a href="https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/">PHP: php_value vs php_admin_value and the use of php_flag explained</a><a href=".html"></a></li> 143<li><a href="https://ma.ttias.be/php-php_value-vs-php_admin_value-and-the-use-of-php_flag-explained/">PHP: php_value vs php_admin_value and the use of php_flag explained</a><a href=".html"></a></li>
146</ul> 144</ul>
147<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="ot"> *:80</span><span class="fu">&gt;</span> 145<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="at"> *:80</span><span class="fu">&gt;</span>
148 ServerName<span class="st"> shaarli.my-domain.org</span> 146 ServerName<span class="st"> shaarli.my-domain.org</span>
149 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span> 147 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span>
150 148
151 <span class="ot">LogLevel</span><span class="ch"> </span><span class="kw">warn</span> 149 <span class="ex">LogLevel</span><span class="ch"> </span><span class="kw">warn</span>
152 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span> 150 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span>
153 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span> 151 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
154 152
@@ -158,43 +156,46 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
158 php_value error_log /var/log/apache2/shaarli-php-error.log 156 php_value error_log /var/log/apache2/shaarli-php-error.log
159<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div> 157<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
160<h3 id="standard---keep-access-and-error-logs">Standard - Keep access and error logs</h3> 158<h3 id="standard---keep-access-and-error-logs">Standard - Keep access and error logs</h3>
161<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="ot"> *:80</span><span class="fu">&gt;</span> 159<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="at"> *:80</span><span class="fu">&gt;</span>
162 ServerName<span class="st"> shaarli.my-domain.org</span> 160 ServerName<span class="st"> shaarli.my-domain.org</span>
163 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span> 161 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span>
164 162
165 <span class="ot">LogLevel</span><span class="ch"> </span><span class="kw">warn</span> 163 <span class="ex">LogLevel</span><span class="ch"> </span><span class="kw">warn</span>
166 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span> 164 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span>
167 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span> 165 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
168<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div> 166<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
169<h3 id="paranoid---redirect-http-80-to-https-443">Paranoid - Redirect HTTP (:80) to HTTPS (:443)</h3> 167<h3 id="paranoid---redirect-http-80-to-https-443">Paranoid - Redirect HTTP (:80) to HTTPS (:443)</h3>
170<p>See <a href="https://wiki.mozilla.org/Security/Server_Side_TLS#Apache">Server-side TLS</a> (Mozilla).<a href=".html"></a></p> 168<p>See <a href="https://wiki.mozilla.org/Security/Server_Side_TLS#Apache">Server-side TLS</a> (Mozilla).<a href=".html"></a></p>
171<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="ot"> *:443</span><span class="fu">&gt;</span> 169<div class="sourceCode"><pre class="sourceCode apache"><code class="sourceCode apache"><span class="fu">&lt;VirtualHost</span><span class="at"> *:443</span><span class="fu">&gt;</span>
172 ServerName<span class="st"> shaarli.my-domain.org</span> 170 ServerName<span class="st"> shaarli.my-domain.org</span>
173 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span> 171 DocumentRoot<span class="st"> /absolute/path/to/shaarli/</span>
174 172
175 <span class="ot">SSLEngine</span><span class="ch"> </span><span class="kw">on</span> 173 <span class="ex">SSLEngine</span><span class="ch"> </span><span class="kw">on</span>
176 SSLCertificateFile<span class="st"> /absolute/path/to/the/website/certificate.pem</span> 174 SSLCertificateFile<span class="st"> /absolute/path/to/the/website/certificate.pem</span>
177 SSLCertificateKeyFile<span class="st"> /absolute/path/to/the/website/key.key</span> 175 SSLCertificateKeyFile<span class="st"> /absolute/path/to/the/website/key.key</span>
178 176
179 <span class="fu">&lt;Directory</span><span class="ot"> /absolute/path/to/shaarli/</span><span class="fu">&gt;</span> 177 <span class="fu">&lt;Directory</span><span class="at"> /absolute/path/to/shaarli/</span><span class="fu">&gt;</span>
180 <span class="ot">AllowOverride</span><span class="ch"> </span><span class="kw">All</span> 178 <span class="ex">AllowOverride</span><span class="ch"> </span><span class="kw">All</span>
181 <span class="ot">Options</span><span class="ch"> </span><span class="kw">Indexes</span><span class="ch"> </span><span class="kw">FollowSymLinks</span><span class="ch"> </span><span class="kw">MultiViews</span> 179 <span class="ex">Options</span><span class="ch"> </span><span class="kw">Indexes</span><span class="ch"> </span><span class="kw">FollowSymLinks</span><span class="ch"> </span><span class="kw">MultiViews</span>
182 <span class="ot">Order</span><span class="ch"> </span><span class="kw">allow,deny</span> 180 <span class="ex">Order</span><span class="ch"> </span><span class="kw">allow,deny</span>
183 allow<span class="st"> from all</span> 181 allow<span class="st"> from all</span>
184 <span class="fu">&lt;/Directory&gt;</span> 182 <span class="fu">&lt;/Directory&gt;</span>
185 183
186 <span class="ot">LogLevel</span><span class="ch"> </span><span class="kw">warn</span> 184 <span class="ex">LogLevel</span><span class="ch"> </span><span class="kw">warn</span>
187 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span> 185 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span>
188 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span> 186 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
189<span class="fu">&lt;/VirtualHost&gt;</span> 187<span class="fu">&lt;/VirtualHost&gt;</span>
190<span class="fu">&lt;VirtualHost</span><span class="ot"> *:80</span><span class="fu">&gt;</span> 188<span class="fu">&lt;VirtualHost</span><span class="at"> *:80</span><span class="fu">&gt;</span>
191 ServerName<span class="st"> shaarli.my-domain.org</span> 189 ServerName<span class="st"> shaarli.my-domain.org</span>
192 Redirect<span class="st"> 301 / https://shaarli.my-domain.org</span> 190 Redirect<span class="st"> 301 / https://shaarli.my-domain.org</span>
193 191
194 <span class="ot">LogLevel</span><span class="ch"> </span><span class="kw">warn</span> 192 <span class="ex">LogLevel</span><span class="ch"> </span><span class="kw">warn</span>
195 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span> 193 ErrorLog<span class="st"> /var/log/apache2/shaarli-error.log</span>
196 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span> 194 CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
197<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div> 195<span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
196<h3 id="htaccess">.htaccess</h3>
197<p>Shaarli use <code>.htaccess</code> Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive <code>AllowOverride All</code> in your virtual host configuration for them to work.</p>
198<p><strong>Warning</strong>: If you use Apache 2.2 or lower, you need <a href="https://httpd.apache.org/docs/current/mod/mod_version.html">mod_version</a> to be installed and enabled.<a href=".html"></a></p>
198<h2 id="lighthttpd">LightHttpd</h2> 199<h2 id="lighthttpd">LightHttpd</h2>
199<h2 id="nginx">Nginx</h2> 200<h2 id="nginx">Nginx</h2>
200<h3 id="foreword">Foreword</h3> 201<h3 id="foreword">Foreword</h3>
@@ -235,7 +236,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
235<li>files may be located in a user's home directory</li> 236<li>files may be located in a user's home directory</li>
236<li>in this case, make sure both Nginx and PHP-FPM are running as the local user/group!</li> 237<li>in this case, make sure both Nginx and PHP-FPM are running as the local user/group!</li>
237</ul> 238</ul>
238<p>For all following examples, a development configuration will be used:</p> 239<p>For all following configuration examples, this user/group pair will be used:</p>
239<ul> 240<ul>
240<li><code>user:group = john:users</code>,</li> 241<li><code>user:group = john:users</code>,</li>
241</ul> 242</ul>
@@ -253,6 +254,24 @@ user john users;
253http { 254http {
254 [...][](.html) 255 [...][](.html)
255}</code></pre> 256}</code></pre>
257<h3 id="optional-increase-the-maximum-file-upload-size">(Optional) Increase the maximum file upload size</h3>
258<p>Some bookmark dumps generated by web browsers can be <em>huge</em> due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.</p>
259<p>To increase upload size, you will need to modify both nginx and PHP configuration:</p>
260<pre class="nginx"><code># /etc/nginx/nginx.conf
261
262http {
263 [...][](.html)
264
265 client_max_body_size 10m;
266
267 [...][](.html)
268}</code></pre>
269<div class="sourceCode"><pre class="sourceCode ini"><code class="sourceCode ini"><span class="co"># /etc/php5/fpm/php.ini</span>
270
271<span class="kw">[...][]</span><span class="dt">(.html)</span>
272<span class="dt">post_max_size </span><span class="ot">=</span><span class="st"> 10M</span>
273<span class="kw">[...][]</span><span class="dt">(.html)</span>
274<span class="dt">upload_max_filesize </span><span class="ot">=</span><span class="st"> 10M</span></code></pre></div>
256<h3 id="minimal-1">Minimal</h3> 275<h3 id="minimal-1">Minimal</h3>
257<p><em>WARNING: Use for development only!</em></p> 276<p><em>WARNING: Use for development only!</em></p>
258<pre class="nginx"><code>user john users; 277<pre class="nginx"><code>user john users;
@@ -352,6 +371,11 @@ http {
352 error_log /var/log/nginx/shaarli.error.log; 371 error_log /var/log/nginx/shaarli.error.log;
353 } 372 }
354 373
374 location = /shaarli/favicon.ico {
375 # serve the Shaarli favicon from its custom location
376 alias /var/www/shaarli/images/favicon.ico;
377 }
378
355 include deny.conf; 379 include deny.conf;
356 include static_assets.conf; 380 include static_assets.conf;
357 include php.conf; 381 include php.conf;
@@ -405,15 +429,15 @@ http {
405 error_log /var/log/nginx/shaarli.error.log; 429 error_log /var/log/nginx/shaarli.error.log;
406 } 430 }
407 431
432 location = /shaarli/favicon.ico {
433 # serve the Shaarli favicon from its custom location
434 alias /var/www/shaarli/images/favicon.ico;
435 }
436
408 include deny.conf; 437 include deny.conf;
409 include static_assets.conf; 438 include static_assets.conf;
410 include php.conf; 439 include php.conf;
411 } 440 }
412}</code></pre> 441}</code></pre>
413<h2 id="restricting-search-engines-and-web-crawler-traffic">Restricting search engines and web crawler traffic</h2>
414<p>Creating a <code>robots.txt</code> witht he following contents at the root of your Shaarli installation will prevent &quot;honest&quot; web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.</p>
415<pre><code>User-agent: *
416Disallow: /</code></pre>
417<p>See: <a href="http://www.robotstxt.org/" class="uri">http://www.robotstxt.org/</a>, <a href="http://www.robotstxt.org/robotstxt.html" class="uri">http://www.robotstxt.org/robotstxt.html</a>, <a href="http://www.robotstxt.org/meta.html" class="uri">http://www.robotstxt.org/meta.html</a></p>
418</body> 442</body>
419</html> 443</html>
diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md
index fd98a608..df10feb2 100644
--- a/doc/Server-configuration.md
+++ b/doc/Server-configuration.md
@@ -102,6 +102,12 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache)
102</VirtualHost> 102</VirtualHost>
103``` 103```
104 104
105### .htaccess
106
107Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work.
108
109**Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html)
110
105## LightHttpd 111## LightHttpd
106 112
107## Nginx 113## Nginx
@@ -136,7 +142,7 @@ On a development server:
136- files may be located in a user's home directory 142- files may be located in a user's home directory
137- in this case, make sure both Nginx and PHP-FPM are running as the local user/group! 143- in this case, make sure both Nginx and PHP-FPM are running as the local user/group!
138 144
139For all following examples, a development configuration will be used: 145For all following configuration examples, this user/group pair will be used:
140- `user:group = john:users`, 146- `user:group = john:users`,
141 147
142which corresponds to the following service configuration: 148which corresponds to the following service configuration:
@@ -160,6 +166,32 @@ http {
160} 166}
161``` 167```
162 168
169### (Optional) Increase the maximum file upload size
170Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
171
172To increase upload size, you will need to modify both nginx and PHP configuration:
173
174```nginx
175# /etc/nginx/nginx.conf
176
177http {
178 [...][](.html)
179
180 client_max_body_size 10m;
181
182 [...][](.html)
183}
184```
185
186```ini
187# /etc/php5/fpm/php.ini
188
189[...][](.html)
190post_max_size = 10M
191[...][](.html)
192upload_max_filesize = 10M
193```
194
163### Minimal 195### Minimal
164_WARNING: Use for development only!_ 196_WARNING: Use for development only!_
165 197
@@ -271,6 +303,11 @@ http {
271 error_log /var/log/nginx/shaarli.error.log; 303 error_log /var/log/nginx/shaarli.error.log;
272 } 304 }
273 305
306 location = /shaarli/favicon.ico {
307 # serve the Shaarli favicon from its custom location
308 alias /var/www/shaarli/images/favicon.ico;
309 }
310
274 include deny.conf; 311 include deny.conf;
275 include static_assets.conf; 312 include static_assets.conf;
276 include php.conf; 313 include php.conf;
@@ -328,21 +365,14 @@ http {
328 error_log /var/log/nginx/shaarli.error.log; 365 error_log /var/log/nginx/shaarli.error.log;
329 } 366 }
330 367
368 location = /shaarli/favicon.ico {
369 # serve the Shaarli favicon from its custom location
370 alias /var/www/shaarli/images/favicon.ico;
371 }
372
331 include deny.conf; 373 include deny.conf;
332 include static_assets.conf; 374 include static_assets.conf;
333 include php.conf; 375 include php.conf;
334 } 376 }
335} 377}
336``` 378```
337
338## Restricting search engines and web crawler traffic
339
340Creating a `robots.txt` witht he following contents at the root of your Shaarli installation will prevent "honest" web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.
341
342```
343User-agent: *
344Disallow: /
345```
346
347See: http://www.robotstxt.org/, http://www.robotstxt.org/robotstxt.html, http://www.robotstxt.org/meta.html
348
diff --git a/doc/Server-requirements.html b/doc/Server-requirements.html
index 8e4deeb8..2c2545bb 100644
--- a/doc/Server-requirements.html
+++ b/doc/Server-requirements.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
@@ -96,18 +94,18 @@
96</tr> 94</tr>
97<tr class="odd"> 95<tr class="odd">
98<td style="text-align: center;">5.5</td> 96<td style="text-align: center;">5.5</td>
99<td style="text-align: center;">Supported</td> 97<td style="text-align: center;">EOL: 2016-07-10</td>
100<td style="text-align: center;">✅</td> 98<td style="text-align: center;">✅</td>
101</tr> 99</tr>
102<tr class="even"> 100<tr class="even">
103<td style="text-align: center;">5.4</td> 101<td style="text-align: center;">5.4</td>
104<td style="text-align: center;">EOL: 2015-09-14</td> 102<td style="text-align: center;">EOL: 2015-09-14</td>
105<td style="text-align: center;">✅</td> 103<td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td>
106</tr> 104</tr>
107<tr class="odd"> 105<tr class="odd">
108<td style="text-align: center;">5.3</td> 106<td style="text-align: center;">5.3</td>
109<td style="text-align: center;">EOL: 2014-08-14</td> 107<td style="text-align: center;">EOL: 2014-08-14</td>
110<td style="text-align: center;">✅</td> 108<td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td>
111</tr> 109</tr>
112</tbody> 110</tbody>
113</table> 111</table>
@@ -115,6 +113,25 @@
115<ul> 113<ul>
116<li><a href="https://github.com/shaarli/Shaarli/blob/master/.travis.yml">Travis configuration</a><a href=".html"></a></li> 114<li><a href="https://github.com/shaarli/Shaarli/blob/master/.travis.yml">Travis configuration</a><a href=".html"></a></li>
117</ul> 115</ul>
116<h3 id="dependency-management">Dependency management</h3>
117<p>Starting with Shaarli <code>v0.8.x</code>, <a href="https://getcomposer.org/">Composer</a> is used to resolve,<a href=".html"></a><br />
118download and install third-party PHP dependencies.</p>
119<table>
120<thead>
121<tr class="header">
122<th>Library</th>
123<th style="text-align: center;">Required?</th>
124<th>Usage</th>
125</tr>
126</thead>
127<tbody>
128<tr class="odd">
129<td><a href="https://packagist.org/packages/shaarli/netscape-bookmark-parser"><code>shaarli/netscape-bookmark-parser</code></a></td>
130<td style="text-align: center;">All</td>
131<td>Import bookmarks from Netscape files<a href=".html"></a></td>
132</tr>
133</tbody>
134</table>
118<h3 id="extensions">Extensions</h3> 135<h3 id="extensions">Extensions</h3>
119<table style="width:19%;"> 136<table style="width:19%;">
120<colgroup> 137<colgroup>
@@ -142,13 +159,18 @@
142</tr> 159</tr>
143<tr class="odd"> 160<tr class="odd">
144<td><a href="http://php.net/manual/en/book.image.php"><code>php-gd</code></a></td> 161<td><a href="http://php.net/manual/en/book.image.php"><code>php-gd</code></a></td>
145<td style="text-align: center;">-</td> 162<td style="text-align: center;">optional</td>
146<td>thumbnail resizing<a href=".html"></a></td> 163<td>thumbnail resizing<a href=".html"></a></td>
147</tr> 164</tr>
148<tr class="even"> 165<tr class="even">
149<td><a href="http://php.net/manual/fr/book.intl.php"><code>php-intl</code></a></td> 166<td><a href="http://php.net/manual/en/book.intl.php"><code>php-intl</code></a></td>
150<td style="text-align: center;">Optional</td> 167<td style="text-align: center;">optional</td>
151<td>Tag cloud intelligent sorting (eg. <code>e-&gt;è-&gt;f</code>)<a href=".html"></a></td> 168<td>localized text sorting (e.g. <code>e-&gt;è-&gt;f</code>)<a href=".html"></a></td>
169</tr>
170<tr class="odd">
171<td><a href="http://php.net/manual/en/book.curl.php"><code>php-curl</code></a></td>
172<td style="text-align: center;">optional</td>
173<td>using cURL for fetching webpages and thumbnails in a more robust way<a href=".html"></a></td>
152</tr> 174</tr>
153</tbody> 175</tbody>
154</table> 176</table>
diff --git a/doc/Server-requirements.md b/doc/Server-requirements.md
index 7955fddf..4962193e 100644
--- a/doc/Server-requirements.md
+++ b/doc/Server-requirements.md
@@ -12,17 +12,26 @@ Version | Status | Shaarli compatibility
12:---:|:---:|:---: 12:---:|:---:|:---:
137.0 | Supported | :white_check_mark: 137.0 | Supported | :white_check_mark:
145.6 | Supported | :white_check_mark: 145.6 | Supported | :white_check_mark:
155.5 | Supported | :white_check_mark: 155.5 | EOL: 2016-07-10 | :white_check_mark:
165.4 | EOL: 2015-09-14 | :white_check_mark: 165.4 | EOL: 2015-09-14 | :white_check_mark: (up to Shaarli 0.8.x)
175.3 | EOL: 2014-08-14 | :white_check_mark: 175.3 | EOL: 2014-08-14 | :white_check_mark: (up to Shaarli 0.8.x)
18 18
19See also: 19See also:
20- [Travis configuration](https://github.com/shaarli/Shaarli/blob/master/.travis.yml)[](.html) 20- [Travis configuration](https://github.com/shaarli/Shaarli/blob/master/.travis.yml)[](.html)
21 21
22### Dependency management
23Starting with Shaarli `v0.8.x`, [Composer](https://getcomposer.org/) is used to resolve,[](.html)
24download and install third-party PHP dependencies.
25
26Library | Required? | Usage
27---|:---:|---
28[`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html)
29
22### Extensions 30### Extensions
23Extension | Required? | Usage 31Extension | Required? | Usage
24---|:---:|--- 32---|:---:|---
25[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS[](.html) 33[`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS[](.html)
26[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support[](.html) 34[`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support[](.html)
27[`php-gd`](http://php.net/manual/en/book.image.php) | - | thumbnail resizing[](.html) 35[`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing[](.html)
28[`php-intl`](http://php.net/manual/fr/book.intl.php) | Optional | Tag cloud intelligent sorting (eg. `e->è->f`)[](.html) 36[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)[](.html)
37[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way[](.html)
diff --git a/doc/Server-security.html b/doc/Server-security.html
index 6b44a133..3551deff 100644
--- a/doc/Server-security.html
+++ b/doc/Server-security.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -118,11 +116,11 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
118</ul> 116</ul>
119<h3 id="locate-.ini-files">Locate .ini files</h3> 117<h3 id="locate-.ini-files">Locate .ini files</h3>
120<h4 id="console-environment">Console environment</h4> 118<h4 id="console-environment">Console environment</h4>
121<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">php</span> --ini 119<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">php</span> --ini
122<span class="kw">Configuration</span> File (php.ini) <span class="kw">Path</span>: /etc/php 120<span class="ex">Configuration</span> File (php.ini) <span class="ex">Path</span>: /etc/php
123<span class="kw">Loaded</span> Configuration File: /etc/php/php.ini 121<span class="ex">Loaded</span> Configuration File: /etc/php/php.ini
124<span class="kw">Scan</span> for additional .ini files in: /etc/php/conf.d 122<span class="ex">Scan</span> for additional .ini files in: /etc/php/conf.d
125<span class="kw">Additional</span> .ini files parsed: /etc/php/conf.d/xdebug.ini</code></pre></div> 123<span class="ex">Additional</span> .ini files parsed: /etc/php/conf.d/xdebug.ini</code></pre></div>
126<h4 id="server-environment">Server environment</h4> 124<h4 id="server-environment">Server environment</h4>
127<ul> 125<ul>
128<li>create a <code>phpinfo.php</code> script located in a path supported by the web server, e.g. 126<li>create a <code>phpinfo.php</code> script located in a path supported by the web server, e.g.
@@ -161,5 +159,15 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
161<span class="kw">[Definition][]</span><span class="dt">(.html)</span> 159<span class="kw">[Definition][]</span><span class="dt">(.html)</span>
162<span class="dt">failregex </span><span class="ot">=</span><span class="st"> \s-\s&lt;HOST&gt;\s-\sLogin failed for user.*$</span> 160<span class="dt">failregex </span><span class="ot">=</span><span class="st"> \s-\s&lt;HOST&gt;\s-\sLogin failed for user.*$</span>
163<span class="dt">ignoreregex </span><span class="ot">=</span><span class="st"> </span></code></pre></div> 161<span class="dt">ignoreregex </span><span class="ot">=</span><span class="st"> </span></code></pre></div>
162<h2 id="robots---restricting-search-engines-and-web-crawler-traffic">Robots - Restricting search engines and web crawler traffic</h2>
163<p>Creating a <code>robots.txt</code> with the following contents at the root of your Shaarli installation will prevent <em>honest</em> web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.</p>
164<pre><code>User-agent: *
165Disallow: /</code></pre>
166<p>See:</p>
167<ul>
168<li><a href="http://www.robotstxt.org/" class="uri">http://www.robotstxt.org/</a></li>
169<li><a href="http://www.robotstxt.org/robotstxt.html" class="uri">http://www.robotstxt.org/robotstxt.html</a></li>
170<li><a href="http://www.robotstxt.org/meta.html" class="uri">http://www.robotstxt.org/meta.html</a></li>
171</ul>
164</body> 172</body>
165</html> 173</html>
diff --git a/doc/Server-security.md b/doc/Server-security.md
index 0d16e284..50549a21 100644
--- a/doc/Server-security.md
+++ b/doc/Server-security.md
@@ -58,3 +58,17 @@ before = common.conf
58failregex = \s-\s<HOST>\s-\sLogin failed for user.*$ 58failregex = \s-\s<HOST>\s-\sLogin failed for user.*$
59ignoreregex = 59ignoreregex =
60``` 60```
61
62## Robots - Restricting search engines and web crawler traffic
63
64Creating a `robots.txt` with the following contents at the root of your Shaarli installation will prevent _honest_ web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.
65
66```
67User-agent: *
68Disallow: /
69```
70
71See:
72- http://www.robotstxt.org/
73- http://www.robotstxt.org/robotstxt.html
74- http://www.robotstxt.org/meta.html
diff --git a/doc/Shaarli-configuration.html b/doc/Shaarli-configuration.html
index 74947578..6d717c65 100644
--- a/doc/Shaarli-configuration.html
+++ b/doc/Shaarli-configuration.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,18 +96,19 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
104<h1 id="shaarli-configuration">Shaarli configuration</h1> 102<h1 id="shaarli-configuration">Shaarli configuration</h1>
103<h1 id="shaarli-configuration-1">Shaarli configuration</h1>
105<h2 id="foreword">Foreword</h2> 104<h2 id="foreword">Foreword</h2>
106<p><strong>Do not edit configuration options in index.php! Your changes would be lost.</strong></p> 105<p><strong>Do not edit configuration options in index.php! Your changes would be lost.</strong></p>
107<p>Once your Shaarli instance is installed, the file <code>data/config.php</code> is generated:</p> 106<p>Once your Shaarli instance is installed, the file <code>data/config.json.php</code> is generated:</p>
108<ul> 107<ul>
109<li>it contains all settings, and can be edited to customize values</li> 108<li>it contains all settings in JSON format, and can be edited to customize values</li>
110<li>it defines which <a href="Plugin-System">plugins</a> are enabled<a href=".html"></a></li> 109<li>it defines which <a href="Plugin-System">plugins</a> are enabled<a href="(.html).html">(.html)</a></li>
111<li>its values override those defined in <code>index.php</code></li> 110<li>its values override those defined in <code>index.php</code></li>
111<li>it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration</li>
112</ul> 112</ul>
113<h2 id="file-and-directory-permissions">File and directory permissions</h2> 113<h2 id="file-and-directory-permissions">File and directory permissions</h2>
114<p>The server process running Shaarli must have:</p> 114<p>The server process running Shaarli must have:</p>
@@ -141,120 +141,155 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
141<li>unzip Shaarli in the default web server location (usually <code>/var/www/</code>) and set the web server user as the owner</li> 141<li>unzip Shaarli in the default web server location (usually <code>/var/www/</code>) and set the web server user as the owner</li>
142<li>put users in the same group as the web server, and set the appropriate access rights</li> 142<li>put users in the same group as the web server, and set the appropriate access rights</li>
143</ul></li> 143</ul></li>
144<li>if you have a domain / subdomain to serve Shaarli, <a href="Server-configuration">configure the server</a> accordingly<a href=".html"></a></li> 144<li>if you have a domain / subdomain to serve Shaarli, <a href="Server-configuration">configure the server</a> accordingly<a href="(.html).html">(.html)</a></li>
145</ul> 145</ul>
146<h2 id="example-dataconfig.php">Example <code>data/config.php</code></h2> 146<h2 id="configuration">Configuration</h2>
147<p>See also <a href="Plugin-System.html">Plugin System</a>.</p> 147<p>In <code>data/config.json.php</code>.</p>
148<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">&lt;?php</span> 148<p>See also <a href="Plugin-System.html">Plugin System</a>.<a href=".html"></a></p>
149<span class="co">// User login</span> 149<h3 id="credentials">Credentials</h3>
150<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;login&#39;</span><span class="ot">]</span> = <span class="st">&#39;&lt;login&gt;&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 150<blockquote>
151 151<p>You shouldn't edit those.</p>
152<span class="co">// User password hash</span> 152</blockquote>
153<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;hash&#39;</span><span class="ot">]</span> = <span class="st">&#39;200c452da46c2f889e5e48c49ef044bcacdcb095&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 153<p><strong>login</strong>: Login username.<br />
154 154<strong>hash</strong>: Generated password hash.<br />
155<span class="co">// Password salt</span> 155<strong>salt</strong>: Password salt.</p>
156<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;salt&#39;</span><span class="ot">]</span> = <span class="st">&#39;13b654102321576033d8473b63a275a1bf94c0f0&#39;</span><span class="ot">;</span> <span class="ot">[](</span>.html<span class="ot">)</span> 156<h3 id="general">General</h3>
157 157<p><strong>title</strong>: Shaarli's instance title.<br />
158<span class="co">// Local timezone</span> 158<strong>header_link</strong>: Link to the homepage.<br />
159<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;timezone&#39;</span><span class="ot">]</span> = <span class="st">&#39;Africa/Abidjan&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 159<strong>links_per_page</strong>: Number of shaares displayed per page.<br />
160<span class="fu">date_default_timezone_set</span><span class="ot">(</span><span class="st">&#39;Africa/Abidjan&#39;</span><span class="ot">);</span> 160<strong>timezone</strong>: See <a href="http://php.net/manual/en/timezones.php">the list of supported timezones</a>. <a href=".html"></a><br />
161 161<strong>enabled_plugins</strong>: List of enabled plugins.</p>
162<span class="co">// Shaarli title</span> 162<h3 id="security">Security</h3>
163<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;title&#39;</span><span class="ot">]</span> = <span class="st">&#39;My Little Shaarly&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 163<p><strong>session_protection_disabled</strong>: Disable session cookie hijacking protection (not recommended).<br />
164 164It might be useful if your IP adress often changes.<br />
165<span class="co">// Link the Shaarli title points to</span> 165<strong>ban_after</strong>: Failed login attempts before being IP banned.<br />
166<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;titleLink&#39;</span><span class="ot">]</span> = <span class="st">&#39;?&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 166<strong>ban_duration</strong>: IP ban duration in seconds.<br />
167 167<strong>open_shaarli</strong>: Anyone can add a new link while logged out if enabled.<br />
168<span class="co">// HTTP referer redirector</span> 168<strong>trusted_proxies</strong>: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy.</p>
169<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;redirector&#39;</span><span class="ot">]</span> = <span class="st">&#39;&#39;</span><span class="ot">;[](</span>.html<span class="ot">)</span> 169<h3 id="resources">Resources</h3>
170 170<p><strong>data_dir</strong>: Data directory.<br />
171<span class="co">// Disable session hijacking</span> 171<strong>datastore</strong>: Shaarli's links database file path.<br />
172<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;disablesessionprotection&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;</span> <span class="ot">[](</span>.html<span class="ot">)</span> 172<strong>updates</strong>: File path for the ran updates file.<br />
173 173<strong>log</strong>: Log file path.<br />
174<span class="co">// Whether new links are private by default</span> 174<strong>update_check</strong>: Last update check file path.<br />
175<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;privateLinkByDefault&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;[](</span>.html<span class="ot">)</span> 175<strong>raintpl_tpl</strong>: Templates directory.<br />
176 176<strong>raintpl_tmp</strong>: Template engine cache directory.<br />
177<span class="co">// Enabled plugins</span> 177<strong>thumbnails_cache</strong>: Thumbnails cache directory.<br />
178<span class="co">// Note: each plugin may provide further settings through its own &quot;config.php&quot;</span> 178<strong>page_cache</strong>: Shaarli's internal cache directory.<br />
179<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ENABLED_PLUGINS&#39;</span><span class="ot">]</span> = <span class="fu">array</span><span class="ot">(</span><span class="st">&#39;addlink_toolbar&#39;</span><span class="ot">,</span> <span class="st">&#39;qrcode&#39;</span><span class="ot">);](</span><span class="st">&#39;ENABLED_PLUGINS&#39;</span><span class="ot">]</span>-=-<span class="fu">array</span><span class="ot">(</span><span class="st">&#39;addlink_toolbar&#39;</span><span class="ot">,</span>-<span class="st">&#39;qrcode&#39;</span><span class="ot">);</span>.html<span class="ot">)</span> 179<strong>ban_file</strong>: Banned IP file path.</p>
180 180<h3 id="updates">Updates</h3>
181<span class="co">// Subdirectory where Shaarli stores its data files.</span> 181<p><strong>check_updates</strong>: Enable or disable update check to the git repository.<br />
182<span class="co">// You can change it for better security.</span> 182<strong>check_updates_branch</strong>: Git branch used to check updates (e.g. <code>stable</code> or <code>master</code>).<br />
183<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;DATADIR&#39;</span><span class="ot">]</span> = <span class="st">&#39;data&#39;</span><span class="ot">;](</span><span class="st">&#39;DATADIR&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;data&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 183<strong>check_updates_interval</strong>: Look for new version every N seconds (default: every day).</p>
184 184<h3 id="privacy">Privacy</h3>
185<span class="co">// File used to store settings</span> 185<p><strong>default_private_links</strong>: Check the private checkbox by default for every new link.<br />
186<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;CONFIG_FILE&#39;</span><span class="ot">]</span> = <span class="st">&#39;data/config.php&#39;</span><span class="ot">;](</span><span class="st">&#39;CONFIG_FILE&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;data/config.php&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 186<strong>hide_public_links</strong>: All links are hidden while logged out.<br />
187 187<strong>hide_timestamps</strong>: Timestamps are hidden.</p>
188<span class="co">// File containing the link database</span> 188<h3 id="feed">Feed</h3>
189<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;DATASTORE&#39;</span><span class="ot">]</span> = <span class="st">&#39;data/datastore.php&#39;</span><span class="ot">;](</span><span class="st">&#39;DATASTORE&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;data/datastore.php&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 189<p><strong>rss_permalinks</strong>: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL.<br />
190 190<strong>show_atom</strong>: Display ATOM feed button.</p>
191<span class="co">// Number of links displayed per page</span> 191<h3 id="thumbnail">Thumbnail</h3>
192<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;LINKS_PER_PAGE&#39;</span><span class="ot">]</span> = <span class="dv">20</span><span class="ot">;](</span><span class="st">&#39;LINKS_PER_PAGE&#39;</span><span class="ot">]</span>-=-<span class="dv">20</span><span class="ot">;</span>.html<span class="ot">)</span> 192<p><strong>enable_thumbnails</strong>: Enable or disable thumbnail display.<br />
193 193<strong>enable_localcache</strong>: Enable or disable local cache.</p>
194<span class="co">// File recording failed login attempts and IP bans</span> 194<h3 id="redirector">Redirector</h3>
195<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;IPBANS_FILENAME&#39;</span><span class="ot">]</span> = <span class="st">&#39;data/ipbans.php&#39;</span><span class="ot">;](</span><span class="st">&#39;IPBANS_FILENAME&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;data/ipbans.php&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 195<p><strong>url</strong>: Redirector URL, such as <code>anonym.to</code>.<br />
196 196<strong>encode_url</strong>: Enable this if the redirector needs encoded URL to work properly.</p>
197<span class="co">// Failed login attempts before being banned</span> 197<h2 id="configuration-file-example">Configuration file example</h2>
198<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;BAN_AFTER&#39;</span><span class="ot">]</span> = <span class="dv">4</span><span class="ot">;](</span><span class="st">&#39;BAN_AFTER&#39;</span><span class="ot">]</span>-=-<span class="dv">4</span><span class="ot">;</span>.html<span class="ot">)</span> 198<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="er">&lt;?php</span> <span class="er">/*</span>
199 199<span class="fu">{</span>
200<span class="co">// Duration of an IP ban, in seconds (30 minutes)</span> 200 <span class="dt">&quot;credentials&quot;</span><span class="fu">:</span> <span class="fu">{</span>
201<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;BAN_DURATION&#39;</span><span class="ot">]</span> = <span class="dv">1800</span><span class="ot">;](</span><span class="st">&#39;BAN_DURATION&#39;</span><span class="ot">]</span>-=-<span class="dv">1800</span><span class="ot">;</span>.html<span class="ot">)</span> 201 <span class="dt">&quot;login&quot;</span><span class="fu">:</span> <span class="st">&quot;&lt;login&gt;&quot;</span><span class="fu">,</span>
202 202 <span class="dt">&quot;hash&quot;</span><span class="fu">:</span> <span class="st">&quot;&lt;password hash&gt;&quot;</span><span class="fu">,</span>
203<span class="co">// If set to true, everyone will be able to add, edit and remove links,</span> 203 <span class="dt">&quot;salt&quot;</span><span class="fu">:</span> <span class="st">&quot;&lt;password salt&gt;&quot;</span>
204<span class="co">// as well as change configuration</span> 204 <span class="fu">},</span>
205<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;OPEN_SHAARLI&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;](</span><span class="st">&#39;OPEN_SHAARLI&#39;</span><span class="ot">]</span>-=-<span class="kw">false</span><span class="ot">;</span>.html<span class="ot">)</span> 205 <span class="dt">&quot;security&quot;</span><span class="fu">:</span> <span class="fu">{</span>
206 206 <span class="dt">&quot;ban_after&quot;</span><span class="fu">:</span> <span class="dv">4</span><span class="fu">,</span>
207<span class="co">// Do not show link timestamps</span> 207 <span class="dt">&quot;session_protection_disabled&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
208<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;HIDE_TIMESTAMPS&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;](</span><span class="st">&#39;HIDE_TIMESTAMPS&#39;</span><span class="ot">]</span>-=-<span class="kw">false</span><span class="ot">;</span>.html<span class="ot">)</span> 208 <span class="dt">&quot;ban_duration&quot;</span><span class="fu">:</span> <span class="dv">1800</span><span class="fu">,</span>
209 209 <span class="dt">&quot;trusted_proxies&quot;</span><span class="fu">:</span> <span class="ot">[[]</span><span class="er">(.html)</span>
210<span class="co">// Set to false to disable local thumbnail cache, e.g. due to limited disk quotas</span> 210 <span class="st">&quot;1.2.3.4&quot;</span><span class="ot">,</span>
211<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ENABLE_THUMBNAILS&#39;</span><span class="ot">]</span> = <span class="kw">true</span><span class="ot">;](</span><span class="st">&#39;ENABLE_THUMBNAILS&#39;</span><span class="ot">]</span>-=-<span class="kw">true</span><span class="ot">;</span>.html<span class="ot">)</span> 211 <span class="st">&quot;5.6.7.8&quot;</span>
212 212 <span class="ot">]</span>
213<span class="co">// Thumbnail cache directory</span> 213 <span class="fu">},</span>
214<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;CACHEDIR&#39;</span><span class="ot">]</span> = <span class="st">&#39;cache&#39;</span><span class="ot">;](</span><span class="st">&#39;CACHEDIR&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;cache&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 214 <span class="dt">&quot;resources&quot;</span><span class="fu">:</span> <span class="fu">{</span>
215 215 <span class="dt">&quot;data_dir&quot;</span><span class="fu">:</span> <span class="st">&quot;data&quot;</span><span class="fu">,</span>
216<span class="co">// Enable feed (rss, atom, dailyrss) cache</span> 216 <span class="dt">&quot;config&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">config.php&quot;</span><span class="fu">,</span>
217<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ENABLE_LOCALCACHE&#39;</span><span class="ot">]</span> = <span class="kw">true</span><span class="ot">;](</span><span class="st">&#39;ENABLE_LOCALCACHE&#39;</span><span class="ot">]</span>-=-<span class="kw">true</span><span class="ot">;</span>.html<span class="ot">)</span> 217 <span class="dt">&quot;datastore&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">datastore.php&quot;</span><span class="fu">,</span>
218 218 <span class="dt">&quot;ban_file&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">ipbans.php&quot;</span><span class="fu">,</span>
219<span class="co">// Feed cache directory</span> 219 <span class="dt">&quot;updates&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">updates.txt&quot;</span><span class="fu">,</span>
220<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;PAGECACHE&#39;</span><span class="ot">]</span> = <span class="st">&#39;pagecache&#39;</span><span class="ot">;](</span><span class="st">&#39;PAGECACHE&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;pagecache&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 220 <span class="dt">&quot;log&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">log.txt&quot;</span><span class="fu">,</span>
221 221 <span class="dt">&quot;update_check&quot;</span><span class="fu">:</span> <span class="st">&quot;data</span><span class="ch">\/</span><span class="st">lastupdatecheck.txt&quot;</span><span class="fu">,</span>
222<span class="co">// RainTPL cache directory (keep the trailing slash!)</span> 222 <span class="dt">&quot;raintpl_tmp&quot;</span><span class="fu">:</span> <span class="st">&quot;tmp</span><span class="ch">\/</span><span class="st">&quot;</span><span class="fu">,</span>
223<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;RAINTPL_TMP&#39;</span><span class="ot">]</span> = <span class="st">&#39;tmp/&#39;</span><span class="ot">;](</span><span class="st">&#39;RAINTPL_TMP&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;tmp/&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 223 <span class="dt">&quot;raintpl_tpl&quot;</span><span class="fu">:</span> <span class="st">&quot;tpl</span><span class="ch">\/</span><span class="st">&quot;</span><span class="fu">,</span>
224 224 <span class="dt">&quot;thumbnails_cache&quot;</span><span class="fu">:</span> <span class="st">&quot;cache&quot;</span><span class="fu">,</span>
225<span class="co">// RainTPL template directory (keep the trailing slash!)</span> 225 <span class="dt">&quot;page_cache&quot;</span><span class="fu">:</span> <span class="st">&quot;pagecache&quot;</span>
226<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span> = <span class="st">&#39;tpl/&#39;</span><span class="ot">;](</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;tpl/&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 226 <span class="fu">},</span>
227 227 <span class="dt">&quot;general&quot;</span><span class="fu">:</span> <span class="fu">{</span>
228<span class="co">// Whether Shaarli checks for new releases at https://github.com/shaarli/Shaarli</span> 228 <span class="dt">&quot;check_updates&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
229<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ENABLE_UPDATECHECK&#39;</span><span class="ot">]</span> = <span class="kw">true</span><span class="ot">;](</span><span class="st">&#39;ENABLE_UPDATECHECK&#39;</span><span class="ot">]</span>-=-<span class="kw">true</span><span class="ot">;</span>.html<span class="ot">)</span> 229 <span class="dt">&quot;rss_permalinks&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
230 230 <span class="dt">&quot;links_per_page&quot;</span><span class="fu">:</span> <span class="dv">20</span><span class="fu">,</span>
231<span class="co">// File to store the latest Shaarli version</span> 231 <span class="dt">&quot;default_private_links&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
232<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;UPDATECHECK_FILENAME&#39;</span><span class="ot">]</span> = <span class="st">&#39;data/lastupdatecheck.txt&#39;</span><span class="ot">;](</span><span class="st">&#39;UPDATECHECK_FILENAME&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;data/lastupdatecheck.txt&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 232 <span class="dt">&quot;enable_thumbnails&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
233 233 <span class="dt">&quot;enable_localcache&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
234<span class="co">// Delay between version checks (requires to be logged in) (24 hours)</span> 234 <span class="dt">&quot;check_updates_branch&quot;</span><span class="fu">:</span> <span class="st">&quot;stable&quot;</span><span class="fu">,</span>
235<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;UPDATECHECK_INTERVAL&#39;</span><span class="ot">]</span> = <span class="dv">86400</span><span class="ot">;](</span><span class="st">&#39;UPDATECHECK_INTERVAL&#39;</span><span class="ot">]</span>-=-<span class="dv">86400</span><span class="ot">;</span>.html<span class="ot">)</span> 235 <span class="dt">&quot;check_updates_interval&quot;</span><span class="fu">:</span> <span class="dv">86400</span><span class="fu">,</span>
236 236 <span class="dt">&quot;enabled_plugins&quot;</span><span class="fu">:</span> <span class="ot">[[]</span><span class="er">(.html)</span>
237<span class="co">// For each link, display a link to an archived version on archive.org</span> 237 <span class="st">&quot;markdown&quot;</span><span class="ot">,</span>
238<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ARCHIVE_ORG&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;](</span><span class="st">&#39;ARCHIVE_ORG&#39;</span><span class="ot">]</span>-=-<span class="kw">false</span><span class="ot">;</span>.html<span class="ot">)</span> 238 <span class="st">&quot;wallabag&quot;</span><span class="ot">,</span>
239 239 <span class="st">&quot;archiveorg&quot;</span>
240<span class="co">// The RSS item links point:</span> 240 <span class="ot">]</span><span class="fu">,</span>
241<span class="co">// true =&gt; directly to the link</span> 241 <span class="dt">&quot;timezone&quot;</span><span class="fu">:</span> <span class="st">&quot;Europe</span><span class="ch">\/</span><span class="st">Paris&quot;</span><span class="fu">,</span>
242<span class="co">// false =&gt; to the entry on Shaarli (permalink)</span> 242 <span class="dt">&quot;title&quot;</span><span class="fu">:</span> <span class="st">&quot;My Shaarli&quot;</span><span class="fu">,</span>
243<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;ENABLE_RSS_PERMALINKS&#39;</span><span class="ot">]</span> = <span class="kw">true</span><span class="ot">;](</span><span class="st">&#39;ENABLE_RSS_PERMALINKS&#39;</span><span class="ot">]</span>-=-<span class="kw">true</span><span class="ot">;</span>.html<span class="ot">)</span> 243 <span class="dt">&quot;header_link&quot;</span><span class="fu">:</span> <span class="st">&quot;?&quot;</span>
244 244 <span class="fu">},</span>
245<span class="co">// Hide all links to non-logged users</span> 245 <span class="dt">&quot;extras&quot;</span><span class="fu">:</span> <span class="fu">{</span>
246<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;HIDE_PUBLIC_LINKS&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;](</span><span class="st">&#39;HIDE_PUBLIC_LINKS&#39;</span><span class="ot">]</span>-=-<span class="kw">false</span><span class="ot">;</span>.html<span class="ot">)</span> 246 <span class="dt">&quot;show_atom&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
247 247 <span class="dt">&quot;hide_public_links&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
248<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;PUBSUBHUB_URL&#39;</span><span class="ot">]</span> = <span class="st">&#39;&#39;</span><span class="ot">;](</span><span class="st">&#39;PUBSUBHUB_URL&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;&#39;</span><span class="ot">;</span>.html<span class="ot">)</span> 248 <span class="dt">&quot;hide_timestamps&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
249 249 <span class="dt">&quot;open_shaarli&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
250<span class="co">// Show an ATOM Feed button next to the Subscribe (RSS) button.</span> 250 <span class="dt">&quot;redirector&quot;</span><span class="fu">:</span> <span class="st">&quot;http://anonym.to/?&quot;</span><span class="fu">,</span>
251<span class="co">// ATOM feeds are available at the address ?do=atom regardless of this option.</span> 251 <span class="dt">&quot;redirector_encode_url&quot;</span><span class="fu">:</span> <span class="kw">false</span>
252<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;SHOW_ATOM&#39;</span><span class="ot">]</span> = <span class="kw">false</span><span class="ot">;](</span><span class="st">&#39;SHOW_ATOM&#39;</span><span class="ot">]</span>-=-<span class="kw">false</span><span class="ot">;</span>.html<span class="ot">)</span> 252 <span class="fu">},</span>
253 253 <span class="dt">&quot;general&quot;</span><span class="fu">:</span> <span class="fu">{</span>
254<span class="co">// Set this to true if the redirector requires encoded URL, false otherwise.</span> 254 <span class="dt">&quot;header_link&quot;</span><span class="fu">:</span> <span class="st">&quot;?&quot;</span><span class="fu">,</span>
255<span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;REDIRECTOR_URLENCODE&#39;</span><span class="ot">]</span> = <span class="kw">true</span><span class="ot">;](</span><span class="st">&#39;REDIRECTOR_URLENCODE&#39;</span><span class="ot">]</span>-=-<span class="kw">true</span><span class="ot">;</span>.html<span class="ot">)</span> 255 <span class="dt">&quot;links_per_page&quot;</span><span class="fu">:</span> <span class="dv">20</span><span class="fu">,</span>
256<span class="kw">?&gt;</span></code></pre></div> 256 <span class="dt">&quot;enabled_plugins&quot;</span><span class="fu">:</span> <span class="ot">[[]</span><span class="er">(.html)</span>
257 <span class="st">&quot;markdown&quot;</span><span class="ot">,</span>
258 <span class="st">&quot;wallabag&quot;</span>
259 <span class="ot">]</span><span class="fu">,</span>
260 <span class="dt">&quot;timezone&quot;</span><span class="fu">:</span> <span class="st">&quot;Europe</span><span class="ch">\/</span><span class="st">Paris&quot;</span><span class="fu">,</span>
261 <span class="dt">&quot;title&quot;</span><span class="fu">:</span> <span class="st">&quot;My Shaarli&quot;</span>
262 <span class="fu">},</span>
263 <span class="dt">&quot;updates&quot;</span><span class="fu">:</span> <span class="fu">{</span>
264 <span class="dt">&quot;check_updates&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
265 <span class="dt">&quot;check_updates_branch&quot;</span><span class="fu">:</span> <span class="st">&quot;stable&quot;</span><span class="fu">,</span>
266 <span class="dt">&quot;check_updates_interval&quot;</span><span class="fu">:</span> <span class="dv">86400</span>
267 <span class="fu">},</span>
268 <span class="dt">&quot;feed&quot;</span><span class="fu">:</span> <span class="fu">{</span>
269 <span class="dt">&quot;rss_permalinks&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
270 <span class="dt">&quot;show_atom&quot;</span><span class="fu">:</span> <span class="kw">false</span>
271 <span class="fu">},</span>
272 <span class="dt">&quot;privacy&quot;</span><span class="fu">:</span> <span class="fu">{</span>
273 <span class="dt">&quot;default_private_links&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
274 <span class="dt">&quot;hide_public_links&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span>
275 <span class="dt">&quot;hide_timestamps&quot;</span><span class="fu">:</span> <span class="kw">false</span>
276 <span class="fu">},</span>
277 <span class="dt">&quot;thumbnail&quot;</span><span class="fu">:</span> <span class="fu">{</span>
278 <span class="dt">&quot;enable_thumbnails&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span>
279 <span class="dt">&quot;enable_localcache&quot;</span><span class="fu">:</span> <span class="kw">true</span>
280 <span class="fu">},</span>
281 <span class="dt">&quot;redirector&quot;</span><span class="fu">:</span> <span class="fu">{</span>
282 <span class="dt">&quot;url&quot;</span><span class="fu">:</span> <span class="st">&quot;http://anonym.to/?&quot;</span><span class="fu">,</span>
283 <span class="dt">&quot;encode_url&quot;</span><span class="fu">:</span> <span class="kw">false</span>
284 <span class="fu">},</span>
285 <span class="dt">&quot;plugins&quot;</span><span class="fu">:</span> <span class="fu">{</span>
286 <span class="dt">&quot;WALLABAG_URL&quot;</span><span class="fu">:</span> <span class="st">&quot;http://demo.wallabag.org&quot;</span><span class="fu">,</span>
287 <span class="dt">&quot;WALLABAG_VERSION&quot;</span><span class="fu">:</span> <span class="st">&quot;1&quot;</span>
288 <span class="fu">}</span>
289<span class="fu">}</span> <span class="er">?&gt;</span></code></pre></div>
257<h2 id="additional-configuration">Additional configuration</h2> 290<h2 id="additional-configuration">Additional configuration</h2>
258<p>The playvideos plugin may require that you adapt your server's <a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting">Content Security Policy</a> configuration to work properly.<a href=".html"></a></p> 291<p>The playvideos plugin may require that you adapt your server's<br />
292<a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting">Content Security Policy</a> <a href=".html"></a><br />
293configuration to work properly.<a href="(.html).html">(.html)</a></p>
259</body> 294</body>
260</html> 295</html>
diff --git a/doc/Shaarli-configuration.md b/doc/Shaarli-configuration.md
index d0560d79..4a783c0e 100644
--- a/doc/Shaarli-configuration.md
+++ b/doc/Shaarli-configuration.md
@@ -1,14 +1,18 @@
1#Shaarli configuration 1#Shaarli configuration
2# Shaarli configuration
3
2## Foreword 4## Foreword
3 5
4**Do not edit configuration options in index.php! Your changes would be lost.** 6**Do not edit configuration options in index.php! Your changes would be lost.**
5 7
6Once your Shaarli instance is installed, the file `data/config.php` is generated: 8Once your Shaarli instance is installed, the file `data/config.json.php` is generated:
7* it contains all settings, and can be edited to customize values 9* it contains all settings in JSON format, and can be edited to customize values
8* it defines which [plugins](Plugin-System) are enabled[](.html) 10* it defines which [plugins](Plugin-System) are enabled[(.html)]((.html).html)
9* its values override those defined in `index.php` 11* its values override those defined in `index.php`
12* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration
10 13
11## File and directory permissions 14## File and directory permissions
15
12The server process running Shaarli must have: 16The server process running Shaarli must have:
13- `read` access to the following resources: 17- `read` access to the following resources:
14 - PHP scripts: `index.php`, `application/*.php`, `plugins/*.php` 18 - PHP scripts: `index.php`, `application/*.php`, `plugins/*.php`
@@ -29,123 +33,179 @@ On a Linux distribution:
29- to give it access to Shaarli, either: 33- to give it access to Shaarli, either:
30 - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner 34 - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner
31 - put users in the same group as the web server, and set the appropriate access rights 35 - put users in the same group as the web server, and set the appropriate access rights
32- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[](.html) 36- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[(.html)]((.html).html)
33 37
34## Example `data/config.php` 38## Configuration
35See also [Plugin System](Plugin-System.html). 39
36 40In `data/config.json.php`.
37```php 41
38<?php 42See also [Plugin System](Plugin-System.html).[](.html)
39// User login 43
40$GLOBALS['login'] = '<login>';[](.html) 44### Credentials
41 45
42// User password hash 46> You shouldn't edit those.
43$GLOBALS['hash'] = '200c452da46c2f889e5e48c49ef044bcacdcb095';[](.html) 47
44 48**login**: Login username.
45// Password salt 49**hash**: Generated password hash.
46$GLOBALS['salt'] = '13b654102321576033d8473b63a275a1bf94c0f0'; [](.html) 50**salt**: Password salt.
47 51
48// Local timezone 52### General
49$GLOBALS['timezone'] = 'Africa/Abidjan';[](.html) 53
50date_default_timezone_set('Africa/Abidjan'); 54**title**: Shaarli's instance title.
51 55**header_link**: Link to the homepage.
52// Shaarli title 56**links_per_page**: Number of shaares displayed per page.
53$GLOBALS['title'] = 'My Little Shaarly';[](.html) 57**timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). [](.html)
54 58**enabled_plugins**: List of enabled plugins.
55// Link the Shaarli title points to 59
56$GLOBALS['titleLink'] = '?';[](.html) 60### Security
57 61
58// HTTP referer redirector 62**session_protection_disabled**: Disable session cookie hijacking protection (not recommended).
59$GLOBALS['redirector'] = '';[](.html) 63It might be useful if your IP adress often changes.
60 64**ban_after**: Failed login attempts before being IP banned.
61// Disable session hijacking 65**ban_duration**: IP ban duration in seconds.
62$GLOBALS['disablesessionprotection'] = false; [](.html) 66**open_shaarli**: Anyone can add a new link while logged out if enabled.
63 67**trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy.
64// Whether new links are private by default 68
65$GLOBALS['privateLinkByDefault'] = false;[](.html) 69### Resources
66 70
67// Enabled plugins 71**data_dir**: Data directory.
68// Note: each plugin may provide further settings through its own "config.php" 72**datastore**: Shaarli's links database file path.
69$GLOBALS['config'['ENABLED_PLUGINS'] = array('addlink_toolbar', 'qrcode');]('ENABLED_PLUGINS']-=-array('addlink_toolbar',-'qrcode');.html) 73**updates**: File path for the ran updates file.
70 74**log**: Log file path.
71// Subdirectory where Shaarli stores its data files. 75**update_check**: Last update check file path.
72// You can change it for better security. 76**raintpl_tpl**: Templates directory.
73$GLOBALS['config'['DATADIR'] = 'data';]('DATADIR']-=-'data';.html) 77**raintpl_tmp**: Template engine cache directory.
74 78**thumbnails_cache**: Thumbnails cache directory.
75// File used to store settings 79**page_cache**: Shaarli's internal cache directory.
76$GLOBALS['config'['CONFIG_FILE'] = 'data/config.php';]('CONFIG_FILE']-=-'data/config.php';.html) 80**ban_file**: Banned IP file path.
77 81
78// File containing the link database 82### Updates
79$GLOBALS['config'['DATASTORE'] = 'data/datastore.php';]('DATASTORE']-=-'data/datastore.php';.html) 83
80 84**check_updates**: Enable or disable update check to the git repository.
81// Number of links displayed per page 85**check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`).
82$GLOBALS['config'['LINKS_PER_PAGE'] = 20;]('LINKS_PER_PAGE']-=-20;.html) 86**check_updates_interval**: Look for new version every N seconds (default: every day).
83 87
84// File recording failed login attempts and IP bans 88### Privacy
85$GLOBALS['config'['IPBANS_FILENAME'] = 'data/ipbans.php';]('IPBANS_FILENAME']-=-'data/ipbans.php';.html) 89
86 90**default_private_links**: Check the private checkbox by default for every new link.
87// Failed login attempts before being banned 91**hide_public_links**: All links are hidden while logged out.
88$GLOBALS['config'['BAN_AFTER'] = 4;]('BAN_AFTER']-=-4;.html) 92**hide_timestamps**: Timestamps are hidden.
89 93
90// Duration of an IP ban, in seconds (30 minutes) 94### Feed
91$GLOBALS['config'['BAN_DURATION'] = 1800;]('BAN_DURATION']-=-1800;.html) 95
92 96**rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL.
93// If set to true, everyone will be able to add, edit and remove links, 97**show_atom**: Display ATOM feed button.
94// as well as change configuration 98
95$GLOBALS['config'['OPEN_SHAARLI'] = false;]('OPEN_SHAARLI']-=-false;.html) 99### Thumbnail
96 100
97// Do not show link timestamps 101**enable_thumbnails**: Enable or disable thumbnail display.
98$GLOBALS['config'['HIDE_TIMESTAMPS'] = false;]('HIDE_TIMESTAMPS']-=-false;.html) 102**enable_localcache**: Enable or disable local cache.
99 103
100// Set to false to disable local thumbnail cache, e.g. due to limited disk quotas 104### Redirector
101$GLOBALS['config'['ENABLE_THUMBNAILS'] = true;]('ENABLE_THUMBNAILS']-=-true;.html) 105
102 106**url**: Redirector URL, such as `anonym.to`.
103// Thumbnail cache directory 107**encode_url**: Enable this if the redirector needs encoded URL to work properly.
104$GLOBALS['config'['CACHEDIR'] = 'cache';]('CACHEDIR']-=-'cache';.html) 108
105 109## Configuration file example
106// Enable feed (rss, atom, dailyrss) cache 110
107$GLOBALS['config'['ENABLE_LOCALCACHE'] = true;]('ENABLE_LOCALCACHE']-=-true;.html) 111```json
108 112<?php /*
109// Feed cache directory 113{
110$GLOBALS['config'['PAGECACHE'] = 'pagecache';]('PAGECACHE']-=-'pagecache';.html) 114 "credentials": {
111 115 "login": "<login>",
112// RainTPL cache directory (keep the trailing slash!) 116 "hash": "<password hash>",
113$GLOBALS['config'['RAINTPL_TMP'] = 'tmp/';]('RAINTPL_TMP']-=-'tmp/';.html) 117 "salt": "<password salt>"
114 118 },
115// RainTPL template directory (keep the trailing slash!) 119 "security": {
116$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/';]('RAINTPL_TPL']-=-'tpl/';.html) 120 "ban_after": 4,
117 121 "session_protection_disabled": false,
118// Whether Shaarli checks for new releases at https://github.com/shaarli/Shaarli 122 "ban_duration": 1800,
119$GLOBALS['config'['ENABLE_UPDATECHECK'] = true;]('ENABLE_UPDATECHECK']-=-true;.html) 123 "trusted_proxies": [[](.html)
120 124 "1.2.3.4",
121// File to store the latest Shaarli version 125 "5.6.7.8"
122$GLOBALS['config'['UPDATECHECK_FILENAME'] = 'data/lastupdatecheck.txt';]('UPDATECHECK_FILENAME']-=-'data/lastupdatecheck.txt';.html) 126 ]
123 127 },
124// Delay between version checks (requires to be logged in) (24 hours) 128 "resources": {
125$GLOBALS['config'['UPDATECHECK_INTERVAL'] = 86400;]('UPDATECHECK_INTERVAL']-=-86400;.html) 129 "data_dir": "data",
126 130 "config": "data\/config.php",
127// For each link, display a link to an archived version on archive.org 131 "datastore": "data\/datastore.php",
128$GLOBALS['config'['ARCHIVE_ORG'] = false;]('ARCHIVE_ORG']-=-false;.html) 132 "ban_file": "data\/ipbans.php",
129 133 "updates": "data\/updates.txt",
130// The RSS item links point: 134 "log": "data\/log.txt",
131// true => directly to the link 135 "update_check": "data\/lastupdatecheck.txt",
132// false => to the entry on Shaarli (permalink) 136 "raintpl_tmp": "tmp\/",
133$GLOBALS['config'['ENABLE_RSS_PERMALINKS'] = true;]('ENABLE_RSS_PERMALINKS']-=-true;.html) 137 "raintpl_tpl": "tpl\/",
134 138 "thumbnails_cache": "cache",
135// Hide all links to non-logged users 139 "page_cache": "pagecache"
136$GLOBALS['config'['HIDE_PUBLIC_LINKS'] = false;]('HIDE_PUBLIC_LINKS']-=-false;.html) 140 },
137 141 "general": {
138$GLOBALS['config'['PUBSUBHUB_URL'] = '';]('PUBSUBHUB_URL']-=-'';.html) 142 "check_updates": true,
139 143 "rss_permalinks": true,
140// Show an ATOM Feed button next to the Subscribe (RSS) button. 144 "links_per_page": 20,
141// ATOM feeds are available at the address ?do=atom regardless of this option. 145 "default_private_links": true,
142$GLOBALS['config'['SHOW_ATOM'] = false;]('SHOW_ATOM']-=-false;.html) 146 "enable_thumbnails": true,
143 147 "enable_localcache": true,
144// Set this to true if the redirector requires encoded URL, false otherwise. 148 "check_updates_branch": "stable",
145$GLOBALS['config'['REDIRECTOR_URLENCODE'] = true;]('REDIRECTOR_URLENCODE']-=-true;.html) 149 "check_updates_interval": 86400,
146?> 150 "enabled_plugins": [[](.html)
151 "markdown",
152 "wallabag",
153 "archiveorg"
154 ],
155 "timezone": "Europe\/Paris",
156 "title": "My Shaarli",
157 "header_link": "?"
158 },
159 "extras": {
160 "show_atom": false,
161 "hide_public_links": false,
162 "hide_timestamps": false,
163 "open_shaarli": false,
164 "redirector": "http://anonym.to/?",
165 "redirector_encode_url": false
166 },
167 "general": {
168 "header_link": "?",
169 "links_per_page": 20,
170 "enabled_plugins": [[](.html)
171 "markdown",
172 "wallabag"
173 ],
174 "timezone": "Europe\/Paris",
175 "title": "My Shaarli"
176 },
177 "updates": {
178 "check_updates": true,
179 "check_updates_branch": "stable",
180 "check_updates_interval": 86400
181 },
182 "feed": {
183 "rss_permalinks": true,
184 "show_atom": false
185 },
186 "privacy": {
187 "default_private_links": true,
188 "hide_public_links": false,
189 "hide_timestamps": false
190 },
191 "thumbnail": {
192 "enable_thumbnails": true,
193 "enable_localcache": true
194 },
195 "redirector": {
196 "url": "http://anonym.to/?",
197 "encode_url": false
198 },
199 "plugins": {
200 "WALLABAG_URL": "http://demo.wallabag.org",
201 "WALLABAG_VERSION": "1"
202 }
203} ?>
147``` 204```
148 205
149## Additional configuration 206## Additional configuration
150 207
151The playvideos plugin may require that you adapt your server's [Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting) configuration to work properly.[](.html) 208The playvideos plugin may require that you adapt your server's
209[Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting) [](.html)
210configuration to work properly.[(.html)]((.html).html)
211
diff --git a/doc/Shaarli-installation.html b/doc/Shaarli-installation.html
deleted file mode 100644
index 487ec1db..00000000
--- a/doc/Shaarli-installation.html
+++ /dev/null
@@ -1,72 +0,0 @@
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="generator" content="pandoc">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
7 <title>Shaarli – Shaarli installation</title>
8 <style type="text/css">code{white-space: pre;}</style>
9 <link rel="stylesheet" href="github-markdown.css">
10 <!--[if lt IE 9]>
11 <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
12 <![endif]-->
13</head>
14<body>
15<div id="local-sidebar">
16<ul>
17<li><a href="Home.html">Home</a></li>
18<li>Installation
19<ul>
20<li><a href="Download.html">Download</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li>
28<li><a href="Docker.html">Docker</a></li>
29<li><a href="Usage.html">Usage</a>
30<ul>
31<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
32<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
33<li><a href="Firefox-share.html">Firefox share</a></li>
34<li><a href="RSS-feeds.html">RSS feeds</a></li>
35</ul></li>
36<li>How To
37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
43<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
44</ul></li>
45<li><a href="Troubleshooting.html">Troubleshooting</a></li>
46<li><a href="Development.html">Development</a>
47<ul>
48<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
49<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
50<li><a href="Directory-structure.html">Directory structure</a></li>
51<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
52<li><a href="Plugin-System.html">Plugin System</a></li>
53<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
54<li><a href="Security.html">Security</a></li>
55<li><a href="Static-analysis.html">Static analysis</a></li>
56<li><a href="Theming.html">Theming</a></li>
57<li><a href="Unit-tests.html">Unit tests</a></li>
58</ul></li>
59<li>About
60<ul>
61<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li>
65</ul>
66</div>
67<h1 id="shaarli-installation">Shaarli installation</h1>
68<p>Once Shaarli is downloaded and installed behind a web server, open it in your favorite browser.</p>
69<p><img src="http://i.imgur.com/wuMpDSN.png" alt="install screenshot" /><a href=".html"></a></p>
70<p>Setup your Shaarli installation, and it's ready to use!</p>
71</body>
72</html>
diff --git a/doc/Shaarli-installation.md b/doc/Shaarli-installation.md
deleted file mode 100644
index be9726e0..00000000
--- a/doc/Shaarli-installation.md
+++ /dev/null
@@ -1,6 +0,0 @@
1#Shaarli installation
2Once Shaarli is downloaded and installed behind a web server, open it in your favorite browser.
3
4![install screenshot](http://i.imgur.com/wuMpDSN.png)[](.html)
5
6Setup your Shaarli installation, and it's ready to use!
diff --git a/doc/Sharing-button.html b/doc/Sharing-button.html
index 3770d8ad..93710efe 100644
--- a/doc/Sharing-button.html
+++ b/doc/Sharing-button.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/Static-analysis.html b/doc/Static-analysis.html
index 86cb4696..d964e917 100644
--- a/doc/Static-analysis.html
+++ b/doc/Static-analysis.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/TODO.html b/doc/TODO.html
deleted file mode 100644
index 04224dbf..00000000
--- a/doc/TODO.html
+++ /dev/null
@@ -1,74 +0,0 @@
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="generator" content="pandoc">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
7 <title>Shaarli – TODO</title>
8 <style type="text/css">code{white-space: pre;}</style>
9 <link rel="stylesheet" href="github-markdown.css">
10 <!--[if lt IE 9]>
11 <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
12 <![endif]-->
13</head>
14<body>
15<div id="local-sidebar">
16<ul>
17<li><a href="Home.html">Home</a></li>
18<li>Installation
19<ul>
20<li><a href="Download.html">Download</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li>
28<li><a href="Docker.html">Docker</a></li>
29<li><a href="Usage.html">Usage</a>
30<ul>
31<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
32<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
33<li><a href="Firefox-share.html">Firefox share</a></li>
34<li><a href="RSS-feeds.html">RSS feeds</a></li>
35</ul></li>
36<li>How To
37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
43<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
44</ul></li>
45<li><a href="Troubleshooting.html">Troubleshooting</a></li>
46<li><a href="Development.html">Development</a>
47<ul>
48<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
49<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
50<li><a href="Directory-structure.html">Directory structure</a></li>
51<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
52<li><a href="Plugin-System.html">Plugin System</a></li>
53<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
54<li><a href="Security.html">Security</a></li>
55<li><a href="Static-analysis.html">Static analysis</a></li>
56<li><a href="Theming.html">Theming</a></li>
57<li><a href="Unit-tests.html">Unit tests</a></li>
58</ul></li>
59<li>About
60<ul>
61<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li>
65</ul>
66</div>
67<h1 id="todo">TODO</h1>
68<ul>
69<li>add more screenshots</li>
70<li>improve developer documentation: storage architecture, classes and functions, security handling...</li>
71<li>add server configuration examples: lighthttpd</li>
72</ul>
73</body>
74</html>
diff --git a/doc/TODO.md b/doc/TODO.md
deleted file mode 100644
index fb72fd57..00000000
--- a/doc/TODO.md
+++ /dev/null
@@ -1,4 +0,0 @@
1#TODO
2* add more screenshots
3* improve developer documentation: storage architecture, classes and functions, security handling...
4* add server configuration examples: lighthttpd
diff --git a/doc/Theming.html b/doc/Theming.html
index 27c5d863..7cbf7aef 100644
--- a/doc/Theming.html
+++ b/doc/Theming.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -121,18 +119,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
121<ul> 119<ul>
122<li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li> 120<li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li>
123</ul></li> 121</ul></li>
124<li><p>Edit <code>data/config.php</code> to have Shaarli use this template, e.g.</p> 122<li><p>Edit <code>data/config.json.php</code> to have Shaarli use this template, in <code>&quot;resource&quot;</code> e.g.</p>
125<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span> = <span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;](</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;</span>.html<span class="ot">)</span></code></pre></div></li> 123<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="er">&quot;raintpl_tpl&quot;:</span> <span class="er">&quot;tpl\/my-template\/&quot;,</span></code></pre></div></li>
126</ul> 124</ul>
127<h2 id="community-themes-templates">Community themes &amp; templates</h2> 125<h2 id="community-themes-templates">Community themes &amp; templates</h2>
128<ul> 126<ul>
129<li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li> 127<li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li>
130<li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li> 128<li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li>
129<li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme.<a href=".html"></a></li>
131<li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li> 130<li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li>
132<li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li> 131<li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li>
133<li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li> 132<li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li>
133<li><a href="https://github.com/ManufacturaInd/shaarli-2004licious-theme">ManufacturaInd/shaarli-2004licious-theme</a> - A template/theme as a humble homage to the early looks of the del.icio.us site.<a href=".html"></a></li>
134<li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li> 134<li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li>
135<li><a href="https://github.com/Vinm/Blue-theme-for-Shaarli">Vinm/Blue-theme-for Shaarli</a> - A template/theme for Shaarli (<a href="https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2">unmaintained</a>, compatibility unknown)<a href=".html"></a></li> 135<li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for SHaarli.<a href=".html"></a></li>
136<li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li> 136<li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li>
137</ul> 137</ul>
138<h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3> 138<h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3>
@@ -142,17 +142,17 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
142<li>user sites are enabled, e.g. <code>/home/user/public_html/somedir</code> is served as <code>http://localhost/~user/somedir</code></li> 142<li>user sites are enabled, e.g. <code>/home/user/public_html/somedir</code> is served as <code>http://localhost/~user/somedir</code></li>
143<li><code>http</code> is the name of the Apache user</li> 143<li><code>http</code> is the name of the Apache user</li>
144</ul> 144</ul>
145<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">cd</span> ~/public_html 145<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> ~/public_html
146 146
147<span class="co"># clone repositories</span> 147<span class="co"># clone repositories</span>
148$ <span class="kw">git</span> clone https://github.com/shaarli/Shaarli.git shaarli 148$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git shaarli
149$ <span class="kw">pushd</span> shaarli/tpl 149$ <span class="bu">pushd</span> shaarli/tpl
150$ <span class="kw">git</span> clone https://github.com/alexisju/albinomouse-template.git 150$ <span class="fu">git</span> clone https://github.com/alexisju/albinomouse-template.git
151$ <span class="kw">popd</span> 151$ <span class="bu">popd</span>
152 152
153<span class="co"># set access rights for Apache</span> 153<span class="co"># set access rights for Apache</span>
154$ <span class="kw">chgrp</span> -R http shaarli 154$ <span class="fu">chgrp</span> -R http shaarli
155$ <span class="kw">chmod</span> g+rwx shaarli shaarli/cache shaarli/data shaarli/pagecache shaarli/tmp</code></pre></div> 155$ <span class="fu">chmod</span> g+rwx shaarli shaarli/cache shaarli/data shaarli/pagecache shaarli/tmp</code></pre></div>
156<p>Get config written:</p> 156<p>Get config written:</p>
157<ul> 157<ul>
158<li>go to the freshly installed site</li> 158<li>go to the freshly installed site</li>
@@ -161,6 +161,6 @@ $ <span class="kw">chmod</span> g+rwx shaarli shaarli/cache shaarli/data shaarli
161</ul> 161</ul>
162<p>Edit Shaarli's <a href="configuration%7CShaarli-configuration.html">configuration|Shaarli configuration</a>:</p> 162<p>Edit Shaarli's <a href="configuration%7CShaarli-configuration.html">configuration|Shaarli configuration</a>:</p>
163<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># the file should be owned by Apache, thus not writeable =&gt; sudo</span> 163<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># the file should be owned by Apache, thus not writeable =&gt; sudo</span>
164$ <span class="kw">sudo</span> sed -i s=tpl=tpl/albinomouse-template=g shaarli/data/config.php</code></pre></div> 164$ <span class="fu">sudo</span> sed -i s=tpl=tpl/albinomouse-template=g shaarli/data/config.php</code></pre></div>
165</body> 165</body>
166</html> 166</html>
diff --git a/doc/Theming.md b/doc/Theming.md
index 9dfdcf9f..a21899c2 100644
--- a/doc/Theming.md
+++ b/doc/Theming.md
@@ -16,19 +16,21 @@ _WARNING - This feature is currently being worked on and will be improved in the
16- Find it's git clone URL or download the zip archive for the template. 16- Find it's git clone URL or download the zip archive for the template.
17- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive. 17- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive.
18 - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files. 18 - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files.
19- Edit `data/config.php` to have Shaarli use this template, e.g. 19- Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g.
20```php 20```json
21$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html) 21"raintpl_tpl": "tpl\/my-template\/",
22``` 22```
23 23
24## Community themes & templates 24## Community themes & templates
25- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) 25- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html)
26- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) 26- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html)
27- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html)
27- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) 28- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html)
28- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) 29- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html)
29- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html) 30- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html)
31- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site.[](.html)
30- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) 32- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html)
31- [Vinm/Blue-theme-for Shaarli](https://github.com/Vinm/Blue-theme-for-Shaarli) - A template/theme for Shaarli ([unmaintained](https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2), compatibility unknown)[](.html) 33- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html)
32- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) 34- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html)
33 35
34### Example installation: AlbinoMouse template 36### Example installation: AlbinoMouse template
diff --git a/doc/Troubleshooting.html b/doc/Troubleshooting.html
index 3de8ad1e..ed1c6f09 100644
--- a/doc/Troubleshooting.html
+++ b/doc/Troubleshooting.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -132,6 +130,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
132<ul> 130<ul>
133<li>false (default): real referer</li> 131<li>false (default): real referer</li>
134<li>true: spoof referer (use target URI as referer)</li> 132<li>true: spoof referer (use target URI as referer)</li>
133<li>known to break some functionality in Shaarli</li>
135</ul> 134</ul>
136<p><code>network.http.referer.trimmingPolicy</code> - trim the URI not to send a full Referer</p> 135<p><code>network.http.referer.trimmingPolicy</code> - trim the URI not to send a full Referer</p>
137<ul> 136<ul>
@@ -140,7 +139,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
140<li>2: scheme+host+port</li> 139<li>2: scheme+host+port</li>
141</ul> 140</ul>
142<h3 id="firefox-localhost-and-redirections">Firefox, localhost and redirections</h3> 141<h3 id="firefox-localhost-and-redirections">Firefox, localhost and redirections</h3>
143<p><code>localhost</code> is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or anly accept requests from the same base domain/host, Shaarli redirections will not work properly.</p> 142<p><code>localhost</code> is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host, Shaarli redirections will not work properly.</p>
144<p>To solve this, assign a local domain to your host, e.g.</p> 143<p>To solve this, assign a local domain to your host, e.g.</p>
145<pre><code>127.0.0.1 localhost desktop localhost.lan 144<pre><code>127.0.0.1 localhost desktop localhost.lan
146::1 localhost desktop localhost.lan</code></pre> 145::1 localhost desktop localhost.lan</code></pre>
diff --git a/doc/Troubleshooting.md b/doc/Troubleshooting.md
index e91fe846..8e30fce5 100644
--- a/doc/Troubleshooting.md
+++ b/doc/Troubleshooting.md
@@ -25,6 +25,7 @@ HTTP settings are available by browsing `about:config`, here are the available s
25`network.http.referer.spoofSource` - Referer spoofing (~faking) 25`network.http.referer.spoofSource` - Referer spoofing (~faking)
26- false (default): real referer 26- false (default): real referer
27- true: spoof referer (use target URI as referer) 27- true: spoof referer (use target URI as referer)
28 - known to break some functionality in Shaarli
28 29
29`network.http.referer.trimmingPolicy` - trim the URI not to send a full Referer 30`network.http.referer.trimmingPolicy` - trim the URI not to send a full Referer
30- 0 (default): send full URI 31- 0 (default): send full URI
@@ -32,7 +33,7 @@ HTTP settings are available by browsing `about:config`, here are the available s
32- 2: scheme+host+port 33- 2: scheme+host+port
33 34
34### Firefox, localhost and redirections 35### Firefox, localhost and redirections
35`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or anly accept requests from the same base domain/host, Shaarli redirections will not work properly. 36`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host, Shaarli redirections will not work properly.
36 37
37To solve this, assign a local domain to your host, e.g. 38To solve this, assign a local domain to your host, e.g.
38``` 39```
diff --git a/doc/Unit-tests.html b/doc/Unit-tests.html
index 7934e346..266fd33a 100644
--- a/doc/Unit-tests.html
+++ b/doc/Unit-tests.html
@@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
52<div id="local-sidebar"> 52<div id="local-sidebar">
53<ul> 53<ul>
54<li><a href="Home.html">Home</a></li> 54<li><a href="Home.html">Home</a></li>
55<li>Installation 55<li>Setup
56<ul> 56<ul>
57<li><a href="Download.html">Download</a></li> 57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
58<li><a href="Server-requirements.html">Server requirements</a></li> 59<li><a href="Server-requirements.html">Server requirements</a></li>
59<li><a href="Server-configuration.html">Server configuration</a></li> 60<li><a href="Server-configuration.html">Server configuration</a></li>
60<li><a href="Server-security.html">Server security</a></li> 61<li><a href="Server-security.html">Server security</a></li>
61<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li> 63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li> 64</ul></li>
@@ -73,7 +73,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
73<li>How To 73<li>How To
74<ul> 74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
77<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
78<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
79<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -97,7 +96,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
97<ul> 96<ul>
98<li><a href="FAQ.html">FAQ</a></li> 97<li><a href="FAQ.html">FAQ</a></li>
99<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
100<li><a href="TODO.html">TODO</a></li>
101</ul></li> 99</ul></li>
102</ul> 100</ul>
103</div> 101</div>
@@ -111,87 +109,87 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
111</ul> 109</ul>
112<h4 id="sample-usage">Sample usage</h4> 110<h4 id="sample-usage">Sample usage</h4>
113<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># system-wide version</span> 111<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># system-wide version</span>
114$ <span class="kw">composer</span> install 112$ <span class="ex">composer</span> install
115$ <span class="kw">composer</span> update 113$ <span class="ex">composer</span> update
116 114
117<span class="co"># local version</span> 115<span class="co"># local version</span>
118$ <span class="kw">php</span> composer.phar self-update 116$ <span class="ex">php</span> composer.phar self-update
119$ <span class="kw">php</span> composer.phar install 117$ <span class="ex">php</span> composer.phar install
120$ <span class="kw">php</span> composer.phar update</code></pre></div> 118$ <span class="ex">php</span> composer.phar update</code></pre></div>
121<h4 id="install-shaarli-dev-dependencies">Install Shaarli dev dependencies</h4> 119<h4 id="install-shaarli-dev-dependencies">Install Shaarli dev dependencies</h4>
122<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">cd</span> /path/to/shaarli 120<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
123$ <span class="kw">composer</span> update</code></pre></div> 121$ <span class="ex">composer</span> update</code></pre></div>
124<h4 id="install-and-enable-xdebug-to-generate-phpunit-coverage-reports">Install and enable Xdebug to generate PHPUnit coverage reports</h4> 122<h4 id="install-and-enable-xdebug-to-generate-phpunit-coverage-reports">Install and enable Xdebug to generate PHPUnit coverage reports</h4>
125<p>For Debian-based distros:</p> 123<p>For Debian-based distros:</p>
126<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">aptitude</span> install php5-xdebug</code></pre></div> 124<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">aptitude</span> install php5-xdebug</code></pre></div>
127<p>For ArchLinux:</p> 125<p>For ArchLinux:</p>
128<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">pacman</span> -S xdebug</code></pre></div> 126<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">pacman</span> -S xdebug</code></pre></div>
129<p>Then add the following line to <code>/etc/php/php.ini</code>:</p> 127<p>Then add the following line to <code>/etc/php/php.ini</code>:</p>
130<div class="sourceCode"><pre class="sourceCode ini"><code class="sourceCode ini"><span class="dt">zend_extension</span><span class="ot">=</span><span class="st">xdebug.so</span></code></pre></div> 128<div class="sourceCode"><pre class="sourceCode ini"><code class="sourceCode ini"><span class="dt">zend_extension</span><span class="ot">=</span><span class="st">xdebug.so</span></code></pre></div>
131<h4 id="run-unit-tests">Run unit tests</h4> 129<h4 id="run-unit-tests">Run unit tests</h4>
132<p>Successful test suite:</p> 130<p>Successful test suite:</p>
133<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">make</span> test 131<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">make</span> test
134 132
135<span class="kw">-------</span> 133<span class="ex">-------</span>
136<span class="kw">PHPUNIT</span> 134<span class="ex">PHPUNIT</span>
137<span class="kw">-------</span> 135<span class="ex">-------</span>
138<span class="kw">PHPUnit</span> 4.6.9 by Sebastian Bergmann and contributors. 136<span class="ex">PHPUnit</span> 4.6.9 by Sebastian Bergmann and contributors.
139 137
140<span class="kw">Configuration</span> read from /home/virtualtam/public_html/shaarli/phpunit.xml 138<span class="ex">Configuration</span> read from /home/virtualtam/public_html/shaarli/phpunit.xml
141 139
142<span class="kw">....................................</span> 140<span class="ex">....................................</span>
143 141
144<span class="kw">Time</span>: 759 ms, Memory: 8.25Mb 142<span class="ex">Time</span>: 759 ms, Memory: 8.25Mb
145 143
146<span class="kw">OK</span> (36 tests, 65 assertions)</code></pre></div> 144<span class="ex">OK</span> (36 tests, 65 assertions)</code></pre></div>
147<p>Test suite with failures and errors:</p> 145<p>Test suite with failures and errors:</p>
148<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="kw">make</span> test 146<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">make</span> test
149<span class="kw">-------</span> 147<span class="ex">-------</span>
150<span class="kw">PHPUNIT</span> 148<span class="ex">PHPUNIT</span>
151<span class="kw">-------</span> 149<span class="ex">-------</span>
152<span class="kw">PHPUnit</span> 4.6.9 by Sebastian Bergmann and contributors. 150<span class="ex">PHPUnit</span> 4.6.9 by Sebastian Bergmann and contributors.
153 151
154<span class="kw">Configuration</span> read from /home/virtualtam/public_html/shaarli/phpunit.xml 152<span class="ex">Configuration</span> read from /home/virtualtam/public_html/shaarli/phpunit.xml
155 153
156<span class="kw">E..FF...............................</span> 154<span class="ex">E..FF...............................</span>
157 155
158<span class="kw">Time</span>: 802 ms, Memory: 8.25Mb 156<span class="ex">Time</span>: 802 ms, Memory: 8.25Mb
159 157
160<span class="kw">There</span> was 1 error: 158<span class="ex">There</span> was 1 error:
161 159
162<span class="kw">1</span>) <span class="kw">LinkDBTest</span>::testConstructLoggedIn 160<span class="ex">1</span>) <span class="ex">LinkDBTest</span>::testConstructLoggedIn
163<span class="kw">Missing</span> argument 2 for LinkDB::__construct(), <span class="kw">called</span> in /home/virtualtam/public_html/shaarli/tests/Link\ 161<span class="ex">Missing</span> argument 2 for LinkDB::__construct(), <span class="ex">called</span> in /home/virtualtam/public_html/shaarli/tests/Link\
164DBTest.php on line 79 and defined 162DBTest.php on line 79 and defined
165 163
166<span class="kw">/home/virtualtam/public_html/shaarli/application</span>/LinkDB.php:<span class="kw">58</span> 164<span class="ex">/home/virtualtam/public_html/shaarli/application</span>/LinkDB.php:<span class="ex">58</span>
167<span class="kw">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="kw">79</span> 165<span class="ex">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="ex">79</span>
168 166
169<span class="kw">--</span> 167<span class="ex">--</span>
170 168
171<span class="kw">There</span> were 2 failures: 169<span class="ex">There</span> were 2 failures:
172 170
173<span class="kw">1</span>) <span class="kw">LinkDBTest</span>::testCheckDBNew 171<span class="ex">1</span>) <span class="ex">LinkDBTest</span>::testCheckDBNew
174<span class="kw">Failed</span> asserting that two strings are equal. 172<span class="ex">Failed</span> asserting that two strings are equal.
175<span class="kw">---</span> Expected 173<span class="ex">---</span> Expected
176<span class="kw">+++</span> Actual 174<span class="ex">+++</span> Actual
177<span class="kw">@@</span> @@ 175<span class="ex">@@</span> @@
178<span class="kw">-</span><span class="st">&#39;e3edea8ea7bb50be4bcb404df53fbb4546a7156e&#39;</span> 176<span class="ex">-</span><span class="st">&#39;e3edea8ea7bb50be4bcb404df53fbb4546a7156e&#39;</span>
179<span class="kw">+</span><span class="st">&#39;85eab0c610d4f68025f6ed6e6b6b5fabd4b55834&#39;</span> 177<span class="ex">+</span><span class="st">&#39;85eab0c610d4f68025f6ed6e6b6b5fabd4b55834&#39;</span>
180 178
181<span class="kw">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="kw">121</span> 179<span class="ex">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="ex">121</span>
182 180
183<span class="kw">2</span>) <span class="kw">LinkDBTest</span>::testCheckDBLoad 181<span class="ex">2</span>) <span class="ex">LinkDBTest</span>::testCheckDBLoad
184<span class="kw">Failed</span> asserting that two strings are equal. 182<span class="ex">Failed</span> asserting that two strings are equal.
185<span class="kw">---</span> Expected 183<span class="ex">---</span> Expected
186<span class="kw">+++</span> Actual 184<span class="ex">+++</span> Actual
187<span class="kw">@@</span> @@ 185<span class="ex">@@</span> @@
188<span class="kw">-</span><span class="st">&#39;e3edea8ea7bb50be4bcb404df53fbb4546a7156e&#39;</span> 186<span class="ex">-</span><span class="st">&#39;e3edea8ea7bb50be4bcb404df53fbb4546a7156e&#39;</span>
189<span class="kw">+</span><span class="st">&#39;85eab0c610d4f68025f6ed6e6b6b5fabd4b55834&#39;</span> 187<span class="ex">+</span><span class="st">&#39;85eab0c610d4f68025f6ed6e6b6b5fabd4b55834&#39;</span>
190 188
191<span class="kw">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="kw">133</span> 189<span class="ex">/home/virtualtam/public_html/shaarli/tests</span>/LinkDBTest.php:<span class="ex">133</span>
192 190
193<span class="kw">FAILURES</span>! 191<span class="ex">FAILURES</span>!
194<span class="kw">Tests</span>: 36, Assertions: 63, Errors: 1, Failures: 2.</code></pre></div> 192<span class="ex">Tests</span>: 36, Assertions: 63, Errors: 1, Failures: 2.</code></pre></div>
195<h4 id="test-results-and-coverage">Test results and coverage</h4> 193<h4 id="test-results-and-coverage">Test results and coverage</h4>
196<p>By default, PHPUnit will run all suitable tests found under the <code>tests</code> directory.</p> 194<p>By default, PHPUnit will run all suitable tests found under the <code>tests</code> directory.</p>
197<p>Each test has 3 possible outcomes:</p> 195<p>Each test has 3 possible outcomes:</p>
diff --git a/doc/Upgrade-and-migration.html b/doc/Upgrade-and-migration.html
new file mode 100644
index 00000000..a5b041d5
--- /dev/null
+++ b/doc/Upgrade-and-migration.html
@@ -0,0 +1,242 @@
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="generator" content="pandoc">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
7 <title>Shaarli – Upgrade and migration</title>
8 <style type="text/css">code{white-space: pre;}</style>
9 <style type="text/css">
10div.sourceCode { overflow-x: auto; }
11table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
12 margin: 0; padding: 0; vertical-align: baseline; border: none; }
13table.sourceCode { width: 100%; line-height: 100%; }
14td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
15td.sourceCode { padding-left: 5px; }
16code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
17code > span.dt { color: #902000; } /* DataType */
18code > span.dv { color: #40a070; } /* DecVal */
19code > span.bn { color: #40a070; } /* BaseN */
20code > span.fl { color: #40a070; } /* Float */
21code > span.ch { color: #4070a0; } /* Char */
22code > span.st { color: #4070a0; } /* String */
23code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
24code > span.ot { color: #007020; } /* Other */
25code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
26code > span.fu { color: #06287e; } /* Function */
27code > span.er { color: #ff0000; font-weight: bold; } /* Error */
28code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
29code > span.cn { color: #880000; } /* Constant */
30code > span.sc { color: #4070a0; } /* SpecialChar */
31code > span.vs { color: #4070a0; } /* VerbatimString */
32code > span.ss { color: #bb6688; } /* SpecialString */
33code > span.im { } /* Import */
34code > span.va { color: #19177c; } /* Variable */
35code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
36code > span.op { color: #666666; } /* Operator */
37code > span.bu { } /* BuiltIn */
38code > span.ex { } /* Extension */
39code > span.pp { color: #bc7a00; } /* Preprocessor */
40code > span.at { color: #7d9029; } /* Attribute */
41code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
42code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
43code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
44code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
45 </style>
46 <link rel="stylesheet" href="github-markdown.css">
47 <!--[if lt IE 9]>
48 <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
49 <![endif]-->
50</head>
51<body>
52<div id="local-sidebar">
53<ul>
54<li><a href="Home.html">Home</a></li>
55<li>Setup
56<ul>
57<li><a href="Download-and-Installation.html">Download and Installation</a></li>
58<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
59<li><a href="Server-requirements.html">Server requirements</a></li>
60<li><a href="Server-configuration.html">Server configuration</a></li>
61<li><a href="Server-security.html">Server security</a></li>
62<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
63<li><a href="Plugins.html">Plugins</a></li>
64</ul></li>
65<li><a href="Docker.html">Docker</a></li>
66<li><a href="Usage.html">Usage</a>
67<ul>
68<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
69<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
70<li><a href="Firefox-share.html">Firefox share</a></li>
71<li><a href="RSS-feeds.html">RSS feeds</a></li>
72</ul></li>
73<li>How To
74<ul>
75<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
76<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
77<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
78<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
79<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
80</ul></li>
81<li><a href="Troubleshooting.html">Troubleshooting</a></li>
82<li><a href="Development.html">Development</a>
83<ul>
84<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
85<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
86<li><a href="Directory-structure.html">Directory structure</a></li>
87<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
88<li><a href="Plugin-System.html">Plugin System</a></li>
89<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
90<li><a href="Security.html">Security</a></li>
91<li><a href="Static-analysis.html">Static analysis</a></li>
92<li><a href="Theming.html">Theming</a></li>
93<li><a href="Unit-tests.html">Unit tests</a></li>
94</ul></li>
95<li>About
96<ul>
97<li><a href="FAQ.html">FAQ</a></li>
98<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
99</ul></li>
100</ul>
101</div>
102<h1 id="upgrade-and-migration">Upgrade and migration</h1>
103<h2 id="preparation">Preparation</h2>
104<h3 id="backup-your-data">Backup your data</h3>
105<p>Shaarli stores all user data under the <code>data</code> directory:</p>
106<ul>
107<li><code>data/config.php</code> - main configuration file</li>
108<li><code>data/datastore.php</code> - bookmarked links</li>
109<li><code>data/ipbans.php</code> - banned IP addresses</li>
110</ul>
111<p>See <a href="Shaarli-configuration.html">Shaarli configuration</a> for more information about Shaarli resources.</p>
112<p>It is recommended to backup this repository <em>before</em> starting updating/upgrading Shaarli:</p>
113<ul>
114<li>users with SSH access: copy or archive the directory to a temporary location</li>
115<li>users with FTP access: download a local copy of your Shaarli installation using your favourite client</li>
116</ul>
117<h3 id="migrating-data-from-a-previous-installation">Migrating data from a previous installation</h3>
118<p>As all user data is kept under <code>data</code>, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps:</p>
119<ul>
120<li>backup the <code>data</code> directory</li>
121<li>install or update Shaarli:
122<ul>
123<li>fresh installation - see <a href="Download-and-installation.html">Download and installation</a></li>
124<li>update - see the following sections</li>
125</ul></li>
126<li>check or restore the <code>data</code> directory</li>
127</ul>
128<h2 id="upgrading-from-release-archives">Upgrading from release archives</h2>
129<p>All tagged revisions can be downloaded as tarballs or ZIP archives from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
130<p>We <em>recommend</em> using the releases from the <code>stable</code> branch, which are available as:</p>
131<ul>
132<li>gzipped tarball - <a href="https://github.com/shaarli/Shaarli/archive/stable.tar.gz" class="uri">https://github.com/shaarli/Shaarli/archive/stable.tar.gz</a></li>
133<li>ZIP archive - <a href="https://github.com/shaarli/Shaarli/archive/stable.zip" class="uri">https://github.com/shaarli/Shaarli/archive/stable.zip</a></li>
134</ul>
135<p>Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the <code>data</code> directory!</p>
136<p>After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to <code>data/config.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p>
137<h2 id="upgrading-with-git">Upgrading with Git</h2>
138<h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3>
139<p>If you have installed Shaarli from the <a href="Download#clone-with-git-recommended">community Git repository</a>, simply <a href="https://www.git-scm.com/docs/git-pull">pull new changes</a> from your local clone:<a href=".html"></a></p>
140<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
141$ <span class="fu">git</span> pull
142
143<span class="ex">From</span> github.com:shaarli/Shaarli
144 <span class="ex">*</span> branch master -<span class="op">&gt;</span> FETCH_HEAD
145<span class="ex">Updating</span> ebd67c6..521f0e6
146<span class="ex">Fast-forward</span>
147 <span class="ex">application/Url.php</span> <span class="kw">|</span> <span class="ex">1</span> +
148 <span class="ex">shaarli_version.php</span> <span class="kw">|</span> <span class="ex">2</span> +-
149 <span class="ex">tests/Url/UrlTest.php</span> <span class="kw">|</span> <span class="ex">1</span> +
150 <span class="ex">3</span> files changed, 3 insertions(+), <span class="ex">1</span> deletion(-)</code></pre></div>
151<p>Shaarli &gt;= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p>
152<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev
153
154<span class="ex">Loading</span> composer repositories with package information
155<span class="ex">Updating</span> dependencies
156 <span class="ex">-</span> Installing shaarli/netscape-bookmark-parser (v1.0.1)
157 <span class="ex">Downloading</span>: 100%</code></pre></div>
158<h3 id="migrating-and-upgrading-from-sebsauvages-repository">Migrating and upgrading from Sebsauvage's repository</h3>
159<p>If you have installed Shaarli from <a href="https://github.com/sebsauvage/Shaarli">Sebsauvage's original Git repository</a>, you can use <a href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes">Git remotes</a> to update your working copy.<a href=".html"></a></p>
160<p>The following guide assumes that:</p>
161<ul>
162<li>you have a basic knowledge of Git <a href="https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell">branching</a> and <a href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes">remote repositories</a><a href=".html"></a></li>
163<li>the default remote is named <code>origin</code> and points to Sebsauvage's repository</li>
164<li>the current branch is <code>master</code>
165<ul>
166<li>if you have personal branches containing customizations, you will need to <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">rebase them</a> after the upgrade; beware though, a lot of changes have been made since the community fork has been created, so things are very likely to break<embed src=".html" /></li>
167</ul></li>
168<li>the working copy is clean:
169<ul>
170<li>no versioned file has been locally modified</li>
171<li>no untracked files are present</li>
172</ul></li>
173</ul>
174<h4 id="step-0-show-repository-information">Step 0: show repository information</h4>
175<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
176
177$ <span class="fu">git</span> remote -v
178<span class="ex">origin</span> https://github.com/sebsauvage/Shaarli (fetch)
179<span class="ex">origin</span> https://github.com/sebsauvage/Shaarli (push)
180
181$ <span class="fu">git</span> branch -vv
182<span class="ex">*</span> master 029f75f [origin/master] Update README.md[](.html)
183
184$ <span class="fu">git</span> status
185<span class="ex">On</span> branch master
186<span class="ex">Your</span> branch is up-to-date with <span class="st">&#39;origin/master&#39;</span>.
187<span class="ex">nothing</span> to commit, working directory clean</code></pre></div>
188<h4 id="step-1-update-git-remotes">Step 1: update Git remotes</h4>
189<pre><code>$ git remote rename origin sebsauvage
190$ git remote -v
191sebsauvage https://github.com/sebsauvage/Shaarli (fetch)
192sebsauvage https://github.com/sebsauvage/Shaarli (push)
193
194$ git remote add origin https://github.com/shaarli/Shaarli
195$ git fetch origin
196
197remote: Counting objects: 3015, done.
198remote: Compressing objects: 100% (19/19), done.
199remote: Total 3015 (delta 446), reused 457 (delta 446), pack-reused 2550
200Receiving objects: 100% (3015/3015), 2.59 MiB | 918.00 KiB/s, done.
201Resolving deltas: 100% (1899/1899), completed with 48 local objects.
202From https://github.com/shaarli/Shaarli
203 * [new branch] master -&gt; origin/master[](.html)
204 * [new branch] stable -&gt; origin/stable[](.html)
205[...][](.html)
206 * [new tag] v0.6.4 -&gt; v0.6.4[](.html)
207 * [new tag] v0.7.0 -&gt; v0.7.0[](.html)</code></pre>
208<h4 id="step-2-use-the-stable-community-branch">Step 2: use the stable community branch</h4>
209<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">git</span> checkout origin/stable -b stable
210<span class="ex">Branch</span> stable set up to track remote branch stable from origin.
211<span class="ex">Switched</span> to a new branch <span class="st">&#39;stable&#39;</span>
212
213$ <span class="fu">git</span> branch -vv
214 <span class="ex">master</span> 029f75f [sebsauvage/master] Update README.md[](.html)
215<span class="ex">*</span> stable 890afc3 [origin/stable] Merge pull request <span class="co">#509 from ArthurHoaro/v0.6.5[](.html)</span></code></pre></div>
216<p>Shaarli &gt;= <code>v0.8.x</code>: install/update third-party PHP dependencies using <a href="https://getcomposer.org/">Composer</a>:<a href=".html"></a></p>
217<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev
218
219<span class="ex">Loading</span> composer repositories with package information
220<span class="ex">Updating</span> dependencies
221 <span class="ex">-</span> Installing shaarli/netscape-bookmark-parser (v1.0.1)
222 <span class="ex">Downloading</span>: 100%</code></pre></div>
223<p>Optionally, you can delete information related to the legacy version:</p>
224<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">git</span> branch -D master
225<span class="ex">Deleted</span> branch master (was 029f75f)<span class="ex">.</span>
226
227$ <span class="fu">git</span> remote remove sebsauvage
228
229$ <span class="fu">git</span> remote -v
230<span class="ex">origin</span> https://github.com/shaarli/Shaarli (fetch)
231<span class="ex">origin</span> https://github.com/shaarli/Shaarli (push)
232
233$ <span class="fu">git</span> gc
234<span class="ex">Counting</span> objects: 3317, done.
235<span class="ex">Delta</span> compression using up to 8 threads.
236<span class="ex">Compressing</span> objects: 100% (1237/1237), <span class="kw">done</span><span class="ex">.</span>
237<span class="ex">Writing</span> objects: 100% (3317/3317), <span class="kw">done</span><span class="ex">.</span>
238<span class="ex">Total</span> 3317 (delta 2050), <span class="ex">reused</span> 3301 (delta 2034)<span class="ex">to</span></code></pre></div>
239<h4 id="step-3-configuration">Step 3: configuration</h4>
240<p>After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to <code>data/config.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p>
241</body>
242</html>
diff --git a/doc/Upgrade-and-migration.md b/doc/Upgrade-and-migration.md
new file mode 100644
index 00000000..0bc33824
--- /dev/null
+++ b/doc/Upgrade-and-migration.md
@@ -0,0 +1,161 @@
1#Upgrade and migration
2## Preparation
3### Backup your data
4
5Shaarli stores all user data under the `data` directory:
6- `data/config.php` - main configuration file
7- `data/datastore.php` - bookmarked links
8- `data/ipbans.php` - banned IP addresses
9
10See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources.
11
12It is recommended to backup this repository _before_ starting updating/upgrading Shaarli:
13- users with SSH access: copy or archive the directory to a temporary location
14- users with FTP access: download a local copy of your Shaarli installation using your favourite client
15
16### Migrating data from a previous installation
17As all user data is kept under `data`, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps:
18
19- backup the `data` directory
20- install or update Shaarli:
21 - fresh installation - see [Download and installation](Download-and-installation.html)
22 - update - see the following sections
23- check or restore the `data` directory
24
25## Upgrading from release archives
26All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html)
27
28We _recommend_ using the releases from the `stable` branch, which are available as:
29- gzipped tarball - https://github.com/shaarli/Shaarli/archive/stable.tar.gz
30- ZIP archive - https://github.com/shaarli/Shaarli/archive/stable.zip
31
32Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the `data` directory!
33
34After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details).
35
36## Upgrading with Git
37### Updating a community Shaarli
38If you have installed Shaarli from the [community Git repository](Download#clone-with-git-recommended), simply [pull new changes](https://www.git-scm.com/docs/git-pull) from your local clone:[](.html)
39
40```bash
41$ cd /path/to/shaarli
42$ git pull
43
44From github.com:shaarli/Shaarli
45 * branch master -> FETCH_HEAD
46Updating ebd67c6..521f0e6
47Fast-forward
48 application/Url.php | 1 +
49 shaarli_version.php | 2 +-
50 tests/Url/UrlTest.php | 1 +
51 3 files changed, 3 insertions(+), 1 deletion(-)
52```
53
54Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html)
55
56```bash
57$ composer update --no-dev
58
59Loading composer repositories with package information
60Updating dependencies
61 - Installing shaarli/netscape-bookmark-parser (v1.0.1)
62 Downloading: 100%
63```
64
65### Migrating and upgrading from Sebsauvage's repository
66If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.[](.html)
67
68The following guide assumes that:
69- you have a basic knowledge of Git [branching](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) and [remote repositories](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes)[](.html)
70- the default remote is named `origin` and points to Sebsauvage's repository
71- the current branch is `master`
72 - if you have personal branches containing customizations, you will need to [rebase them](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) after the upgrade; beware though, a lot of changes have been made since the community fork has been created, so things are very likely to break![](.html)
73- the working copy is clean:
74 - no versioned file has been locally modified
75 - no untracked files are present
76
77#### Step 0: show repository information
78```bash
79$ cd /path/to/shaarli
80
81$ git remote -v
82origin https://github.com/sebsauvage/Shaarli (fetch)
83origin https://github.com/sebsauvage/Shaarli (push)
84
85$ git branch -vv
86* master 029f75f [origin/master] Update README.md[](.html)
87
88$ git status
89On branch master
90Your branch is up-to-date with 'origin/master'.
91nothing to commit, working directory clean
92```
93
94#### Step 1: update Git remotes
95```
96$ git remote rename origin sebsauvage
97$ git remote -v
98sebsauvage https://github.com/sebsauvage/Shaarli (fetch)
99sebsauvage https://github.com/sebsauvage/Shaarli (push)
100
101$ git remote add origin https://github.com/shaarli/Shaarli
102$ git fetch origin
103
104remote: Counting objects: 3015, done.
105remote: Compressing objects: 100% (19/19), done.
106remote: Total 3015 (delta 446), reused 457 (delta 446), pack-reused 2550
107Receiving objects: 100% (3015/3015), 2.59 MiB | 918.00 KiB/s, done.
108Resolving deltas: 100% (1899/1899), completed with 48 local objects.
109From https://github.com/shaarli/Shaarli
110 * [new branch] master -> origin/master[](.html)
111 * [new branch] stable -> origin/stable[](.html)
112[...][](.html)
113 * [new tag] v0.6.4 -> v0.6.4[](.html)
114 * [new tag] v0.7.0 -> v0.7.0[](.html)
115```
116
117#### Step 2: use the stable community branch
118
119```bash
120$ git checkout origin/stable -b stable
121Branch stable set up to track remote branch stable from origin.
122Switched to a new branch 'stable'
123
124$ git branch -vv
125 master 029f75f [sebsauvage/master] Update README.md[](.html)
126* stable 890afc3 [origin/stable] Merge pull request #509 from ArthurHoaro/v0.6.5[](.html)
127```
128
129Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html)
130
131```bash
132$ composer update --no-dev
133
134Loading composer repositories with package information
135Updating dependencies
136 - Installing shaarli/netscape-bookmark-parser (v1.0.1)
137 Downloading: 100%
138```
139
140Optionally, you can delete information related to the legacy version:
141
142```bash
143$ git branch -D master
144Deleted branch master (was 029f75f).
145
146$ git remote remove sebsauvage
147
148$ git remote -v
149origin https://github.com/shaarli/Shaarli (fetch)
150origin https://github.com/shaarli/Shaarli (push)
151
152$ git gc
153Counting objects: 3317, done.
154Delta compression using up to 8 threads.
155Compressing objects: 100% (1237/1237), done.
156Writing objects: 100% (3317/3317), done.
157Total 3317 (delta 2050), reused 3301 (delta 2034)to
158```
159
160#### Step 3: configuration
161After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details).
diff --git a/doc/Upgrade-from-original-sebsauvage-Shaarli.html b/doc/Upgrade-from-original-sebsauvage-Shaarli.html
deleted file mode 100644
index db69a0ed..00000000
--- a/doc/Upgrade-from-original-sebsauvage-Shaarli.html
+++ /dev/null
@@ -1,74 +0,0 @@
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="generator" content="pandoc">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
7 <title>Shaarli – Upgrade from original sebsauvage Shaarli</title>
8 <style type="text/css">code{white-space: pre;}</style>
9 <link rel="stylesheet" href="github-markdown.css">
10 <!--[if lt IE 9]>
11 <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
12 <![endif]-->
13</head>
14<body>
15<div id="local-sidebar">
16<ul>
17<li><a href="Home.html">Home</a></li>
18<li>Installation
19<ul>
20<li><a href="Download.html">Download</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li>
28<li><a href="Docker.html">Docker</a></li>
29<li><a href="Usage.html">Usage</a>
30<ul>
31<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
32<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
33<li><a href="Firefox-share.html">Firefox share</a></li>
34<li><a href="RSS-feeds.html">RSS feeds</a></li>
35</ul></li>
36<li>How To
37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
43<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
44</ul></li>
45<li><a href="Troubleshooting.html">Troubleshooting</a></li>
46<li><a href="Development.html">Development</a>
47<ul>
48<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
49<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
50<li><a href="Directory-structure.html">Directory structure</a></li>
51<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
52<li><a href="Plugin-System.html">Plugin System</a></li>
53<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
54<li><a href="Security.html">Security</a></li>
55<li><a href="Static-analysis.html">Static analysis</a></li>
56<li><a href="Theming.html">Theming</a></li>
57<li><a href="Unit-tests.html">Unit tests</a></li>
58</ul></li>
59<li>About
60<ul>
61<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li>
65</ul>
66</div>
67<h1 id="upgrade-from-original-sebsauvage-shaarli">Upgrade from original sebsauvage Shaarli</h1>
68<ul>
69<li>Backup your original <code>data/</code> directory.</li>
70<li><a href="https://github.com/shaarli/Shaarli#installation--upgrade">Install</a> and setup the Shaarli community fork.<a href=".html"></a></li>
71<li>Copy your original <code>data</code> directory over the new installation.</li>
72</ul>
73</body>
74</html>
diff --git a/doc/Upgrade-from-original-sebsauvage-Shaarli.md b/doc/Upgrade-from-original-sebsauvage-Shaarli.md
deleted file mode 100644
index 6ae0c67b..00000000
--- a/doc/Upgrade-from-original-sebsauvage-Shaarli.md
+++ /dev/null
@@ -1,4 +0,0 @@
1#Upgrade from original sebsauvage Shaarli
2 * Backup your original `data/` directory.
3 * [Install](https://github.com/shaarli/Shaarli#installation--upgrade) and setup the Shaarli community fork.[](.html)
4 * Copy your original `data` directory over the new installation.
diff --git a/doc/Usage.html b/doc/Usage.html
index 2befaa02..63f21d93 100644
--- a/doc/Usage.html
+++ b/doc/Usage.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,7 +59,6 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
diff --git a/doc/_Footer.html b/doc/_Footer.html
index a054cc53..e8a62d2a 100644
--- a/doc/_Footer.html
+++ b/doc/_Footer.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,11 +59,10 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
67<h1 id="footer-shaarli-the-personal-minimalist-super-fast-no-database-delicious-clone">_Footer<br /> 65<h1 id="footer-shaarli-the-personal-minimalist-super-fast-database-free-bookmarking-service">_Footer<br />
68<em>Shaarli, the personal, minimalist, super-fast, no-database delicious clone</em></h1> 66<em>Shaarli, the personal, minimalist, super-fast, database-free bookmarking service</em></h1>
69</body> 67</body>
70</html> 68</html>
diff --git a/doc/_Footer.md b/doc/_Footer.md
index 29c39bb6..50fa4f56 100644
--- a/doc/_Footer.md
+++ b/doc/_Footer.md
@@ -1,2 +1,2 @@
1#_Footer 1#_Footer
2_Shaarli, the personal, minimalist, super-fast, no-database delicious clone_ 2_Shaarli, the personal, minimalist, super-fast, database-free bookmarking service_
diff --git a/doc/_Sidebar.html b/doc/_Sidebar.html
index 89c2cf8a..bb6dad93 100644
--- a/doc/_Sidebar.html
+++ b/doc/_Sidebar.html
@@ -15,13 +15,13 @@
15<div id="local-sidebar"> 15<div id="local-sidebar">
16<ul> 16<ul>
17<li><a href="Home.html">Home</a></li> 17<li><a href="Home.html">Home</a></li>
18<li>Installation 18<li>Setup
19<ul> 19<ul>
20<li><a href="Download.html">Download</a></li> 20<li><a href="Download-and-Installation.html">Download and Installation</a></li>
21<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
21<li><a href="Server-requirements.html">Server requirements</a></li> 22<li><a href="Server-requirements.html">Server requirements</a></li>
22<li><a href="Server-configuration.html">Server configuration</a></li> 23<li><a href="Server-configuration.html">Server configuration</a></li>
23<li><a href="Server-security.html">Server security</a></li> 24<li><a href="Server-security.html">Server security</a></li>
24<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 25<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
26<li><a href="Plugins.html">Plugins</a></li> 26<li><a href="Plugins.html">Plugins</a></li>
27</ul></li> 27</ul></li>
@@ -36,7 +36,6 @@
36<li>How To 36<li>How To
37<ul> 37<ul>
38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 38<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
39<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
40<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 39<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
41<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 40<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
42<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 41<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -60,20 +59,19 @@
60<ul> 59<ul>
61<li><a href="FAQ.html">FAQ</a></li> 60<li><a href="FAQ.html">FAQ</a></li>
62<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 61<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
63<li><a href="TODO.html">TODO</a></li>
64</ul></li> 62</ul></li>
65</ul> 63</ul>
66</div> 64</div>
67<h1 id="sidebar">_Sidebar</h1> 65<h1 id="sidebar">_Sidebar</h1>
68<ul> 66<ul>
69<li><a href="Home.html">Home</a></li> 67<li><a href="Home.html">Home</a></li>
70<li>Installation 68<li>Setup
71<ul> 69<ul>
72<li><a href="Download.html">Download</a></li> 70<li><a href="Download-and-Installation.html">Download and Installation</a></li>
71<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
73<li><a href="Server-requirements.html">Server requirements</a></li> 72<li><a href="Server-requirements.html">Server requirements</a></li>
74<li><a href="Server-configuration.html">Server configuration</a></li> 73<li><a href="Server-configuration.html">Server configuration</a></li>
75<li><a href="Server-security.html">Server security</a></li> 74<li><a href="Server-security.html">Server security</a></li>
76<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
77<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 75<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
78<li><a href="Plugins.html">Plugins</a></li> 76<li><a href="Plugins.html">Plugins</a></li>
79</ul></li> 77</ul></li>
@@ -88,7 +86,6 @@
88<li>How To 86<li>How To
89<ul> 87<ul>
90<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 88<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
91<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
92<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 89<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
93<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 90<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
94<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 91<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -112,7 +109,6 @@
112<ul> 109<ul>
113<li><a href="FAQ.html">FAQ</a></li> 110<li><a href="FAQ.html">FAQ</a></li>
114<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 111<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
115<li><a href="TODO.html">TODO</a></li>
116</ul></li> 112</ul></li>
117</ul> 113</ul>
118</body> 114</body>
diff --git a/doc/_Sidebar.md b/doc/_Sidebar.md
index 7c71f462..1778e3a3 100644
--- a/doc/_Sidebar.md
+++ b/doc/_Sidebar.md
@@ -1,11 +1,11 @@
1#_Sidebar 1#_Sidebar
2- [Home](Home.html) 2- [Home](Home.html)
3- Installation 3- Setup
4 - [Download](Download.html) 4 - [Download and Installation](Download-and-Installation.html)
5 - [Upgrade and migration](Upgrade-and-migration.html)
5 - [Server requirements](Server-requirements.html) 6 - [Server requirements](Server-requirements.html)
6 - [Server configuration](Server-configuration.html) 7 - [Server configuration](Server-configuration.html)
7 - [Server security](Server-security.html) 8 - [Server security](Server-security.html)
8 - [Shaarli installation](Shaarli-installation.html)
9 - [Shaarli configuration](Shaarli-configuration.html) 9 - [Shaarli configuration](Shaarli-configuration.html)
10 - [Plugins](Plugins.html) 10 - [Plugins](Plugins.html)
11- [Docker](Docker.html) 11- [Docker](Docker.html)
@@ -16,7 +16,6 @@
16 - [RSS feeds](RSS-feeds.html) 16 - [RSS feeds](RSS-feeds.html)
17- How To 17- How To
18 - [Backup, restore, import and export](Backup,-restore,-import-and-export.html) 18 - [Backup, restore, import and export](Backup,-restore,-import-and-export.html)
19 - [Upgrade from original sebsauvage/Shaarli](Upgrade-from-original-sebsauvage/Shaarli.html)
20 - [Copy an existing installation over SSH and serve it locally](Copy-an-existing-installation-over-SSH-and-serve-it-locally.html) 19 - [Copy an existing installation over SSH and serve it locally](Copy-an-existing-installation-over-SSH-and-serve-it-locally.html)
21 - [Create and serve multiple Shaarlis (farm)](Create-and-serve-multiple-Shaarlis-(farm).html) 20 - [Create and serve multiple Shaarlis (farm)](Create-and-serve-multiple-Shaarlis-(farm).html)
22 - [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html) 21 - [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html)
@@ -36,4 +35,3 @@
36- About 35- About
37 - [FAQ](FAQ.html) 36 - [FAQ](FAQ.html)
38 - [Community & Related software](Community-&-Related-software.html) 37 - [Community & Related software](Community-&-Related-software.html)
39 - [TODO](TODO.html)
diff --git a/doc/sidebar.html b/doc/sidebar.html
index 62844581..4dad0161 100644
--- a/doc/sidebar.html
+++ b/doc/sidebar.html
@@ -1,13 +1,13 @@
1<div id="local-sidebar"> 1<div id="local-sidebar">
2<ul> 2<ul>
3<li><a href="Home.html">Home</a></li> 3<li><a href="Home.html">Home</a></li>
4<li>Installation 4<li>Setup
5<ul> 5<ul>
6<li><a href="Download.html">Download</a></li> 6<li><a href="Download-and-Installation.html">Download and Installation</a></li>
7<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
7<li><a href="Server-requirements.html">Server requirements</a></li> 8<li><a href="Server-requirements.html">Server requirements</a></li>
8<li><a href="Server-configuration.html">Server configuration</a></li> 9<li><a href="Server-configuration.html">Server configuration</a></li>
9<li><a href="Server-security.html">Server security</a></li> 10<li><a href="Server-security.html">Server security</a></li>
10<li><a href="Shaarli-installation.html">Shaarli installation</a></li>
11<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li> 11<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
12<li><a href="Plugins.html">Plugins</a></li> 12<li><a href="Plugins.html">Plugins</a></li>
13</ul></li> 13</ul></li>
@@ -22,7 +22,6 @@
22<li>How To 22<li>How To
23<ul> 23<ul>
24<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li> 24<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
25<li><a href="Upgrade-from-original-sebsauvage/Shaarli.html">Upgrade from original sebsauvage/Shaarli</a></li>
26<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li> 25<li><a href="Copy-an-existing-installation-over-SSH-and-serve-it-locally.html">Copy an existing installation over SSH and serve it locally</a></li>
27<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li> 26<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
28<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> 27<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
@@ -46,7 +45,6 @@
46<ul> 45<ul>
47<li><a href="FAQ.html">FAQ</a></li> 46<li><a href="FAQ.html">FAQ</a></li>
48<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li> 47<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
49<li><a href="TODO.html">TODO</a></li>
50</ul></li> 48</ul></li>
51</ul> 49</ul>
52</div> 50</div>
diff --git a/docker/.htaccess b/docker/.htaccess
index b584d98c..f601c1ee 100644
--- a/docker/.htaccess
+++ b/docker/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile
index 2ed59b89..d9ef8da7 100644
--- a/docker/development/Dockerfile
+++ b/docker/development/Dockerfile
@@ -1,26 +1,36 @@
1FROM debian:jessie 1FROM debian:jessie
2MAINTAINER Shaarli Community 2MAINTAINER Shaarli Community
3 3
4ENV TERM dumb
4RUN apt-get update \ 5RUN apt-get update \
5 && apt-get install -y \ 6 && apt-get install --no-install-recommends -y \
6 nginx-light php5-fpm php5-gd supervisor \ 7 ca-certificates \
7 git nano 8 nginx-light \
9 php5-curl \
10 php5-fpm \
11 php5-gd \
12 php5-intl \
13 supervisor \
14 git \
15 nano \
16 && apt-get clean
17
18RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
19RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
20COPY nginx.conf /etc/nginx/nginx.conf
21COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
8 22
9ADD https://getcomposer.org/composer.phar /usr/local/bin/composer 23ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
10RUN chmod 755 /usr/local/bin/composer 24RUN chmod 755 /usr/local/bin/composer
11 25
12COPY nginx.conf /etc/nginx/nginx.conf
13COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
14RUN echo "<?php phpinfo(); ?>" > /var/www/index.php
15
16WORKDIR /var/www 26WORKDIR /var/www
27RUN git clone https://github.com/shaarli/Shaarli.git shaarli \
28 && cd shaarli \
29 && composer --prefer-dist install
17RUN rm -rf html \ 30RUN rm -rf html \
18 && git clone https://github.com/shaarli/Shaarli.git shaarli \ 31 && echo "<?php phpinfo(); ?>" > index.php \
19 && chown -R www-data:www-data . 32 && chown -R www-data:www-data .
20 33
21WORKDIR /var/www/shaarli
22RUN composer install
23
24VOLUME /var/www/shaarli/data 34VOLUME /var/www/shaarli/data
25 35
26EXPOSE 80 36EXPOSE 80
diff --git a/docker/development/nginx.conf b/docker/development/nginx.conf
index cda09b56..ac0c6c61 100644
--- a/docker/development/nginx.conf
+++ b/docker/development/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -49,6 +51,11 @@ http {
49 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 51 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
50 } 52 }
51 53
54 location = /favicon.ico {
55 # serve the Shaarli favicon from its custom location
56 alias /var/www/shaarli/images/favicon.ico;
57 }
58
52 location ~ (index)\.php$ { 59 location ~ (index)\.php$ {
53 # filter and proxy PHP requests to PHP-FPM 60 # filter and proxy PHP requests to PHP-FPM
54 fastcgi_pass unix:/var/run/php5-fpm.sock; 61 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile
index 3db4eb56..d0509115 100644
--- a/docker/production/Dockerfile
+++ b/docker/production/Dockerfile
@@ -1,17 +1,34 @@
1FROM debian:jessie 1FROM debian:jessie
2MAINTAINER Shaarli Community 2MAINTAINER Shaarli Community
3 3
4ENV TERM dumb
4RUN apt-get update \ 5RUN apt-get update \
5 && apt-get install -y curl nginx-light php5-fpm php5-gd supervisor 6 && apt-get install --no-install-recommends -y \
7 ca-certificates \
8 curl \
9 nginx-light \
10 php5-curl \
11 php5-fpm \
12 php5-gd \
13 php5-intl \
14 supervisor \
15 && apt-get clean
6 16
17RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
18RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
7COPY nginx.conf /etc/nginx/nginx.conf 19COPY nginx.conf /etc/nginx/nginx.conf
8COPY supervised.conf /etc/supervisor/conf.d/supervised.conf 20COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
9 21
22ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
23RUN chmod 755 /usr/local/bin/composer
24
10WORKDIR /var/www 25WORKDIR /var/www
11RUN rm -rf html \ 26RUN curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xzf - \
12 && curl -L https://github.com/shaarli/Shaarli/archive/master.tar.gz | tar xvzf - \
13 && mv Shaarli-master shaarli \ 27 && mv Shaarli-master shaarli \
14 && chown -R www-data:www-data shaarli 28 && cd shaarli \
29 && composer --prefer-dist --no-dev install
30RUN rm -rf html \
31 && chown -R www-data:www-data .
15 32
16VOLUME /var/www/shaarli/data 33VOLUME /var/www/shaarli/data
17 34
diff --git a/docker/production/nginx.conf b/docker/production/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/nginx.conf
+++ b/docker/production/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -41,6 +43,11 @@ http {
41 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 43 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
42 } 44 }
43 45
46 location = /favicon.ico {
47 # serve the Shaarli favicon from its custom location
48 alias /var/www/shaarli/images/favicon.ico;
49 }
50
44 location ~ (index)\.php$ { 51 location ~ (index)\.php$ {
45 # filter and proxy PHP requests to PHP-FPM 52 # filter and proxy PHP requests to PHP-FPM
46 fastcgi_pass unix:/var/run/php5-fpm.sock; 53 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/stable/Dockerfile b/docker/production/stable/Dockerfile
index 2bb3948c..fc9588b0 100644
--- a/docker/production/stable/Dockerfile
+++ b/docker/production/stable/Dockerfile
@@ -1,17 +1,34 @@
1FROM debian:jessie 1FROM debian:jessie
2MAINTAINER Shaarli Community 2MAINTAINER Shaarli Community
3 3
4ENV TERM dumb
4RUN apt-get update \ 5RUN apt-get update \
5 && apt-get install -y curl nginx-light php5-fpm php5-gd supervisor 6 && apt-get install --no-install-recommends -y \
7 ca-certificates \
8 curl \
9 nginx-light \
10 php5-curl \
11 php5-fpm \
12 php5-gd \
13 php5-intl \
14 supervisor \
15 && apt-get clean
6 16
17RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
18RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
7COPY nginx.conf /etc/nginx/nginx.conf 19COPY nginx.conf /etc/nginx/nginx.conf
8COPY supervised.conf /etc/supervisor/conf.d/supervised.conf 20COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
9 21
22ADD https://getcomposer.org/composer.phar /usr/local/bin/composer
23RUN chmod 755 /usr/local/bin/composer
24
10WORKDIR /var/www 25WORKDIR /var/www
11RUN rm -rf html \ 26RUN curl -L https://github.com/shaarli/Shaarli/archive/stable.tar.gz | tar xzf - \
12 && curl -L https://github.com/shaarli/Shaarli/archive/stable.tar.gz | tar xvzf - \
13 && mv Shaarli-stable shaarli \ 27 && mv Shaarli-stable shaarli \
14 && chown -R www-data:www-data shaarli 28 && cd shaarli \
29 && composer --prefer-dist --no-dev install
30RUN rm -rf html \
31 && chown -R www-data:www-data .
15 32
16VOLUME /var/www/shaarli/data 33VOLUME /var/www/shaarli/data
17 34
diff --git a/docker/production/stable/nginx.conf b/docker/production/stable/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/stable/nginx.conf
+++ b/docker/production/stable/nginx.conf
@@ -11,6 +11,8 @@ http {
11 default_type application/octet-stream; 11 default_type application/octet-stream;
12 keepalive_timeout 20; 12 keepalive_timeout 20;
13 13
14 client_max_body_size 10m;
15
14 index index.html index.php; 16 index index.html index.php;
15 17
16 server { 18 server {
@@ -41,6 +43,11 @@ http {
41 add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 43 add_header Cache-Control "public, must-revalidate, proxy-revalidate";
42 } 44 }
43 45
46 location = /favicon.ico {
47 # serve the Shaarli favicon from its custom location
48 alias /var/www/shaarli/images/favicon.ico;
49 }
50
44 location ~ (index)\.php$ { 51 location ~ (index)\.php$ {
45 # filter and proxy PHP requests to PHP-FPM 52 # filter and proxy PHP requests to PHP-FPM
46 fastcgi_pass unix:/var/run/php5-fpm.sock; 53 fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/inc/shaarli.css b/inc/shaarli.css
index 305afddc..a24d4b7c 100644
--- a/inc/shaarli.css
+++ b/inc/shaarli.css
@@ -37,6 +37,10 @@ em {
37 font-style: italic; 37 font-style: italic;
38} 38}
39 39
40strong {
41 font-weight: bold;
42}
43
40/* Buttons */ 44/* Buttons */
41.bigbutton { 45.bigbutton {
42 background-color: #c0c0c0; 46 background-color: #c0c0c0;
@@ -1156,7 +1160,7 @@ ul.errors {
1156} 1160}
1157 1161
1158#pluginsadmin .plugin_parameter { 1162#pluginsadmin .plugin_parameter {
1159 padding: 5px 0; 1163 padding: 10px 0;
1160 border-width: 1px 0; 1164 border-width: 1px 0;
1161 border-style: solid; 1165 border-style: solid;
1162 border-color: #c0c0c0; 1166 border-color: #c0c0c0;
@@ -1164,12 +1168,17 @@ ul.errors {
1164 1168
1165#pluginsadmin .float_label { 1169#pluginsadmin .float_label {
1166 float: left; 1170 float: left;
1167 width: 20%; 1171 width: 40%;
1168} 1172}
1169 1173
1170#pluginsadmin a { 1174#pluginsadmin a {
1175 color: #486D08;
1176}
1177
1178#pluginsadmin a.arrow {
1171 color: black; 1179 color: black;
1172} 1180}
1181
1173/* 404 page */ 1182/* 404 page */
1174.error-container { 1183.error-container {
1175 1184
diff --git a/index.php b/index.php
index 7465c41f..b4ccd1bd 100644
--- a/index.php
+++ b/index.php
@@ -1,8 +1,8 @@
1<?php 1<?php
2/** 2/**
3 * Shaarli v0.7.0 - Shaare your links... 3 * Shaarli v0.8.4 - Shaare your links...
4 * 4 *
5 * The personal, minimalist, super-fast, no-database Delicious clone. 5 * The personal, minimalist, super-fast, database free, bookmarking service.
6 * 6 *
7 * Friendly fork by the Shaarli community: 7 * Friendly fork by the Shaarli community:
8 * - https://github.com/shaarli/Shaarli 8 * - https://github.com/shaarli/Shaarli
@@ -22,114 +22,13 @@ if (date_default_timezone_get() == '') {
22 date_default_timezone_set('UTC'); 22 date_default_timezone_set('UTC');
23} 23}
24 24
25/* -----------------------------------------------------------------------------
26 * Hardcoded parameters
27 * You should not touch any code below (or at your own risks!)
28 * (These parameters can be overwritten by editing the file /data/config.php)
29 * -----------------------------------------------------------------------------
30 */
31
32/*
33 * Shaarli directories & configuration files
34 */
35// Data subdirectory
36$GLOBALS['config']['DATADIR'] = 'data';
37
38// Main configuration file
39$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php';
40
41// Link datastore
42$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php';
43
44// Banned IPs
45$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php';
46
47// Processed updates file.
48$GLOBALS['config']['UPDATES_FILE'] = $GLOBALS['config']['DATADIR'].'/updates.txt';
49
50// Access log
51$GLOBALS['config']['LOG_FILE'] = $GLOBALS['config']['DATADIR'].'/log.txt';
52
53// For updates check of Shaarli
54$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
55
56// Set ENABLE_UPDATECHECK to disabled by default.
57$GLOBALS['config']['ENABLE_UPDATECHECK'] = false;
58
59// RainTPL cache directory (keep the trailing slash!)
60$GLOBALS['config']['RAINTPL_TMP'] = 'tmp/';
61// Raintpl template directory (keep the trailing slash!)
62$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
63
64// Thumbnail cache directory
65$GLOBALS['config']['CACHEDIR'] = 'cache';
66
67// Atom & RSS feed cache directory
68$GLOBALS['config']['PAGECACHE'] = 'pagecache';
69
70/*
71 * Global configuration
72 */
73// Ban IP after this many failures
74$GLOBALS['config']['BAN_AFTER'] = 4;
75// Ban duration for IP address after login failures (in seconds)
76$GLOBALS['config']['BAN_DURATION'] = 1800;
77
78// Feed options
79// Enable RSS permalinks by default.
80// This corresponds to the default behavior of shaarli before this was added as an option.
81$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true;
82// If true, an extra "ATOM feed" button will be displayed in the toolbar
83$GLOBALS['config']['SHOW_ATOM'] = false;
84
85// Link display options
86$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
87$GLOBALS['config']['HIDE_TIMESTAMPS'] = false;
88$GLOBALS['config']['LINKS_PER_PAGE'] = 20;
89
90// Open Shaarli (true): anyone can add/edit/delete links without having to login
91$GLOBALS['config']['OPEN_SHAARLI'] = false;
92
93// Thumbnails
94// Display thumbnails in links
95$GLOBALS['config']['ENABLE_THUMBNAILS'] = true;
96// Store thumbnails in a local cache
97$GLOBALS['config']['ENABLE_LOCALCACHE'] = true;
98
99// Update check frequency for Shaarli. 86400 seconds=24 hours
100$GLOBALS['config']['UPDATECHECK_BRANCH'] = 'stable';
101$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400;
102
103$GLOBALS['config']['REDIRECTOR_URLENCODE'] = true;
104
105/*
106 * Plugin configuration
107 *
108 * Warning: order matters!
109 *
110 * These settings may be be overriden in:
111 * - data/config.php
112 * - each plugin's configuration file
113 */
114//$GLOBALS['config']['ENABLED_PLUGINS'] = array(
115// 'qrcode', 'archiveorg', 'readityourself', 'demo_plugin', 'playvideos',
116// 'wallabag', 'markdown', 'addlink_toolbar',
117//);
118$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode');
119
120// Initialize plugin parameters array.
121$GLOBALS['plugins'] = array();
122
123// PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable.
124$GLOBALS['config']['PUBSUBHUB_URL'] = '';
125
126/* 25/*
127 * PHP configuration 26 * PHP configuration
128 */ 27 */
129define('shaarli_version', '0.7.0'); 28define('shaarli_version', '0.8.2');
130 29
131// http://server.com/x/shaarli --> /shaarli/ 30// http://server.com/x/shaarli --> /shaarli/
132define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0))); 31define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
133 32
134// High execution time in case of problematic imports/exports. 33// High execution time in case of problematic imports/exports.
135ini_set('max_input_time','60'); 34ini_set('max_input_time','60');
@@ -144,20 +43,31 @@ error_reporting(E_ALL^E_WARNING);
144// See all errors (for debugging only) 43// See all errors (for debugging only)
145//error_reporting(-1); 44//error_reporting(-1);
146 45
147/* 46
148 * User configuration 47// 3rd-party libraries
149 */ 48if (! file_exists(__DIR__ . '/vendor/autoload.php')) {
150if (is_file($GLOBALS['config']['CONFIG_FILE'])) { 49 header('Content-Type: text/plain; charset=utf-8');
151 require_once $GLOBALS['config']['CONFIG_FILE']; 50 echo "Error: missing Composer configuration\n\n"
51 ."If you installed Shaarli through Git or using the development branch,\n"
52 ."please refer to the installation documentation to install PHP"
53 ." dependencies using Composer:\n"
54 ."- https://github.com/shaarli/Shaarli/wiki/Server-requirements\n"
55 ."- https://github.com/shaarli/Shaarli/wiki/Download-and-Installation";
56 exit;
152} 57}
58require_once 'inc/rain.tpl.class.php';
59require_once __DIR__ . '/vendor/autoload.php';
153 60
154// Shaarli library 61// Shaarli library
155require_once 'application/ApplicationUtils.php'; 62require_once 'application/ApplicationUtils.php';
156require_once 'application/Cache.php'; 63require_once 'application/Cache.php';
157require_once 'application/CachedPage.php'; 64require_once 'application/CachedPage.php';
65require_once 'application/config/ConfigManager.php';
66require_once 'application/config/ConfigPlugin.php';
158require_once 'application/FeedBuilder.php'; 67require_once 'application/FeedBuilder.php';
159require_once 'application/FileUtils.php'; 68require_once 'application/FileUtils.php';
160require_once 'application/HttpUtils.php'; 69require_once 'application/HttpUtils.php';
70require_once 'application/Languages.php';
161require_once 'application/LinkDB.php'; 71require_once 'application/LinkDB.php';
162require_once 'application/LinkFilter.php'; 72require_once 'application/LinkFilter.php';
163require_once 'application/LinkUtils.php'; 73require_once 'application/LinkUtils.php';
@@ -166,7 +76,6 @@ require_once 'application/PageBuilder.php';
166require_once 'application/TimeZone.php'; 76require_once 'application/TimeZone.php';
167require_once 'application/Url.php'; 77require_once 'application/Url.php';
168require_once 'application/Utils.php'; 78require_once 'application/Utils.php';
169require_once 'application/Config.php';
170require_once 'application/PluginManager.php'; 79require_once 'application/PluginManager.php';
171require_once 'application/Router.php'; 80require_once 'application/Router.php';
172require_once 'application/Updater.php'; 81require_once 'application/Updater.php';
@@ -210,15 +119,18 @@ if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
210 $_COOKIE['shaarli'] = session_id(); 119 $_COOKIE['shaarli'] = session_id();
211} 120}
212 121
213include "inc/rain.tpl.class.php"; //include Rain TPL 122$conf = new ConfigManager();
214raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory 123$conf->setEmpty('general.timezone', date_default_timezone_get());
215raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory 124$conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER)));
125RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl'); // template directory
126RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory
216 127
217$pluginManager = PluginManager::getInstance(); 128$pluginManager = new PluginManager($conf);
218$pluginManager->load($GLOBALS['config']['ENABLED_PLUGINS']); 129$pluginManager->load($conf->get('general.enabled_plugins'));
219 130
220ob_start(); // Output buffering for the page cache. 131date_default_timezone_set($conf->get('general.timezone', 'UTC'));
221 132
133ob_start(); // Output buffering for the page cache.
222 134
223// In case stupid admin has left magic_quotes enabled in php.ini: 135// In case stupid admin has left magic_quotes enabled in php.ini:
224if (get_magic_quotes_gpc()) 136if (get_magic_quotes_gpc())
@@ -235,18 +147,9 @@ header("Cache-Control: no-store, no-cache, must-revalidate");
235header("Cache-Control: post-check=0, pre-check=0", false); 147header("Cache-Control: post-check=0, pre-check=0", false);
236header("Pragma: no-cache"); 148header("Pragma: no-cache");
237 149
238// Handling of old config file which do not have the new parameters. 150if (! is_file($conf->getConfigFileExt())) {
239if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
240if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
241if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
242if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
243if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
244if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
245// I really need to rewrite Shaarli with a proper configuation manager.
246
247if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
248 // Ensure Shaarli has proper access to its resources 151 // Ensure Shaarli has proper access to its resources
249 $errors = ApplicationUtils::checkResourcePermissions($GLOBALS['config']); 152 $errors = ApplicationUtils::checkResourcePermissions($conf);
250 153
251 if ($errors != array()) { 154 if ($errors != array()) {
252 $message = '<p>Insufficient permissions:</p><ul>'; 155 $message = '<p>Insufficient permissions:</p><ul>';
@@ -262,15 +165,11 @@ if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
262 } 165 }
263 166
264 // Display the installation form if no existing config is found 167 // Display the installation form if no existing config is found
265 install(); 168 install($conf);
266} 169}
267 170
268$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : '';
269$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : '';
270$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : '';
271
272// a token depending of deployment salt, user password, and the current ip 171// a token depending of deployment salt, user password, and the current ip
273define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GLOBALS['salt'])); 172define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
274 173
275// Sniff browser language and set date format accordingly. 174// Sniff browser language and set date format accordingly.
276if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 175if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
@@ -278,17 +177,21 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
278} 177}
279header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling. 178header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling.
280 179
281//================================================================================================== 180/**
282// Checking session state (i.e. is the user still logged in) 181 * Checking session state (i.e. is the user still logged in)
283//================================================================================================== 182 *
284 183 * @param ConfigManager $conf The configuration manager.
285function setup_login_state() { 184 *
286 if ($GLOBALS['config']['OPEN_SHAARLI']) { 185 * @return bool: true if the user is logged in, false otherwise.
186 */
187function setup_login_state($conf)
188{
189 if ($conf->get('security.open_shaarli')) {
287 return true; 190 return true;
288 } 191 }
289 $userIsLoggedIn = false; // By default, we do not consider the user as logged in; 192 $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
290 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met. 193 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
291 if (!isset($GLOBALS['login'])) { 194 if (! $conf->exists('credentials.login')) {
292 $userIsLoggedIn = false; // Shaarli is not configured yet. 195 $userIsLoggedIn = false; // Shaarli is not configured yet.
293 $loginFailure = true; 196 $loginFailure = true;
294 } 197 }
@@ -296,13 +199,13 @@ function setup_login_state() {
296 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN && 199 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
297 !$loginFailure) 200 !$loginFailure)
298 { 201 {
299 fillSessionInfo(); 202 fillSessionInfo($conf);
300 $userIsLoggedIn = true; 203 $userIsLoggedIn = true;
301 } 204 }
302 // If session does not exist on server side, or IP address has changed, or session has expired, logout. 205 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
303 if (empty($_SESSION['uid']) || 206 if (empty($_SESSION['uid'])
304 ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || 207 || ($conf->get('security.session_protection_disabled') == false && $_SESSION['ip'] != allIPs())
305 time() >= $_SESSION['expires_on']) 208 || time() >= $_SESSION['expires_on'])
306 { 209 {
307 logout(); 210 logout();
308 $userIsLoggedIn = false; 211 $userIsLoggedIn = false;
@@ -320,22 +223,26 @@ function setup_login_state() {
320 223
321 return $userIsLoggedIn; 224 return $userIsLoggedIn;
322} 225}
323$userIsLoggedIn = setup_login_state(); 226$userIsLoggedIn = setup_login_state($conf);
324 227
325// ------------------------------------------------------------------------------------------ 228/**
326// PubSubHubbub protocol support (if enabled) [UNTESTED] 229 * PubSubHubbub protocol support (if enabled) [UNTESTED]
327// (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ ) 230 * (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ )
328if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) include './publisher.php'; 231 *
329function pubsubhub() 232 * @param ConfigManager $conf Configuration Manager instance.
233 */
234function pubsubhub($conf)
330{ 235{
331 if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) 236 $pshUrl = $conf->get('config.PUBSUBHUB_URL');
237 if (!empty($pshUrl))
332 { 238 {
333 $p = new Publisher($GLOBALS['config']['PUBSUBHUB_URL']); 239 include_once './publisher.php';
334 $topic_url = array ( 240 $p = new Publisher($pshUrl);
335 index_url($_SERVER).'?do=atom', 241 $topic_url = array (
336 index_url($_SERVER).'?do=rss' 242 index_url($_SERVER).'?do=atom',
337 ); 243 index_url($_SERVER).'?do=rss'
338 $p->publish_update($topic_url); 244 );
245 $p->publish_update($topic_url);
339 } 246 }
340} 247}
341 248
@@ -345,32 +252,46 @@ function pubsubhub()
345// Returns the IP address of the client (Used to prevent session cookie hijacking.) 252// Returns the IP address of the client (Used to prevent session cookie hijacking.)
346function allIPs() 253function allIPs()
347{ 254{
348 $ip = $_SERVER["REMOTE_ADDR"]; 255 $ip = $_SERVER['REMOTE_ADDR'];
349 // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy. 256 // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
350 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; } 257 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
351 if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; } 258 if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
352 return $ip; 259 return $ip;
353} 260}
354 261
355function fillSessionInfo() { 262/**
263 * Load user session.
264 *
265 * @param ConfigManager $conf Configuration Manager instance.
266 */
267function fillSessionInfo($conf)
268{
356 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid) 269 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
357 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked. 270 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
358 $_SESSION['username']=$GLOBALS['login']; 271 $_SESSION['username']= $conf->get('credentials.login');
359 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration. 272 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
360} 273}
361 274
362// Check that user/password is correct. 275/**
363function check_auth($login,$password) 276 * Check that user/password is correct.
277 *
278 * @param string $login Username
279 * @param string $password User password
280 * @param ConfigManager $conf Configuration Manager instance.
281 *
282 * @return bool: authentication successful or not.
283 */
284function check_auth($login, $password, $conf)
364{ 285{
365 $hash = sha1($password.$login.$GLOBALS['salt']); 286 $hash = sha1($password . $login . $conf->get('credentials.salt'));
366 if ($login==$GLOBALS['login'] && $hash==$GLOBALS['hash']) 287 if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
367 { // Login/password is correct. 288 { // Login/password is correct.
368 fillSessionInfo(); 289 fillSessionInfo($conf);
369 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Login successful'); 290 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
370 return True; 291 return true;
371 } 292 }
372 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login); 293 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login);
373 return False; 294 return false;
374} 295}
375 296
376// Returns true if the user is logged in. 297// Returns true if the user is logged in.
@@ -395,34 +316,71 @@ function logout() {
395// ------------------------------------------------------------------------------------------ 316// ------------------------------------------------------------------------------------------
396// Brute force protection system 317// Brute force protection system
397// Several consecutive failed logins will ban the IP address for 30 minutes. 318// Several consecutive failed logins will ban the IP address for 30 minutes.
398if (!is_file($GLOBALS['config']['IPBANS_FILENAME'])) file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"); 319if (!is_file($conf->get('resource.ban_file', 'data/ipbans.php'))) {
399include $GLOBALS['config']['IPBANS_FILENAME']; 320 // FIXME! globals
400// Signal a failed login. Will ban the IP if too many failures: 321 file_put_contents(
401function ban_loginFailed() 322 $conf->get('resource.ban_file', 'data/ipbans.php'),
323 "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"
324 );
325}
326include $conf->get('resource.ban_file', 'data/ipbans.php');
327/**
328 * Signal a failed login. Will ban the IP if too many failures:
329 *
330 * @param ConfigManager $conf Configuration Manager instance.
331 */
332function ban_loginFailed($conf)
402{ 333{
403 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 334 $ip = $_SERVER['REMOTE_ADDR'];
404 if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0; 335 $trusted = $conf->get('security.trusted_proxies', array());
336 if (in_array($ip, $trusted)) {
337 $ip = getIpAddressFromProxy($_SERVER, $trusted);
338 if (!$ip) {
339 return;
340 }
341 }
342 $gb = $GLOBALS['IPBANS'];
343 if (! isset($gb['FAILURES'][$ip])) {
344 $gb['FAILURES'][$ip]=0;
345 }
405 $gb['FAILURES'][$ip]++; 346 $gb['FAILURES'][$ip]++;
406 if ($gb['FAILURES'][$ip]>($GLOBALS['config']['BAN_AFTER']-1)) 347 if ($gb['FAILURES'][$ip] > ($conf->get('security.ban_after') - 1))
407 { 348 {
408 $gb['BANS'][$ip]=time()+$GLOBALS['config']['BAN_DURATION']; 349 $gb['BANS'][$ip] = time() + $conf->get('security.ban_after', 1800);
409 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'IP address banned from login'); 350 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'IP address banned from login');
410 } 351 }
411 $GLOBALS['IPBANS'] = $gb; 352 $GLOBALS['IPBANS'] = $gb;
412 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 353 file_put_contents(
354 $conf->get('resource.ban_file', 'data/ipbans.php'),
355 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
356 );
413} 357}
414 358
415// Signals a successful login. Resets failed login counter. 359/**
416function ban_loginOk() 360 * Signals a successful login. Resets failed login counter.
361 *
362 * @param ConfigManager $conf Configuration Manager instance.
363 */
364function ban_loginOk($conf)
417{ 365{
418 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 366 $ip = $_SERVER['REMOTE_ADDR'];
367 $gb = $GLOBALS['IPBANS'];
419 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 368 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
420 $GLOBALS['IPBANS'] = $gb; 369 $GLOBALS['IPBANS'] = $gb;
421 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 370 file_put_contents(
371 $conf->get('resource.ban_file', 'data/ipbans.php'),
372 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
373 );
422} 374}
423 375
424// Checks if the user CAN login. If 'true', the user can try to login. 376/**
425function ban_canLogin() 377 * Checks if the user CAN login. If 'true', the user can try to login.
378 *
379 * @param ConfigManager $conf Configuration Manager instance.
380 *
381 * @return bool: true if the user is allowed to login.
382 */
383function ban_canLogin($conf)
426{ 384{
427 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 385 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
428 if (isset($gb['BANS'][$ip])) 386 if (isset($gb['BANS'][$ip]))
@@ -430,9 +388,12 @@ function ban_canLogin()
430 // User is banned. Check if the ban has expired: 388 // User is banned. Check if the ban has expired:
431 if ($gb['BANS'][$ip]<=time()) 389 if ($gb['BANS'][$ip]<=time())
432 { // Ban expired, user can try to login again. 390 { // Ban expired, user can try to login again.
433 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Ban lifted.'); 391 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Ban lifted.');
434 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 392 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
435 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 393 file_put_contents(
394 $conf->get('resource.ban_file', 'data/ipbans.php'),
395 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
396 );
436 return true; // Ban has expired, user can login. 397 return true; // Ban has expired, user can login.
437 } 398 }
438 return false; // User is banned. 399 return false; // User is banned.
@@ -444,10 +405,12 @@ function ban_canLogin()
444// Process login form: Check if login/password is correct. 405// Process login form: Check if login/password is correct.
445if (isset($_POST['login'])) 406if (isset($_POST['login']))
446{ 407{
447 if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.'); 408 if (!ban_canLogin($conf)) die('I said: NO. You are banned for the moment. Go away.');
448 if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password']))) 409 if (isset($_POST['password'])
449 { // Login/password is OK. 410 && tokenOk($_POST['token'])
450 ban_loginOk(); 411 && (check_auth($_POST['login'], $_POST['password'], $conf))
412 ) { // Login/password is OK.
413 ban_loginOk($conf);
451 // If user wants to keep the session cookie even after the browser closes: 414 // If user wants to keep the session cookie even after the browser closes:
452 if (!empty($_POST['longlastingsession'])) 415 if (!empty($_POST['longlastingsession']))
453 { 416 {
@@ -495,7 +458,7 @@ if (isset($_POST['login']))
495 } 458 }
496 else 459 else
497 { 460 {
498 ban_loginFailed(); 461 ban_loginFailed($conf);
499 $redir = '&username='. $_POST['login']; 462 $redir = '&username='. $_POST['login'];
500 if (isset($_GET['post'])) { 463 if (isset($_GET['post'])) {
501 $redir .= '&post=' . urlencode($_GET['post']); 464 $redir .= '&post=' . urlencode($_GET['post']);
@@ -543,10 +506,16 @@ function getMaxFileSize()
543// Token should be used in any form which acts on data (create,update,delete,import...). 506// Token should be used in any form which acts on data (create,update,delete,import...).
544if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. 507if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
545 508
546// Returns a token. 509/**
547function getToken() 510 * Returns a token.
511 *
512 * @param ConfigManager $conf Configuration Manager instance.
513 *
514 * @return string token.
515 */
516function getToken($conf)
548{ 517{
549 $rnd = sha1(uniqid('',true).'_'.mt_rand().$GLOBALS['salt']); // We generate a random string. 518 $rnd = sha1(uniqid('', true) .'_'. mt_rand() . $conf->get('credentials.salt')); // We generate a random string.
550 $_SESSION['tokens'][$rnd]=1; // Store it on the server side. 519 $_SESSION['tokens'][$rnd]=1; // Store it on the server side.
551 return $rnd; 520 return $rnd;
552} 521}
@@ -563,15 +532,18 @@ function tokenOk($token)
563 return false; // Wrong token, or already used. 532 return false; // Wrong token, or already used.
564} 533}
565 534
566// ------------------------------------------------------------------------------------------ 535/**
567// Daily RSS feed: 1 RSS entry per day giving all the links on that day. 536 * Daily RSS feed: 1 RSS entry per day giving all the links on that day.
568// Gives the last 7 days (which have links). 537 * Gives the last 7 days (which have links).
569// This RSS feed cannot be filtered. 538 * This RSS feed cannot be filtered.
570function showDailyRSS() { 539 *
540 * @param ConfigManager $conf Configuration Manager instance.
541 */
542function showDailyRSS($conf) {
571 // Cache system 543 // Cache system
572 $query = $_SERVER['QUERY_STRING']; 544 $query = $_SERVER['QUERY_STRING'];
573 $cache = new CachedPage( 545 $cache = new CachedPage(
574 $GLOBALS['config']['PAGECACHE'], 546 $conf->get('config.PAGE_CACHE'),
575 page_url($_SERVER), 547 page_url($_SERVER),
576 startsWith($query,'do=dailyrss') && !isLoggedIn() 548 startsWith($query,'do=dailyrss') && !isLoggedIn()
577 ); 549 );
@@ -584,32 +556,27 @@ function showDailyRSS() {
584 // If cached was not found (or not usable), then read the database and build the response: 556 // If cached was not found (or not usable), then read the database and build the response:
585 // Read links from database (and filter private links if used it not logged in). 557 // Read links from database (and filter private links if used it not logged in).
586 $LINKSDB = new LinkDB( 558 $LINKSDB = new LinkDB(
587 $GLOBALS['config']['DATASTORE'], 559 $conf->get('resource.datastore'),
588 isLoggedIn(), 560 isLoggedIn(),
589 $GLOBALS['config']['HIDE_PUBLIC_LINKS'], 561 $conf->get('privacy.hide_public_links'),
590 $GLOBALS['redirector'], 562 $conf->get('redirector.url'),
591 $GLOBALS['config']['REDIRECTOR_URLENCODE'] 563 $conf->get('redirector.encode_url')
592 ); 564 );
593 565
594 /* Some Shaarlies may have very few links, so we need to look 566 /* Some Shaarlies may have very few links, so we need to look
595 back in time (rsort()) until we have enough days ($nb_of_days). 567 back in time until we have enough days ($nb_of_days).
596 */ 568 */
597 $linkdates = array();
598 foreach ($LINKSDB as $linkdate => $value) {
599 $linkdates[] = $linkdate;
600 }
601 rsort($linkdates);
602 $nb_of_days = 7; // We take 7 days. 569 $nb_of_days = 7; // We take 7 days.
603 $today = Date('Ymd'); 570 $today = date('Ymd');
604 $days = array(); 571 $days = array();
605 572
606 foreach ($linkdates as $linkdate) { 573 foreach ($LINKSDB as $link) {
607 $day = substr($linkdate, 0, 8); // Extract day (without time) 574 $day = $link['created']->format('Ymd'); // Extract day (without time)
608 if (strcmp($day,$today) < 0) { 575 if (strcmp($day, $today) < 0) {
609 if (empty($days[$day])) { 576 if (empty($days[$day])) {
610 $days[$day] = array(); 577 $days[$day] = array();
611 } 578 }
612 $days[$day][] = $linkdate; 579 $days[$day][] = $link;
613 } 580 }
614 581
615 if (count($days) > $nb_of_days) { 582 if (count($days) > $nb_of_days) {
@@ -622,42 +589,35 @@ function showDailyRSS() {
622 $pageaddr = escape(index_url($_SERVER)); 589 $pageaddr = escape(index_url($_SERVER));
623 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; 590 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
624 echo '<channel>'; 591 echo '<channel>';
625 echo '<title>Daily - '. $GLOBALS['title'] . '</title>'; 592 echo '<title>Daily - '. $conf->get('general.title') . '</title>';
626 echo '<link>'. $pageaddr .'</link>'; 593 echo '<link>'. $pageaddr .'</link>';
627 echo '<description>Daily shared links</description>'; 594 echo '<description>Daily shared links</description>';
628 echo '<language>en-en</language>'; 595 echo '<language>en-en</language>';
629 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; 596 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
630 597
631 // For each day. 598 // For each day.
632 foreach ($days as $day => $linkdates) { 599 foreach ($days as $day => $links) {
633 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); 600 $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
634 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. 601 $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
635 602
636 // Build the HTML body of this RSS entry.
637 $html = '';
638 $href = '';
639 $links = array();
640
641 // We pre-format some fields for proper output. 603 // We pre-format some fields for proper output.
642 foreach ($linkdates as $linkdate) { 604 foreach ($links as &$link) {
643 $l = $LINKSDB[$linkdate]; 605 $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
644 $l['formatedDescription'] = format_description($l['description'], $GLOBALS['redirector']); 606 $link['thumbnail'] = thumbnail($conf, $link['url']);
645 $l['thumbnail'] = thumbnail($l['url']); 607 $link['timestamp'] = $link['created']->getTimestamp();
646 $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']); 608 if (startsWith($link['url'], '?')) {
647 $l['timestamp'] = $l_date->getTimestamp(); 609 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
648 if (startsWith($l['url'], '?')) {
649 $l['url'] = index_url($_SERVER) . $l['url']; // make permalink URL absolute
650 } 610 }
651 $links[$linkdate] = $l;
652 } 611 }
653 612
654 // Then build the HTML for this day: 613 // Then build the HTML for this day:
655 $tpl = new RainTPL; 614 $tpl = new RainTPL;
656 $tpl->assign('title', $GLOBALS['title']); 615 $tpl->assign('title', $conf->get('general.title'));
657 $tpl->assign('daydate', $dayDate->getTimestamp()); 616 $tpl->assign('daydate', $dayDate->getTimestamp());
658 $tpl->assign('absurl', $absurl); 617 $tpl->assign('absurl', $absurl);
659 $tpl->assign('links', $links); 618 $tpl->assign('links', $links);
660 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); 619 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS)));
620 $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false));
661 $html = $tpl->draw('dailyrss', $return_string=true); 621 $html = $tpl->draw('dailyrss', $return_string=true);
662 622
663 echo $html . PHP_EOL; 623 echo $html . PHP_EOL;
@@ -672,12 +632,14 @@ function showDailyRSS() {
672/** 632/**
673 * Show the 'Daily' page. 633 * Show the 'Daily' page.
674 * 634 *
675 * @param PageBuilder $pageBuilder Template engine wrapper. 635 * @param PageBuilder $pageBuilder Template engine wrapper.
676 * @param LinkDB $LINKSDB LinkDB instance. 636 * @param LinkDB $LINKSDB LinkDB instance.
637 * @param ConfigManager $conf Configuration Manager instance.
638 * @param PluginManager $pluginManager Plugin Manager instane.
677 */ 639 */
678function showDaily($pageBuilder, $LINKSDB) 640function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
679{ 641{
680 $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 642 $day=date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
681 if (isset($_GET['day'])) $day=$_GET['day']; 643 if (isset($_GET['day'])) $day=$_GET['day'];
682 644
683 $days = $LINKSDB->days(); 645 $days = $LINKSDB->days();
@@ -705,10 +667,9 @@ function showDaily($pageBuilder, $LINKSDB)
705 $taglist = explode(' ',$link['tags']); 667 $taglist = explode(' ',$link['tags']);
706 uasort($taglist, 'strcasecmp'); 668 uasort($taglist, 'strcasecmp');
707 $linksToDisplay[$key]['taglist']=$taglist; 669 $linksToDisplay[$key]['taglist']=$taglist;
708 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $GLOBALS['redirector']); 670 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
709 $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']); 671 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
710 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 672 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
711 $linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
712 } 673 }
713 674
714 /* We need to spread the articles on 3 columns. 675 /* We need to spread the articles on 3 columns.
@@ -741,7 +702,7 @@ function showDaily($pageBuilder, $LINKSDB)
741 'previousday' => $previousday, 702 'previousday' => $previousday,
742 'nextday' => $nextday, 703 'nextday' => $nextday,
743 ); 704 );
744 $pluginManager = PluginManager::getInstance(); 705
745 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn())); 706 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn()));
746 707
747 foreach ($data as $key => $value) { 708 foreach ($data as $key => $value) {
@@ -752,36 +713,46 @@ function showDaily($pageBuilder, $LINKSDB)
752 exit; 713 exit;
753} 714}
754 715
755// Renders the linklist 716/**
756function showLinkList($PAGE, $LINKSDB) { 717 * Renders the linklist
757 buildLinkList($PAGE,$LINKSDB); // Compute list of links to display 718 *
719 * @param pageBuilder $PAGE pageBuilder instance.
720 * @param LinkDB $LINKSDB LinkDB instance.
721 * @param ConfigManager $conf Configuration Manager instance.
722 * @param PluginManager $pluginManager Plugin Manager instance.
723 */
724function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) {
725 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager); // Compute list of links to display
758 $PAGE->renderPage('linklist'); 726 $PAGE->renderPage('linklist');
759} 727}
760 728
761 729/**
762// ------------------------------------------------------------------------------------------ 730 * Render HTML page (according to URL parameters and user rights)
763// Render HTML page (according to URL parameters and user rights) 731 *
764function renderPage() 732 * @param ConfigManager $conf Configuration Manager instance.
733 * @param PluginManager $pluginManager Plugin Manager instance,
734 */
735function renderPage($conf, $pluginManager)
765{ 736{
766 $LINKSDB = new LinkDB( 737 $LINKSDB = new LinkDB(
767 $GLOBALS['config']['DATASTORE'], 738 $conf->get('resource.datastore'),
768 isLoggedIn(), 739 isLoggedIn(),
769 $GLOBALS['config']['HIDE_PUBLIC_LINKS'], 740 $conf->get('privacy.hide_public_links'),
770 $GLOBALS['redirector'], 741 $conf->get('redirector.url'),
771 $GLOBALS['config']['REDIRECTOR_URLENCODE'] 742 $conf->get('redirector.encode_url')
772 ); 743 );
773 744
774 $updater = new Updater( 745 $updater = new Updater(
775 read_updates_file($GLOBALS['config']['UPDATES_FILE']), 746 read_updates_file($conf->get('resource.updates')),
776 $GLOBALS,
777 $LINKSDB, 747 $LINKSDB,
748 $conf,
778 isLoggedIn() 749 isLoggedIn()
779 ); 750 );
780 try { 751 try {
781 $newUpdates = $updater->update(); 752 $newUpdates = $updater->update();
782 if (! empty($newUpdates)) { 753 if (! empty($newUpdates)) {
783 write_updates_file( 754 write_updates_file(
784 $GLOBALS['config']['UPDATES_FILE'], 755 $conf->get('resource.updates'),
785 $updater->getDoneUpdates() 756 $updater->getDoneUpdates()
786 ); 757 );
787 } 758 }
@@ -790,9 +761,10 @@ function renderPage()
790 die($e->getMessage()); 761 die($e->getMessage());
791 } 762 }
792 763
793 $PAGE = new PageBuilder(); 764 $PAGE = new PageBuilder($conf);
794 $PAGE->assign('linkcount', count($LINKSDB)); 765 $PAGE->assign('linkcount', count($LINKSDB));
795 $PAGE->assign('privateLinkcount', count_private($LINKSDB)); 766 $PAGE->assign('privateLinkcount', count_private($LINKSDB));
767 $PAGE->assign('plugin_errors', $pluginManager->getErrors());
796 768
797 // Determine which page will be rendered. 769 // Determine which page will be rendered.
798 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 770 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
@@ -805,7 +777,7 @@ function renderPage()
805 'header', 777 'header',
806 'footer', 778 'footer',
807 ); 779 );
808 $pluginManager = PluginManager::getInstance(); 780
809 foreach($common_hooks as $name) { 781 foreach($common_hooks as $name) {
810 $plugin_data = array(); 782 $plugin_data = array();
811 $pluginManager->executeHooks('render_' . $name, $plugin_data, 783 $pluginManager->executeHooks('render_' . $name, $plugin_data,
@@ -820,9 +792,7 @@ function renderPage()
820 // -------- Display login form. 792 // -------- Display login form.
821 if ($targetPage == Router::$PAGE_LOGIN) 793 if ($targetPage == Router::$PAGE_LOGIN)
822 { 794 {
823 if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli 795 if ($conf->get('security.open_shaarli')) { header('Location: ?'); exit; } // No need to login for open Shaarli
824 $token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful.
825 $PAGE->assign('token',$token);
826 if (isset($_GET['username'])) { 796 if (isset($_GET['username'])) {
827 $PAGE->assign('username', escape($_GET['username'])); 797 $PAGE->assign('username', escape($_GET['username']));
828 } 798 }
@@ -833,7 +803,7 @@ function renderPage()
833 // -------- User wants to logout. 803 // -------- User wants to logout.
834 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) 804 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout'))
835 { 805 {
836 invalidateCaches($GLOBALS['config']['PAGECACHE']); 806 invalidateCaches($conf->get('resource.page_cache'));
837 logout(); 807 logout();
838 header('Location: ?'); 808 header('Location: ?');
839 exit; 809 exit;
@@ -849,8 +819,8 @@ function renderPage()
849 // Get only links which have a thumbnail. 819 // Get only links which have a thumbnail.
850 foreach($links as $link) 820 foreach($links as $link)
851 { 821 {
852 $permalink='?'.escape(smallhash($link['linkdate'])); 822 $permalink='?'.$link['shorturl'];
853 $thumb=lazyThumbnail($link['url'],$permalink); 823 $thumb=lazyThumbnail($conf, $link['url'],$permalink);
854 if ($thumb!='') // Only output links which have a thumbnail. 824 if ($thumb!='') // Only output links which have a thumbnail.
855 { 825 {
856 $link['thumbnail']=$thumb; // Thumbnail HTML code. 826 $link['thumbnail']=$thumb; // Thumbnail HTML code.
@@ -883,7 +853,7 @@ function renderPage()
883 $maxcount = max($maxcount, $value); 853 $maxcount = max($maxcount, $value);
884 } 854 }
885 855
886 // Sort tags alphabetically: case insensitive, support locale if avalaible. 856 // Sort tags alphabetically: case insensitive, support locale if available.
887 uksort($tags, function($a, $b) { 857 uksort($tags, function($a, $b) {
888 // Collator is part of PHP intl. 858 // Collator is part of PHP intl.
889 if (class_exists('Collator')) { 859 if (class_exists('Collator')) {
@@ -922,7 +892,7 @@ function renderPage()
922 892
923 // Daily page. 893 // Daily page.
924 if ($targetPage == Router::$PAGE_DAILY) { 894 if ($targetPage == Router::$PAGE_DAILY) {
925 showDaily($PAGE, $LINKSDB); 895 showDaily($PAGE, $LINKSDB, $conf, $pluginManager);
926 } 896 }
927 897
928 // ATOM and RSS feed. 898 // ATOM and RSS feed.
@@ -933,7 +903,7 @@ function renderPage()
933 // Cache system 903 // Cache system
934 $query = $_SERVER['QUERY_STRING']; 904 $query = $_SERVER['QUERY_STRING'];
935 $cache = new CachedPage( 905 $cache = new CachedPage(
936 $GLOBALS['config']['PAGECACHE'], 906 $conf->get('resource.page_cache'),
937 page_url($_SERVER), 907 page_url($_SERVER),
938 startsWith($query,'do='. $targetPage) && !isLoggedIn() 908 startsWith($query,'do='. $targetPage) && !isLoggedIn()
939 ); 909 );
@@ -946,15 +916,15 @@ function renderPage()
946 // Generate data. 916 // Generate data.
947 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn()); 917 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn());
948 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); 918 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
949 $feedGenerator->setHideDates($GLOBALS['config']['HIDE_TIMESTAMPS'] && !isLoggedIn()); 919 $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn());
950 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']); 920 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
951 if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) { 921 $pshUrl = $conf->get('config.PUBSUBHUB_URL');
952 $feedGenerator->setPubsubhubUrl($GLOBALS['config']['PUBSUBHUB_URL']); 922 if (!empty($pshUrl)) {
923 $feedGenerator->setPubsubhubUrl($pshUrl);
953 } 924 }
954 $data = $feedGenerator->buildData(); 925 $data = $feedGenerator->buildData();
955 926
956 // Process plugin hook. 927 // Process plugin hook.
957 $pluginManager = PluginManager::getInstance();
958 $pluginManager->executeHooks('render_feed', $data, array( 928 $pluginManager->executeHooks('render_feed', $data, array(
959 'loggedin' => isLoggedIn(), 929 'loggedin' => isLoggedIn(),
960 'target' => $targetPage, 930 'target' => $targetPage,
@@ -1080,7 +1050,7 @@ function renderPage()
1080 exit; 1050 exit;
1081 } 1051 }
1082 1052
1083 showLinkList($PAGE, $LINKSDB); 1053 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
1084 if (isset($_GET['edit_link'])) { 1054 if (isset($_GET['edit_link'])) {
1085 header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); 1055 header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
1086 exit; 1056 exit;
@@ -1096,6 +1066,7 @@ function renderPage()
1096 { 1066 {
1097 $data = array( 1067 $data = array(
1098 'pageabsaddr' => index_url($_SERVER), 1068 'pageabsaddr' => index_url($_SERVER),
1069 'sslenabled' => !empty($_SERVER['HTTPS'])
1099 ); 1070 );
1100 $pluginManager->executeHooks('render_tools', $data); 1071 $pluginManager->executeHooks('render_tools', $data);
1101 1072
@@ -1110,19 +1081,23 @@ function renderPage()
1110 // -------- User wants to change his/her password. 1081 // -------- User wants to change his/her password.
1111 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) 1082 if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
1112 { 1083 {
1113 if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.'); 1084 if ($conf->get('security.open_shaarli')) {
1085 die('You are not supposed to change a password on an Open Shaarli.');
1086 }
1087
1114 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 1088 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
1115 { 1089 {
1116 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away! 1090 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1117 1091
1118 // Make sure old password is correct. 1092 // Make sure old password is correct.
1119 $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1093 $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt'));
1120 if ($oldhash!=$GLOBALS['hash']) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; } 1094 if ($oldhash!= $conf->get('credentials.hash')) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
1121 // Save new password 1095 // Save new password
1122 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1096 // Salt renders rainbow-tables attacks useless.
1123 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1097 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
1098 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt')));
1124 try { 1099 try {
1125 writeConfig($GLOBALS, isLoggedIn()); 1100 $conf->write(isLoggedIn());
1126 } 1101 }
1127 catch(Exception $e) { 1102 catch(Exception $e) {
1128 error_log( 1103 error_log(
@@ -1139,7 +1114,6 @@ function renderPage()
1139 } 1114 }
1140 else // show the change password form. 1115 else // show the change password form.
1141 { 1116 {
1142 $PAGE->assign('token',getToken());
1143 $PAGE->renderPage('changepassword'); 1117 $PAGE->renderPage('changepassword');
1144 exit; 1118 exit;
1145 } 1119 }
@@ -1159,17 +1133,17 @@ function renderPage()
1159 ) { 1133 ) {
1160 $tz = $_POST['continent'] . '/' . $_POST['city']; 1134 $tz = $_POST['continent'] . '/' . $_POST['city'];
1161 } 1135 }
1162 $GLOBALS['timezone'] = $tz; 1136 $conf->set('general.timezone', $tz);
1163 $GLOBALS['title']=$_POST['title']; 1137 $conf->set('general.title', escape($_POST['title']));
1164 $GLOBALS['titleLink']=$_POST['titleLink']; 1138 $conf->set('general.header_link', escape($_POST['titleLink']));
1165 $GLOBALS['redirector']=$_POST['redirector']; 1139 $conf->set('redirector.url', escape($_POST['redirector']));
1166 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']); 1140 $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection']));
1167 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']); 1141 $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault']));
1168 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']); 1142 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
1169 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1143 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
1170 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']); 1144 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
1171 try { 1145 try {
1172 writeConfig($GLOBALS, isLoggedIn()); 1146 $conf->write(isLoggedIn());
1173 } 1147 }
1174 catch(Exception $e) { 1148 catch(Exception $e) {
1175 error_log( 1149 error_log(
@@ -1178,20 +1152,24 @@ function renderPage()
1178 ); 1152 );
1179 1153
1180 // TODO: do not handle exceptions/errors in JS. 1154 // TODO: do not handle exceptions/errors in JS.
1181 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; 1155 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>';
1182 exit; 1156 exit;
1183 } 1157 }
1184 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; 1158 echo '<script>alert("Configuration was saved.");document.location=\'?do=configure\';</script>';
1185 exit; 1159 exit;
1186 } 1160 }
1187 else // Show the configuration form. 1161 else // Show the configuration form.
1188 { 1162 {
1189 $PAGE->assign('token',getToken()); 1163 $PAGE->assign('title', $conf->get('general.title'));
1190 $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] ); 1164 $PAGE->assign('redirector', $conf->get('redirector.url'));
1191 $PAGE->assign('redirector', empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] ); 1165 list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
1192 list($timezone_form, $timezone_js) = generateTimeZoneForm($GLOBALS['timezone']);
1193 $PAGE->assign('timezone_form', $timezone_form); 1166 $PAGE->assign('timezone_form', $timezone_form);
1194 $PAGE->assign('timezone_js',$timezone_js); 1167 $PAGE->assign('timezone_js',$timezone_js);
1168 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
1169 $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
1170 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
1171 $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true));
1172 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
1195 $PAGE->renderPage('configure'); 1173 $PAGE->renderPage('configure');
1196 exit; 1174 exit;
1197 } 1175 }
@@ -1201,7 +1179,6 @@ function renderPage()
1201 if ($targetPage == Router::$PAGE_CHANGETAG) 1179 if ($targetPage == Router::$PAGE_CHANGETAG)
1202 { 1180 {
1203 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { 1181 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) {
1204 $PAGE->assign('token', getToken());
1205 $PAGE->assign('tags', $LINKSDB->allTags()); 1182 $PAGE->assign('tags', $LINKSDB->allTags());
1206 $PAGE->renderPage('changetag'); 1183 $PAGE->renderPage('changetag');
1207 exit; 1184 exit;
@@ -1223,7 +1200,7 @@ function renderPage()
1223 $value['tags']=trim(implode(' ',$tags)); 1200 $value['tags']=trim(implode(' ',$tags));
1224 $LINKSDB[$key]=$value; 1201 $LINKSDB[$key]=$value;
1225 } 1202 }
1226 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); 1203 $LINKSDB->save($conf->get('resource.page_cache'));
1227 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>'; 1204 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
1228 exit; 1205 exit;
1229 } 1206 }
@@ -1240,7 +1217,7 @@ function renderPage()
1240 $value['tags']=trim(implode(' ',$tags)); 1217 $value['tags']=trim(implode(' ',$tags));
1241 $LINKSDB[$key]=$value; 1218 $LINKSDB[$key]=$value;
1242 } 1219 }
1243 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. 1220 $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
1244 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>'; 1221 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
1245 exit; 1222 exit;
1246 } 1223 }
@@ -1260,13 +1237,33 @@ function renderPage()
1260 if (! tokenOk($_POST['token'])) { 1237 if (! tokenOk($_POST['token'])) {
1261 die('Wrong token.'); 1238 die('Wrong token.');
1262 } 1239 }
1240
1241 // lf_id should only be present if the link exists.
1242 $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
1243 // Linkdate is kept here to:
1244 // - use the same permalink for notes as they're displayed when creating them
1245 // - let users hack creation date of their posts
1246 // See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link
1247 $linkdate = escape($_POST['lf_linkdate']);
1248 if (isset($LINKSDB[$id])) {
1249 // Edit
1250 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1251 $updated = new DateTime();
1252 $shortUrl = $LINKSDB[$id]['shorturl'];
1253 } else {
1254 // New link
1255 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1256 $updated = null;
1257 $shortUrl = link_small_hash($created, $id);
1258 }
1259
1263 // Remove multiple spaces. 1260 // Remove multiple spaces.
1264 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); 1261 $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
1265 // Remove first '-' char in tags. 1262 // Remove first '-' char in tags.
1266 $tags = preg_replace('/(^| )\-/', '$1', $tags); 1263 $tags = preg_replace('/(^| )\-/', '$1', $tags);
1267 // Remove duplicates. 1264 // Remove duplicates.
1268 $tags = implode(' ', array_unique(explode(' ', $tags))); 1265 $tags = implode(' ', array_unique(explode(' ', $tags)));
1269 $linkdate = $_POST['lf_linkdate']; 1266
1270 $url = trim($_POST['lf_url']); 1267 $url = trim($_POST['lf_url']);
1271 if (! startsWith($url, 'http:') && ! startsWith($url, 'https:') 1268 if (! startsWith($url, 'http:') && ! startsWith($url, 'https:')
1272 && ! startsWith($url, 'ftp:') && ! startsWith($url, 'magnet:') 1269 && ! startsWith($url, 'ftp:') && ! startsWith($url, 'magnet:')
@@ -1276,13 +1273,17 @@ function renderPage()
1276 } 1273 }
1277 1274
1278 $link = array( 1275 $link = array(
1276 'id' => $id,
1279 'title' => trim($_POST['lf_title']), 1277 'title' => trim($_POST['lf_title']),
1280 'url' => $url, 1278 'url' => $url,
1281 'description' => $_POST['lf_description'], 1279 'description' => $_POST['lf_description'],
1282 'private' => (isset($_POST['lf_private']) ? 1 : 0), 1280 'private' => (isset($_POST['lf_private']) ? 1 : 0),
1283 'linkdate' => $linkdate, 1281 'created' => $created,
1284 'tags' => str_replace(',', ' ', $tags) 1282 'updated' => $updated,
1283 'tags' => str_replace(',', ' ', $tags),
1284 'shorturl' => $shortUrl,
1285 ); 1285 );
1286
1286 // If title is empty, use the URL as title. 1287 // If title is empty, use the URL as title.
1287 if ($link['title'] == '') { 1288 if ($link['title'] == '') {
1288 $link['title'] = $link['url']; 1289 $link['title'] = $link['url'];
@@ -1290,9 +1291,9 @@ function renderPage()
1290 1291
1291 $pluginManager->executeHooks('save_link', $link); 1292 $pluginManager->executeHooks('save_link', $link);
1292 1293
1293 $LINKSDB[$linkdate] = $link; 1294 $LINKSDB[$id] = $link;
1294 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); 1295 $LINKSDB->save($conf->get('resource.page_cache'));
1295 pubsubhub(); 1296 pubsubhub($conf);
1296 1297
1297 // If we are called from the bookmarklet, we must close the popup: 1298 // If we are called from the bookmarklet, we must close the popup:
1298 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { 1299 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
@@ -1303,7 +1304,7 @@ function renderPage()
1303 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; 1304 $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
1304 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1305 $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1305 // Scroll to the link which has been edited. 1306 // Scroll to the link which has been edited.
1306 $location .= '#' . smallHash($_POST['lf_linkdate']); 1307 $location .= '#' . $link['shorturl'];
1307 // After saving the link, redirect to the page the user was on. 1308 // After saving the link, redirect to the page the user was on.
1308 header('Location: '. $location); 1309 header('Location: '. $location);
1309 exit; 1310 exit;
@@ -1314,8 +1315,10 @@ function renderPage()
1314 { 1315 {
1315 // If we are called from the bookmarklet, we must close the popup: 1316 // If we are called from the bookmarklet, we must close the popup:
1316 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1317 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
1318 $link = $LINKSDB[(int) escape($_POST['lf_id'])];
1317 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1319 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1318 $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. 1320 // Scroll to the link which has been edited.
1321 $returnurl .= '#'. $link['shorturl'];
1319 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); 1322 $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
1320 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. 1323 header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
1321 exit; 1324 exit;
@@ -1325,15 +1328,18 @@ function renderPage()
1325 if (isset($_POST['delete_link'])) 1328 if (isset($_POST['delete_link']))
1326 { 1329 {
1327 if (!tokenOk($_POST['token'])) die('Wrong token.'); 1330 if (!tokenOk($_POST['token'])) die('Wrong token.');
1331
1328 // We do not need to ask for confirmation: 1332 // We do not need to ask for confirmation:
1329 // - confirmation is handled by JavaScript 1333 // - confirmation is handled by JavaScript
1330 // - we are protected from XSRF by the token. 1334 // - we are protected from XSRF by the token.
1331 $linkdate=$_POST['lf_linkdate'];
1332 1335
1333 $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]); 1336 // FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
1337 $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
1338
1339 $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
1334 1340
1335 unset($LINKSDB[$linkdate]); 1341 unset($LINKSDB[$id]);
1336 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk 1342 $LINKSDB->save('resource.page_cache'); // save to disk
1337 1343
1338 // If we are called from the bookmarklet, we must close the popup: 1344 // If we are called from the bookmarklet, we must close the popup:
1339 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1345 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1371,12 +1377,13 @@ function renderPage()
1371 // -------- User clicked the "EDIT" button on a link: Display link edit form. 1377 // -------- User clicked the "EDIT" button on a link: Display link edit form.
1372 if (isset($_GET['edit_link'])) 1378 if (isset($_GET['edit_link']))
1373 { 1379 {
1374 $link = $LINKSDB[$_GET['edit_link']]; // Read database 1380 $id = (int) escape($_GET['edit_link']);
1381 $link = $LINKSDB[$id]; // Read database
1375 if (!$link) { header('Location: ?'); exit; } // Link not found in database. 1382 if (!$link) { header('Location: ?'); exit; } // Link not found in database.
1383 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1376 $data = array( 1384 $data = array(
1377 'link' => $link, 1385 'link' => $link,
1378 'link_is_new' => false, 1386 'link_is_new' => false,
1379 'token' => getToken(),
1380 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), 1387 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
1381 'tags' => $LINKSDB->allTags(), 1388 'tags' => $LINKSDB->allTags(),
1382 ); 1389 );
@@ -1397,10 +1404,10 @@ function renderPage()
1397 $link_is_new = false; 1404 $link_is_new = false;
1398 // Check if URL is not already in database (in this case, we will edit the existing link) 1405 // Check if URL is not already in database (in this case, we will edit the existing link)
1399 $link = $LINKSDB->getLinkFromUrl($url); 1406 $link = $LINKSDB->getLinkFromUrl($url);
1400 if (!$link) 1407 if (! $link)
1401 { 1408 {
1402 $link_is_new = true; 1409 $link_is_new = true;
1403 $linkdate = strval(date('Ymd_His')); 1410 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
1404 // Get title if it was provided in URL (by the bookmarklet). 1411 // Get title if it was provided in URL (by the bookmarklet).
1405 $title = empty($_GET['title']) ? '' : escape($_GET['title']); 1412 $title = empty($_GET['title']) ? '' : escape($_GET['title']);
1406 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] 1413 // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
@@ -1424,7 +1431,7 @@ function renderPage()
1424 } 1431 }
1425 1432
1426 if ($url == '') { 1433 if ($url == '') {
1427 $url = '?' . smallHash($linkdate); 1434 $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
1428 $title = 'Note: '; 1435 $title = 'Note: ';
1429 } 1436 }
1430 $url = escape($url); 1437 $url = escape($url);
@@ -1438,15 +1445,17 @@ function renderPage()
1438 'tags' => $tags, 1445 'tags' => $tags,
1439 'private' => $private 1446 'private' => $private
1440 ); 1447 );
1448 } else {
1449 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1441 } 1450 }
1442 1451
1443 $data = array( 1452 $data = array(
1444 'link' => $link, 1453 'link' => $link,
1445 'link_is_new' => $link_is_new, 1454 'link_is_new' => $link_is_new,
1446 'token' => getToken(), // XSRF protection.
1447 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), 1455 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
1448 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), 1456 'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
1449 'tags' => $LINKSDB->allTags(), 1457 'tags' => $LINKSDB->allTags(),
1458 'default_private_links' => $conf->get('privacy.default_private_links', false),
1450 ); 1459 );
1451 $pluginManager->executeHooks('render_editlink', $data); 1460 $pluginManager->executeHooks('render_editlink', $data);
1452 1461
@@ -1502,27 +1511,37 @@ function renderPage()
1502 exit; 1511 exit;
1503 } 1512 }
1504 1513
1505 // -------- User is uploading a file for import 1514 if ($targetPage == Router::$PAGE_IMPORT) {
1506 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=upload')) 1515 // Upload a Netscape bookmark dump to import its contents
1507 { 1516
1508 // If file is too big, some form field may be missing. 1517 if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) {
1509 if (!isset($_POST['token']) || (!isset($_FILES)) || (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size']==0)) 1518 // Show import dialog
1510 { 1519 $PAGE->assign('maxfilesize', getMaxFileSize());
1511 $returnurl = ( empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER'] ); 1520 $PAGE->renderPage('import');
1512 echo '<script>alert("The file you are trying to upload is probably bigger than what this webserver can accept ('.getMaxFileSize().' bytes). Please upload in smaller chunks.");document.location=\''.escape($returnurl).'\';</script>';
1513 exit; 1521 exit;
1514 } 1522 }
1515 if (!tokenOk($_POST['token'])) die('Wrong token.');
1516 importFile($LINKSDB);
1517 exit;
1518 }
1519 1523
1520 // -------- Show upload/import dialog: 1524 // Import bookmarks from an uploaded file
1521 if ($targetPage == Router::$PAGE_IMPORT) 1525 if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) {
1522 { 1526 // The file is too big or some form field may be missing.
1523 $PAGE->assign('token',getToken()); 1527 echo '<script>alert("The file you are trying to upload is probably'
1524 $PAGE->assign('maxfilesize',getMaxFileSize()); 1528 .' bigger than what this webserver can accept ('
1525 $PAGE->renderPage('import'); 1529 .getMaxFileSize().' bytes).'
1530 .' Please upload in smaller chunks.");document.location=\'?do='
1531 .Router::$PAGE_IMPORT .'\';</script>';
1532 exit;
1533 }
1534 if (! tokenOk($_POST['token'])) {
1535 die('Wrong token.');
1536 }
1537 $status = NetscapeBookmarkUtils::import(
1538 $_POST,
1539 $_FILES,
1540 $LINKSDB,
1541 $conf->get('resource.page_cache')
1542 );
1543 echo '<script>alert("'.$status.'");document.location=\'?do='
1544 .Router::$PAGE_IMPORT .'\';</script>';
1526 exit; 1545 exit;
1527 } 1546 }
1528 1547
@@ -1533,7 +1552,7 @@ function renderPage()
1533 // Split plugins into 2 arrays: ordered enabled plugins and disabled. 1552 // Split plugins into 2 arrays: ordered enabled plugins and disabled.
1534 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); 1553 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; });
1535 // Load parameters. 1554 // Load parameters.
1536 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $GLOBALS['plugins']); 1555 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array()));
1537 uasort( 1556 uasort(
1538 $enabledPlugins, 1557 $enabledPlugins,
1539 function($a, $b) { return $a['order'] - $b['order']; } 1558 function($a, $b) { return $a['order'] - $b['order']; }
@@ -1552,13 +1571,13 @@ function renderPage()
1552 if (isset($_POST['parameters_form'])) { 1571 if (isset($_POST['parameters_form'])) {
1553 unset($_POST['parameters_form']); 1572 unset($_POST['parameters_form']);
1554 foreach ($_POST as $param => $value) { 1573 foreach ($_POST as $param => $value) {
1555 $GLOBALS['plugins'][$param] = escape($value); 1574 $conf->set('plugins.'. $param, escape($value));
1556 } 1575 }
1557 } 1576 }
1558 else { 1577 else {
1559 $GLOBALS['config']['ENABLED_PLUGINS'] = save_plugin_config($_POST); 1578 $conf->set('general.enabled_plugins', save_plugin_config($_POST));
1560 } 1579 }
1561 writeConfig($GLOBALS, isLoggedIn()); 1580 $conf->write(isLoggedIn());
1562 } 1581 }
1563 catch (Exception $e) { 1582 catch (Exception $e) {
1564 error_log( 1583 error_log(
@@ -1575,103 +1594,20 @@ function renderPage()
1575 } 1594 }
1576 1595
1577 // -------- Otherwise, simply display search form and links: 1596 // -------- Otherwise, simply display search form and links:
1578 showLinkList($PAGE, $LINKSDB); 1597 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
1579 exit; 1598 exit;
1580} 1599}
1581 1600
1582// -----------------------------------------------------------------------------------------------
1583// Process the import file form.
1584function importFile($LINKSDB)
1585{
1586 if (!isLoggedIn()) { die('Not allowed.'); }
1587
1588 $filename=$_FILES['filetoupload']['name'];
1589 $filesize=$_FILES['filetoupload']['size'];
1590 $data=file_get_contents($_FILES['filetoupload']['tmp_name']);
1591 $private = (empty($_POST['private']) ? 0 : 1); // Should the links be imported as private?
1592 $overwrite = !empty($_POST['overwrite']) ; // Should the imported links overwrite existing ones?
1593 $import_count=0;
1594
1595 // Sniff file type:
1596 $type='unknown';
1597 if (startsWith($data,'<!DOCTYPE NETSCAPE-Bookmark-file-1>')) $type='netscape'; // Netscape bookmark file (aka Firefox).
1598
1599 // Then import the bookmarks.
1600 if ($type=='netscape')
1601 {
1602 // This is a standard Netscape-style bookmark file.
1603 // This format is supported by all browsers (except IE, of course), also Delicious, Diigo and others.
1604 foreach(explode('<DT>',$data) as $html) // explode is very fast
1605 {
1606 $link = array('linkdate'=>'','title'=>'','url'=>'','description'=>'','tags'=>'','private'=>0);
1607 $d = explode('<DD>',$html);
1608 if (startsWith($d[0], '<A '))
1609 {
1610 $link['description'] = (isset($d[1]) ? html_entity_decode(trim($d[1]),ENT_QUOTES,'UTF-8') : ''); // Get description (optional)
1611 preg_match('!<A .*?>(.*?)</A>!i',$d[0],$matches); $link['title'] = (isset($matches[1]) ? trim($matches[1]) : ''); // Get title
1612 $link['title'] = html_entity_decode($link['title'],ENT_QUOTES,'UTF-8');
1613 preg_match_all('! ([A-Z_]+)=\"(.*?)"!i',$html,$matches,PREG_SET_ORDER); // Get all other attributes
1614 $raw_add_date=0;
1615 foreach($matches as $m)
1616 {
1617 $attr=$m[1]; $value=$m[2];
1618 if ($attr=='HREF') $link['url']=html_entity_decode($value,ENT_QUOTES,'UTF-8');
1619 elseif ($attr=='ADD_DATE')
1620 {
1621 $raw_add_date=intval($value);
1622 if ($raw_add_date>30000000000) $raw_add_date/=1000; //If larger than year 2920, then was likely stored in milliseconds instead of seconds
1623 }
1624 elseif ($attr=='PRIVATE') $link['private']=($value=='0'?0:1);
1625 elseif ($attr=='TAGS') $link['tags']=html_entity_decode(str_replace(',',' ',$value),ENT_QUOTES,'UTF-8');
1626 }
1627 if ($link['url']!='')
1628 {
1629 if ($private==1) $link['private']=1;
1630 $dblink = $LINKSDB->getLinkFromUrl($link['url']); // See if the link is already in database.
1631 if ($dblink==false)
1632 { // Link not in database, let's import it...
1633 if (empty($raw_add_date)) $raw_add_date=time(); // In case of shitty bookmark file with no ADD_DATE
1634
1635 // Make sure date/time is not already used by another link.
1636 // (Some bookmark files have several different links with the same ADD_DATE)
1637 // We increment date by 1 second until we find a date which is not used in DB.
1638 // (so that links that have the same date/time are more or less kept grouped by date, but do not conflict.)
1639 while (!empty($LINKSDB[date('Ymd_His',$raw_add_date)])) { $raw_add_date++; }// Yes, I know it's ugly.
1640 $link['linkdate']=date('Ymd_His',$raw_add_date);
1641 $LINKSDB[$link['linkdate']] = $link;
1642 $import_count++;
1643 }
1644 else // Link already present in database.
1645 {
1646 if ($overwrite)
1647 { // If overwrite is required, we import link data, except date/time.
1648 $link['linkdate']=$dblink['linkdate'];
1649 $LINKSDB[$link['linkdate']] = $link;
1650 $import_count++;
1651 }
1652 }
1653
1654 }
1655 }
1656 }
1657 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']);
1658
1659 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
1660 }
1661 else
1662 {
1663 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
1664 }
1665}
1666
1667/** 1601/**
1668 * Template for the list of links (<div id="linklist">) 1602 * Template for the list of links (<div id="linklist">)
1669 * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' 1603 * This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
1670 * 1604 *
1671 * @param pageBuilder $PAGE pageBuilder instance. 1605 * @param pageBuilder $PAGE pageBuilder instance.
1672 * @param LinkDB $LINKSDB LinkDB instance. 1606 * @param LinkDB $LINKSDB LinkDB instance.
1607 * @param ConfigManager $conf Configuration Manager instance.
1608 * @param PluginManager $pluginManager Plugin Manager instance.
1673 */ 1609 */
1674function buildLinkList($PAGE,$LINKSDB) 1610function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1675{ 1611{
1676 // Used in templates 1612 // Used in templates
1677 $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; 1613 $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
@@ -1698,10 +1634,7 @@ function buildLinkList($PAGE,$LINKSDB)
1698 $keys[] = $key; 1634 $keys[] = $key;
1699 } 1635 }
1700 1636
1701 // If there is only a single link, we change on-the-fly the title of the page. 1637
1702 if (count($linksToDisplay) == 1) {
1703 $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title'];
1704 }
1705 1638
1706 // Select articles according to paging. 1639 // Select articles according to paging.
1707 $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); 1640 $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
@@ -1716,15 +1649,18 @@ function buildLinkList($PAGE,$LINKSDB)
1716 while ($i<$end && $i<count($keys)) 1649 while ($i<$end && $i<count($keys))
1717 { 1650 {
1718 $link = $linksToDisplay[$keys[$i]]; 1651 $link = $linksToDisplay[$keys[$i]];
1719 $link['description'] = format_description($link['description'], $GLOBALS['redirector']); 1652 $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
1720 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; 1653 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
1721 $link['class'] = $link['private'] == 0 ? $classLi : 'private'; 1654 $link['class'] = $link['private'] == 0 ? $classLi : 'private';
1722 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 1655 $link['timestamp'] = $link['created']->getTimestamp();
1723 $link['timestamp'] = $date->getTimestamp(); 1656 if (! empty($link['updated'])) {
1657 $link['updated_timestamp'] = $link['updated']->getTimestamp();
1658 } else {
1659 $link['updated_timestamp'] = '';
1660 }
1724 $taglist = explode(' ', $link['tags']); 1661 $taglist = explode(' ', $link['tags']);
1725 uasort($taglist, 'strcasecmp'); 1662 uasort($taglist, 'strcasecmp');
1726 $link['taglist'] = $taglist; 1663 $link['taglist'] = $taglist;
1727 $link['shorturl'] = smallHash($link['linkdate']);
1728 // Check for both signs of a note: starting with ? and 7 chars long. 1664 // Check for both signs of a note: starting with ? and 7 chars long.
1729 if ($link['url'][0] === '?' && 1665 if ($link['url'][0] === '?' &&
1730 strlen($link['url']) === 7) { 1666 strlen($link['url']) === 7) {
@@ -1747,8 +1683,6 @@ function buildLinkList($PAGE,$LINKSDB)
1747 $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl; 1683 $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl;
1748 } 1684 }
1749 1685
1750 $token = isLoggedIn() ? getToken() : '';
1751
1752 // Fill all template fields. 1686 // Fill all template fields.
1753 $data = array( 1687 $data = array(
1754 'previous_page_url' => $previous_page_url, 1688 'previous_page_url' => $previous_page_url,
@@ -1758,17 +1692,16 @@ function buildLinkList($PAGE,$LINKSDB)
1758 'result_count' => count($linksToDisplay), 1692 'result_count' => count($linksToDisplay),
1759 'search_term' => $searchterm, 1693 'search_term' => $searchterm,
1760 'search_tags' => $searchtags, 1694 'search_tags' => $searchtags,
1761 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL. 1695 'redirector' => $conf->get('redirector.url'), // Optional redirector URL.
1762 'token' => $token,
1763 'links' => $linkDisp, 1696 'links' => $linkDisp,
1764 'tags' => $LINKSDB->allTags(), 1697 'tags' => $LINKSDB->allTags(),
1765 ); 1698 );
1766 // FIXME! temporary fix - see #399. 1699
1767 if (!empty($GLOBALS['pagetitle']) && count($linkDisp) == 1) { 1700 // If there is only a single link, we change on-the-fly the title of the page.
1768 $data['pagetitle'] = $GLOBALS['pagetitle']; 1701 if (count($linksToDisplay) == 1) {
1702 $data['pagetitle'] = $linksToDisplay[$keys[0]]['title'] .' - '. $conf->get('general.title');
1769 } 1703 }
1770 1704
1771 $pluginManager = PluginManager::getInstance();
1772 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); 1705 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn()));
1773 1706
1774 foreach ($data as $key => $value) { 1707 foreach ($data as $key => $value) {
@@ -1778,18 +1711,26 @@ function buildLinkList($PAGE,$LINKSDB)
1778 return; 1711 return;
1779} 1712}
1780 1713
1781// Compute the thumbnail for a link. 1714/**
1782// 1715 * Compute the thumbnail for a link.
1783// With a link to the original URL. 1716 *
1784// Understands various services (youtube.com...) 1717 * With a link to the original URL.
1785// Input: $url = URL for which the thumbnail must be found. 1718 * Understands various services (youtube.com...)
1786// $href = if provided, this URL will be followed instead of $url 1719 * Input: $url = URL for which the thumbnail must be found.
1787// Returns an associative array with thumbnail attributes (src,href,width,height,style,alt) 1720 * $href = if provided, this URL will be followed instead of $url
1788// Some of them may be missing. 1721 * Returns an associative array with thumbnail attributes (src,href,width,height,style,alt)
1789// Return an empty array if no thumbnail available. 1722 * Some of them may be missing.
1790function computeThumbnail($url,$href=false) 1723 * Return an empty array if no thumbnail available.
1724 *
1725 * @param ConfigManager $conf Configuration Manager instance.
1726 * @param string $url
1727 * @param string|bool $href
1728 *
1729 * @return array
1730 */
1731function computeThumbnail($conf, $url, $href = false)
1791{ 1732{
1792 if (!$GLOBALS['config']['ENABLE_THUMBNAILS']) return array(); 1733 if (!$conf->get('thumbnail.enable_thumbnails')) return array();
1793 if ($href==false) $href=$url; 1734 if ($href==false) $href=$url;
1794 1735
1795 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. 1736 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
@@ -1857,7 +1798,7 @@ function computeThumbnail($url,$href=false)
1857 // So we deport the thumbnail generation in order not to slow down page generation 1798 // So we deport the thumbnail generation in order not to slow down page generation
1858 // (and we also cache the thumbnail) 1799 // (and we also cache the thumbnail)
1859 1800
1860 if (!$GLOBALS['config']['ENABLE_LOCALCACHE']) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache. 1801 if (! $conf->get('thumbnail.enable_localcache')) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache.
1861 1802
1862 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') 1803 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')
1863 || $domain=='vimeo.com' 1804 || $domain=='vimeo.com'
@@ -1880,7 +1821,7 @@ function computeThumbnail($url,$href=false)
1880 $path = parse_url($url,PHP_URL_PATH); 1821 $path = parse_url($url,PHP_URL_PATH);
1881 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL. 1822 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
1882 } 1823 }
1883 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) 1824 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1884 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url), 1825 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1885 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail'); 1826 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1886 } 1827 }
@@ -1891,7 +1832,7 @@ function computeThumbnail($url,$href=false)
1891 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION)); 1832 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
1892 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif') 1833 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
1893 { 1834 {
1894 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) 1835 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1895 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url), 1836 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1896 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail'); 1837 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1897 } 1838 }
@@ -1908,7 +1849,9 @@ function computeThumbnail($url,$href=false)
1908// Returns '' if no thumbnail available. 1849// Returns '' if no thumbnail available.
1909function thumbnail($url,$href=false) 1850function thumbnail($url,$href=false)
1910{ 1851{
1911 $t = computeThumbnail($url,$href); 1852 // FIXME!
1853 global $conf;
1854 $t = computeThumbnail($conf, $url,$href);
1912 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL. 1855 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1913 1856
1914 $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"'; 1857 $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"';
@@ -1926,9 +1869,11 @@ function thumbnail($url,$href=false)
1926// Input: $url = URL for which the thumbnail must be found. 1869// Input: $url = URL for which the thumbnail must be found.
1927// $href = if provided, this URL will be followed instead of $url 1870// $href = if provided, this URL will be followed instead of $url
1928// Returns '' if no thumbnail available. 1871// Returns '' if no thumbnail available.
1929function lazyThumbnail($url,$href=false) 1872function lazyThumbnail($conf, $url,$href=false)
1930{ 1873{
1931 $t = computeThumbnail($url,$href); 1874 // FIXME!
1875 global $conf;
1876 $t = computeThumbnail($conf, $url,$href);
1932 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL. 1877 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1933 1878
1934 $html='<a href="'.escape($t['href']).'">'; 1879 $html='<a href="'.escape($t['href']).'">';
@@ -1954,10 +1899,13 @@ function lazyThumbnail($url,$href=false)
1954} 1899}
1955 1900
1956 1901
1957// ----------------------------------------------------------------------------------------------- 1902/**
1958// Installation 1903 * Installation
1959// This function should NEVER be called if the file data/config.php exists. 1904 * This function should NEVER be called if the file data/config.php exists.
1960function install() 1905 *
1906 * @param ConfigManager $conf Configuration Manager instance.
1907 */
1908function install($conf)
1961{ 1909{
1962 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. 1910 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
1963 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); 1911 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
@@ -1994,15 +1942,21 @@ function install()
1994 ) { 1942 ) {
1995 $tz = $_POST['continent'].'/'.$_POST['city']; 1943 $tz = $_POST['continent'].'/'.$_POST['city'];
1996 } 1944 }
1997 $GLOBALS['timezone'] = $tz; 1945 $conf->set('general.timezone', $tz);
1998 // Everything is ok, let's create config file. 1946 $login = $_POST['setlogin'];
1999 $GLOBALS['login'] = $_POST['setlogin']; 1947 $conf->set('credentials.login', $login);
2000 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1948 $salt = sha1(uniqid('', true) .'_'. mt_rand());
2001 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1949 $conf->set('credentials.salt', $salt);
2002 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(index_url($_SERVER)) : $_POST['title'] ); 1950 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt));
2003 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1951 if (!empty($_POST['title'])) {
1952 $conf->set('general.title', escape($_POST['title']));
1953 } else {
1954 $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
1955 }
1956 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
2004 try { 1957 try {
2005 writeConfig($GLOBALS, isLoggedIn()); 1958 // Everything is ok, let's create config file.
1959 $conf->write(isLoggedIn());
2006 } 1960 }
2007 catch(Exception $e) { 1961 catch(Exception $e) {
2008 error_log( 1962 error_log(
@@ -2025,42 +1979,46 @@ function install()
2025 $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>'; 1979 $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
2026 } 1980 }
2027 1981
2028 $PAGE = new PageBuilder(); 1982 $PAGE = new PageBuilder($conf);
2029 $PAGE->assign('timezone_html',$timezone_html); 1983 $PAGE->assign('timezone_html',$timezone_html);
2030 $PAGE->assign('timezone_js',$timezone_js); 1984 $PAGE->assign('timezone_js',$timezone_js);
2031 $PAGE->renderPage('install'); 1985 $PAGE->renderPage('install');
2032 exit; 1986 exit;
2033} 1987}
2034 1988
2035/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL, 1989/**
2036 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 1990 * Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
2037 The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail. 1991 * I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
2038 This function is called by passing the URL: 1992 * The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail.
2039 http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL] 1993 * This function is called by passing the URL:
2040 [URL] is the URL of the link (e.g. a flickr page) 1994 * http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL]
2041 [HMAC] is the signature for the [URL] (so that these URL cannot be forged). 1995 * [URL] is the URL of the link (e.g. a flickr page)
2042 The function below will fetch the image from the webservice and store it in the cache. 1996 * [HMAC] is the signature for the [URL] (so that these URL cannot be forged).
2043*/ 1997 * The function below will fetch the image from the webservice and store it in the cache.
2044function genThumbnail() 1998 *
1999 * @param ConfigManager $conf Configuration Manager instance,
2000 */
2001function genThumbnail($conf)
2045{ 2002{
2046 // Make sure the parameters in the URL were generated by us. 2003 // Make sure the parameters in the URL were generated by us.
2047 $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']); 2004 $sign = hash_hmac('sha256', $_GET['url'], $conf->get('credentials.salt'));
2048 if ($sign!=$_GET['hmac']) die('Naughty boy!'); 2005 if ($sign!=$_GET['hmac']) die('Naughty boy!');
2049 2006
2007 $cacheDir = $conf->get('resource.thumbnails_cache', 'cache');
2050 // Let's see if we don't already have the image for this URL in the cache. 2008 // Let's see if we don't already have the image for this URL in the cache.
2051 $thumbname=hash('sha1',$_GET['url']).'.jpg'; 2009 $thumbname=hash('sha1',$_GET['url']).'.jpg';
2052 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$thumbname)) 2010 if (is_file($cacheDir .'/'. $thumbname))
2053 { // We have the thumbnail, just serve it: 2011 { // We have the thumbnail, just serve it:
2054 header('Content-Type: image/jpeg'); 2012 header('Content-Type: image/jpeg');
2055 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname); 2013 echo file_get_contents($cacheDir .'/'. $thumbname);
2056 return; 2014 return;
2057 } 2015 }
2058 // We may also serve a blank image (if service did not respond) 2016 // We may also serve a blank image (if service did not respond)
2059 $blankname=hash('sha1',$_GET['url']).'.gif'; 2017 $blankname=hash('sha1',$_GET['url']).'.gif';
2060 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$blankname)) 2018 if (is_file($cacheDir .'/'. $blankname))
2061 { 2019 {
2062 header('Content-Type: image/gif'); 2020 header('Content-Type: image/gif');
2063 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname); 2021 echo file_get_contents($cacheDir .'/'. $blankname);
2064 return; 2022 return;
2065 } 2023 }
2066 2024
@@ -2107,7 +2065,7 @@ function genThumbnail()
2107 list($headers, $content) = get_http_response($imageurl, 10); 2065 list($headers, $content) = get_http_response($imageurl, 10);
2108 if (strpos($headers[0], '200 OK') !== false) { 2066 if (strpos($headers[0], '200 OK') !== false) {
2109 // Save image to cache. 2067 // Save image to cache.
2110 file_put_contents($GLOBALS['config']['CACHEDIR'].'/' . $thumbname, $content); 2068 file_put_contents($cacheDir .'/'. $thumbname, $content);
2111 header('Content-Type: image/jpeg'); 2069 header('Content-Type: image/jpeg');
2112 echo $content; 2070 echo $content;
2113 return; 2071 return;
@@ -2128,7 +2086,7 @@ function genThumbnail()
2128 list($headers, $content) = get_http_response($imageurl, 10); 2086 list($headers, $content) = get_http_response($imageurl, 10);
2129 if (strpos($headers[0], '200 OK') !== false) { 2087 if (strpos($headers[0], '200 OK') !== false) {
2130 // Save image to cache. 2088 // Save image to cache.
2131 file_put_contents($GLOBALS['config']['CACHEDIR'] . '/' . $thumbname, $content); 2089 file_put_contents($cacheDir .'/'. $thumbname, $content);
2132 header('Content-Type: image/jpeg'); 2090 header('Content-Type: image/jpeg');
2133 echo $content; 2091 echo $content;
2134 return; 2092 return;
@@ -2151,7 +2109,7 @@ function genThumbnail()
2151 // No control on image size, so wait long enough 2109 // No control on image size, so wait long enough
2152 list($headers, $content) = get_http_response($imageurl, 20); 2110 list($headers, $content) = get_http_response($imageurl, 20);
2153 if (strpos($headers[0], '200 OK') !== false) { 2111 if (strpos($headers[0], '200 OK') !== false) {
2154 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2112 $filepath = $cacheDir .'/'. $thumbname;
2155 file_put_contents($filepath, $content); // Save image to cache. 2113 file_put_contents($filepath, $content); // Save image to cache.
2156 if (resizeImage($filepath)) 2114 if (resizeImage($filepath))
2157 { 2115 {
@@ -2179,7 +2137,7 @@ function genThumbnail()
2179 // No control on image size, so wait long enough 2137 // No control on image size, so wait long enough
2180 list($headers, $content) = get_http_response($imageurl, 20); 2138 list($headers, $content) = get_http_response($imageurl, 20);
2181 if (strpos($headers[0], '200 OK') !== false) { 2139 if (strpos($headers[0], '200 OK') !== false) {
2182 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2140 $filepath = $cacheDir.'/'.$thumbname;
2183 // Save image to cache. 2141 // Save image to cache.
2184 file_put_contents($filepath, $content); 2142 file_put_contents($filepath, $content);
2185 if (resizeImage($filepath)) 2143 if (resizeImage($filepath))
@@ -2199,7 +2157,7 @@ function genThumbnail()
2199 // We allow 30 seconds max to download (and downloads are limited to 4 Mb) 2157 // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
2200 list($headers, $content) = get_http_response($url, 30); 2158 list($headers, $content) = get_http_response($url, 30);
2201 if (strpos($headers[0], '200 OK') !== false) { 2159 if (strpos($headers[0], '200 OK') !== false) {
2202 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2160 $filepath = $cacheDir .'/'.$thumbname;
2203 // Save image to cache. 2161 // Save image to cache.
2204 file_put_contents($filepath, $content); 2162 file_put_contents($filepath, $content);
2205 if (resizeImage($filepath)) 2163 if (resizeImage($filepath))
@@ -2214,7 +2172,8 @@ function genThumbnail()
2214 2172
2215 // Otherwise, return an empty image (8x8 transparent gif) 2173 // Otherwise, return an empty image (8x8 transparent gif)
2216 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7'); 2174 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
2217 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname,$blankgif); // Also put something in cache so that this URL is not requested twice. 2175 // Also put something in cache so that this URL is not requested twice.
2176 file_put_contents($cacheDir .'/'. $blankname, $blankgif);
2218 header('Content-Type: image/gif'); 2177 header('Content-Type: image/gif');
2219 echo $blankgif; 2178 echo $blankgif;
2220} 2179}
@@ -2252,8 +2211,9 @@ function resizeImage($filepath)
2252 return true; 2211 return true;
2253} 2212}
2254 2213
2255if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. 2214if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail($conf); exit; } // Thumbnail generation/cache does not need the link database.
2256if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS(); exit; } 2215if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS($conf); exit; }
2257if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE']; 2216if (!isset($_SESSION['LINKS_PER_PAGE'])) {
2258renderPage(); 2217 $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);
2259?> 2218}
2219renderPage($conf, $pluginManager);
diff --git a/pagecache/.htaccess b/pagecache/.htaccess
index b584d98c..f601c1ee 100644
--- a/pagecache/.htaccess
+++ b/pagecache/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/plugins/addlink_toolbar/addlink_toolbar.css b/plugins/addlink_toolbar/addlink_toolbar.css
deleted file mode 100644
index b6a612f0..00000000
--- a/plugins/addlink_toolbar/addlink_toolbar.css
+++ /dev/null
@@ -1,4 +0,0 @@
1#addlink_toolbar {
2 display: inline;
3 margin: 0 0 0 25px;
4} \ No newline at end of file
diff --git a/plugins/addlink_toolbar/addlink_toolbar.html b/plugins/addlink_toolbar/addlink_toolbar.html
deleted file mode 100644
index f38c41a0..00000000
--- a/plugins/addlink_toolbar/addlink_toolbar.html
+++ /dev/null
@@ -1,6 +0,0 @@
1<div id="addlink_toolbar">
2 <form method="GET" action="" name="addform" class="addform">
3 <input type="text" name="post" placeholder="URI">
4 <input type="submit" value="Add link" class="bigbutton">
5 </form>
6</div> \ No newline at end of file
diff --git a/plugins/addlink_toolbar/addlink_toolbar.php b/plugins/addlink_toolbar/addlink_toolbar.php
index ba3849cf..bf8a198a 100644
--- a/plugins/addlink_toolbar/addlink_toolbar.php
+++ b/plugins/addlink_toolbar/addlink_toolbar.php
@@ -15,7 +15,27 @@
15function hook_addlink_toolbar_render_header($data) 15function hook_addlink_toolbar_render_header($data)
16{ 16{
17 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) { 17 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
18 $data['fields_toolbar'][] = file_get_contents(PluginManager::$PLUGINS_PATH . '/addlink_toolbar/addlink_toolbar.html'); 18 $form = array(
19 'attr' => array(
20 'method' => 'GET',
21 'action' => '',
22 'name' => 'addform',
23 'class' => 'addform',
24 ),
25 'inputs' => array(
26 array(
27 'type' => 'text',
28 'name' => 'post',
29 'placeholder' => 'URI',
30 ),
31 array(
32 'type' => 'submit',
33 'value' => 'Add link',
34 'class' => 'bigbutton',
35 ),
36 ),
37 );
38 $data['fields_toolbar'][] = $form;
19 } 39 }
20 40
21 return $data; 41 return $data;
@@ -35,4 +55,4 @@ function hook_addlink_toolbar_render_includes($data)
35 } 55 }
36 56
37 return $data; 57 return $data;
38} \ No newline at end of file 58}
diff --git a/plugins/archiveorg/archiveorg.html b/plugins/archiveorg/archiveorg.html
index 576bd46e..0781fe35 100644
--- a/plugins/archiveorg/archiveorg.html
+++ b/plugins/archiveorg/archiveorg.html
@@ -1 +1 @@
<span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" /></a></span> <span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" alt="archive.org" /></a></span>
diff --git a/plugins/archiveorg/archiveorg.php b/plugins/archiveorg/archiveorg.php
index 7d172584..03d13d0e 100644
--- a/plugins/archiveorg/archiveorg.php
+++ b/plugins/archiveorg/archiveorg.php
@@ -17,6 +17,9 @@ function hook_archiveorg_render_linklist($data)
17 $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html'); 17 $archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html');
18 18
19 foreach ($data['links'] as &$value) { 19 foreach ($data['links'] as &$value) {
20 if($value['private'] && preg_match('/^\?[a-zA-Z0-9-_@]{6}($|&|#)/', $value['real_url'])) {
21 continue;
22 }
20 $archive = sprintf($archive_html, $value['url']); 23 $archive = sprintf($archive_html, $value['url']);
21 $value['link_plugin'][] = $archive; 24 $value['link_plugin'][] = $archive;
22 } 25 }
diff --git a/plugins/demo_plugin/custom_demo.css b/plugins/demo_plugin/custom_demo.css
index af5e8bf9..95019e28 100644
--- a/plugins/demo_plugin/custom_demo.css
+++ b/plugins/demo_plugin/custom_demo.css
@@ -2,10 +2,6 @@
2 color: red; 2 color: red;
3} 3}
4 4
5.upper_plugin_demo {
6 float: left;
7}
8
9#demo_marquee { 5#demo_marquee {
10 background: darkmagenta; 6 background: darkmagenta;
11 color: white; 7 color: white;
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php
index 18834e53..8fdbf663 100644
--- a/plugins/demo_plugin/demo_plugin.php
+++ b/plugins/demo_plugin/demo_plugin.php
@@ -15,6 +15,23 @@
15 */ 15 */
16 16
17/** 17/**
18 * Initialization function.
19 * It will be called when the plugin is loaded.
20 * This function can be used to return a list of initialization errors.
21 *
22 * @param $conf ConfigManager instance.
23 *
24 * @return array List of errors (optional).
25 */
26function demo_plugin_init($conf)
27{
28 $conf->get('toto', 'nope');
29
30 $errors[] = 'This a demo init error.';
31 return $errors;
32}
33
34/**
18 * Hook render_header. 35 * Hook render_header.
19 * Executed on every page redering. 36 * Executed on every page redering.
20 * 37 *
@@ -33,15 +50,68 @@ function hook_demo_plugin_render_header($data)
33 50
34 // If loggedin 51 // If loggedin
35 if ($data['_LOGGEDIN_'] === true) { 52 if ($data['_LOGGEDIN_'] === true) {
36 // Buttons in toolbar 53 /*
37 $data['buttons_toolbar'][] = '<li><a href="#">DEMO_buttons_toolbar</a></li>'; 54 * Links in toolbar:
55 * A link is an array of its attributes (key="value"),
56 * and a mandatory `html` key, which contains its value.
57 */
58 $button = array(
59 'attr' => array (
60 'href' => '#',
61 'class' => 'mybutton',
62 'title' => 'hover me',
63 ),
64 'html' => 'DEMO buttons toolbar',
65 );
66 $data['buttons_toolbar'][] = $button;
38 } 67 }
39 68
40 // Fields in toolbar 69 /*
41 $data['fields_toolbar'][] = 'DEMO_fields_toolbar'; 70 * Add additional input fields in the tools.
71 * A field is an array containing:
72 * [
73 * 'form-attribute-1' => 'form attribute 1 value',
74 * 'form-attribute-2' => 'form attribute 2 value',
75 * 'inputs' => [
76 * [
77 * 'input-1-attribute-1 => 'input 1 attribute 1 value',
78 * 'input-1-attribute-2 => 'input 1 attribute 2 value',
79 * ],
80 * [
81 * 'input-2-attribute-1 => 'input 2 attribute 1 value',
82 * ],
83 * ],
84 * ]
85 * This example renders as:
86 * <form form-attribute-1="form attribute 1 value" form-attribute-2="form attribute 2 value">
87 * <input input-1-attribute-1="input 1 attribute 1 value" input-1-attribute-2="input 1 attribute 2 value">
88 * <input input-2-attribute-1="input 2 attribute 1 value">
89 * </form>
90 */
91 $form = array(
92 'attr' => array(
93 'method' => 'GET',
94 'action' => '?',
95 'class' => 'addform',
96 ),
97 'inputs' => array(
98 array(
99 'type' => 'text',
100 'name' => 'demo',
101 'placeholder' => 'demo',
102 )
103 )
104 );
105 $data['fields_toolbar'][] = $form;
42 } 106 }
43 // Another button always displayed 107 // Another button always displayed
44 $data['buttons_toolbar'][] = '<li><a href="#">DEMO</a></li>'; 108 $button = array(
109 'attr' => array(
110 'href' => '#',
111 ),
112 'html' => 'Demo',
113 );
114 $data['buttons_toolbar'][] = $button;
45 115
46 return $data; 116 return $data;
47} 117}
@@ -126,8 +196,19 @@ function hook_demo_plugin_render_footer($data)
126 */ 196 */
127function hook_demo_plugin_render_linklist($data) 197function hook_demo_plugin_render_linklist($data)
128{ 198{
129 // action_plugin 199 /*
130 $data['action_plugin'][] = '<div class="upper_plugin_demo"><a href="?up" title="Uppercase!">←</a></div>'; 200 * Action links (action_plugin):
201 * A link is an array of its attributes (key="value"),
202 * and a mandatory `html` key, which contains its value.
203 * It's also recommended to add key 'on' or 'off' for theme rendering.
204 */
205 $action = array(
206 'attr' => array(
207 'href' => '?up',
208 'title' => 'Uppercase!',
209 ),
210 'html' => '←',
211 );
131 212
132 if (isset($_GET['up'])) { 213 if (isset($_GET['up'])) {
133 // Manipulate link data 214 // Manipulate link data
@@ -135,7 +216,11 @@ function hook_demo_plugin_render_linklist($data)
135 $value['description'] = strtoupper($value['description']); 216 $value['description'] = strtoupper($value['description']);
136 $value['title'] = strtoupper($value['title']); 217 $value['title'] = strtoupper($value['title']);
137 } 218 }
219 $action['on'] = true;
220 } else {
221 $action['off'] = true;
138 } 222 }
223 $data['action_plugin'][] = $action;
139 224
140 // link_plugin (for each link) 225 // link_plugin (for each link)
141 foreach ($data['links'] as &$value) { 226 foreach ($data['links'] as &$value) {
diff --git a/plugins/isso/isso.css b/plugins/isso/isso.css
new file mode 100644
index 00000000..b89babc3
--- /dev/null
+++ b/plugins/isso/isso.css
@@ -0,0 +1,3 @@
1#isso-thread > h4 {
2 text-align: center;
3}
diff --git a/plugins/isso/isso.html b/plugins/isso/isso.html
new file mode 100644
index 00000000..babab9b8
--- /dev/null
+++ b/plugins/isso/isso.html
@@ -0,0 +1,14 @@
1<script data-isso="//%s"
2 data-isso-css="true"
3 data-isso-lang="en"
4 data-isso-reply-to-self="true"
5 data-isso-max-comments-top="20"
6 data-isso-max-comments-nested="5"
7 data-isso-reveal-on-click="5"
8 data-isso-avatar="true"
9 data-isso-avatar-bg="#f0f0f0"
10 data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
11 data-isso-vote="true"
12 src="//%s/js/embed.min.js">
13</script>
14<section id="isso-thread" data-isso-id="%s" data-title="%s"></section>
diff --git a/plugins/isso/isso.meta b/plugins/isso/isso.meta
new file mode 100644
index 00000000..62473abc
--- /dev/null
+++ b/plugins/isso/isso.meta
@@ -0,0 +1,3 @@
1description="Let visitor comment your shaares on permalinks with Isso."
2parameters="ISSO_SERVER"
3parameter.ISSO_SERVER="Isso server URL (without 'http://')"
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php
new file mode 100644
index 00000000..ce16645f
--- /dev/null
+++ b/plugins/isso/isso.php
@@ -0,0 +1,54 @@
1<?php
2
3/**
4 * Plugin Isso.
5 */
6
7/**
8 * Display an error everywhere if the plugin is enabled without configuration.
9 *
10 * @param $data array List of links
11 * @param $conf ConfigManager instance
12 *
13 * @return mixed - linklist data with Isso plugin.
14 */
15function isso_init($conf)
16{
17 $issoUrl = $conf->get('plugins.ISSO_SERVER');
18 if (empty($issoUrl)) {
19 $error = 'Isso plugin error: '.
20 'Please define the "ISSO_SERVER" setting in the plugin administration page.';
21 return array($error);
22 }
23}
24
25/**
26 * Render linklist hook.
27 * Will only display Isso comments on permalinks.
28 *
29 * @param $data array List of links
30 * @param $conf ConfigManager instance
31 *
32 * @return mixed - linklist data with Isso plugin.
33 */
34function hook_isso_render_linklist($data, $conf)
35{
36 $issoUrl = $conf->get('plugins.ISSO_SERVER');
37 if (empty($issoUrl)) {
38 return $data;
39 }
40
41 // Only display comments for permalinks.
42 if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
43 $link = reset($data['links']);
44 $issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
45
46 $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
47 $data['plugin_end_zone'][] = $isso;
48
49 // Hackish way to include this CSS file only when necessary.
50 $data['plugins_includes']['css_files'][] = PluginManager::$PLUGINS_PATH . '/isso/isso.css';
51 }
52
53 return $data;
54}
diff --git a/plugins/markdown/Parsedown.php b/plugins/markdown/Parsedown.php
deleted file mode 100644
index 91e05dcc..00000000
--- a/plugins/markdown/Parsedown.php
+++ /dev/null
@@ -1,1528 +0,0 @@
1<?php
2
3#
4#
5# Parsedown
6# http://parsedown.org
7#
8# (c) Emanuil Rusev
9# http://erusev.com
10#
11# For the full license information, view the LICENSE file that was distributed
12# with this source code.
13#
14#
15
16class Parsedown
17{
18 # ~
19
20 const version = '1.6.0';
21
22 # ~
23
24 function text($text)
25 {
26 # make sure no definitions are set
27 $this->DefinitionData = array();
28
29 # standardize line breaks
30 $text = str_replace(array("\r\n", "\r"), "\n", $text);
31
32 # remove surrounding line breaks
33 $text = trim($text, "\n");
34
35 # split text into lines
36 $lines = explode("\n", $text);
37
38 # iterate through lines to identify blocks
39 $markup = $this->lines($lines);
40
41 # trim line breaks
42 $markup = trim($markup, "\n");
43
44 return $markup;
45 }
46
47 #
48 # Setters
49 #
50
51 function setBreaksEnabled($breaksEnabled)
52 {
53 $this->breaksEnabled = $breaksEnabled;
54
55 return $this;
56 }
57
58 protected $breaksEnabled;
59
60 function setMarkupEscaped($markupEscaped)
61 {
62 $this->markupEscaped = $markupEscaped;
63
64 return $this;
65 }
66
67 protected $markupEscaped;
68
69 function setUrlsLinked($urlsLinked)
70 {
71 $this->urlsLinked = $urlsLinked;
72
73 return $this;
74 }
75
76 protected $urlsLinked = true;
77
78 #
79 # Lines
80 #
81
82 protected $BlockTypes = array(
83 '#' => array('Header'),
84 '*' => array('Rule', 'List'),
85 '+' => array('List'),
86 '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
87 '0' => array('List'),
88 '1' => array('List'),
89 '2' => array('List'),
90 '3' => array('List'),
91 '4' => array('List'),
92 '5' => array('List'),
93 '6' => array('List'),
94 '7' => array('List'),
95 '8' => array('List'),
96 '9' => array('List'),
97 ':' => array('Table'),
98 '<' => array('Comment', 'Markup'),
99 '=' => array('SetextHeader'),
100 '>' => array('Quote'),
101 '[' => array('Reference'),
102 '_' => array('Rule'),
103 '`' => array('FencedCode'),
104 '|' => array('Table'),
105 '~' => array('FencedCode'),
106 );
107
108 # ~
109
110 protected $unmarkedBlockTypes = array(
111 'Code',
112 );
113
114 #
115 # Blocks
116 #
117
118 private function lines(array $lines)
119 {
120 $CurrentBlock = null;
121
122 foreach ($lines as $line)
123 {
124 if (chop($line) === '')
125 {
126 if (isset($CurrentBlock))
127 {
128 $CurrentBlock['interrupted'] = true;
129 }
130
131 continue;
132 }
133
134 if (strpos($line, "\t") !== false)
135 {
136 $parts = explode("\t", $line);
137
138 $line = $parts[0];
139
140 unset($parts[0]);
141
142 foreach ($parts as $part)
143 {
144 $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
145
146 $line .= str_repeat(' ', $shortage);
147 $line .= $part;
148 }
149 }
150
151 $indent = 0;
152
153 while (isset($line[$indent]) and $line[$indent] === ' ')
154 {
155 $indent ++;
156 }
157
158 $text = $indent > 0 ? substr($line, $indent) : $line;
159
160 # ~
161
162 $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
163
164 # ~
165
166 if (isset($CurrentBlock['continuable']))
167 {
168 $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
169
170 if (isset($Block))
171 {
172 $CurrentBlock = $Block;
173
174 continue;
175 }
176 else
177 {
178 if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
179 {
180 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
181 }
182 }
183 }
184
185 # ~
186
187 $marker = $text[0];
188
189 # ~
190
191 $blockTypes = $this->unmarkedBlockTypes;
192
193 if (isset($this->BlockTypes[$marker]))
194 {
195 foreach ($this->BlockTypes[$marker] as $blockType)
196 {
197 $blockTypes []= $blockType;
198 }
199 }
200
201 #
202 # ~
203
204 foreach ($blockTypes as $blockType)
205 {
206 $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
207
208 if (isset($Block))
209 {
210 $Block['type'] = $blockType;
211
212 if ( ! isset($Block['identified']))
213 {
214 $Blocks []= $CurrentBlock;
215
216 $Block['identified'] = true;
217 }
218
219 if (method_exists($this, 'block'.$blockType.'Continue'))
220 {
221 $Block['continuable'] = true;
222 }
223
224 $CurrentBlock = $Block;
225
226 continue 2;
227 }
228 }
229
230 # ~
231
232 if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
233 {
234 $CurrentBlock['element']['text'] .= "\n".$text;
235 }
236 else
237 {
238 $Blocks []= $CurrentBlock;
239
240 $CurrentBlock = $this->paragraph($Line);
241
242 $CurrentBlock['identified'] = true;
243 }
244 }
245
246 # ~
247
248 if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
249 {
250 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
251 }
252
253 # ~
254
255 $Blocks []= $CurrentBlock;
256
257 unset($Blocks[0]);
258
259 # ~
260
261 $markup = '';
262
263 foreach ($Blocks as $Block)
264 {
265 if (isset($Block['hidden']))
266 {
267 continue;
268 }
269
270 $markup .= "\n";
271 $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
272 }
273
274 $markup .= "\n";
275
276 # ~
277
278 return $markup;
279 }
280
281 #
282 # Code
283
284 protected function blockCode($Line, $Block = null)
285 {
286 if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
287 {
288 return;
289 }
290
291 if ($Line['indent'] >= 4)
292 {
293 $text = substr($Line['body'], 4);
294
295 $Block = array(
296 'element' => array(
297 'name' => 'pre',
298 'handler' => 'element',
299 'text' => array(
300 'name' => 'code',
301 'text' => $text,
302 ),
303 ),
304 );
305
306 return $Block;
307 }
308 }
309
310 protected function blockCodeContinue($Line, $Block)
311 {
312 if ($Line['indent'] >= 4)
313 {
314 if (isset($Block['interrupted']))
315 {
316 $Block['element']['text']['text'] .= "\n";
317
318 unset($Block['interrupted']);
319 }
320
321 $Block['element']['text']['text'] .= "\n";
322
323 $text = substr($Line['body'], 4);
324
325 $Block['element']['text']['text'] .= $text;
326
327 return $Block;
328 }
329 }
330
331 protected function blockCodeComplete($Block)
332 {
333 $text = $Block['element']['text']['text'];
334
335 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
336
337 $Block['element']['text']['text'] = $text;
338
339 return $Block;
340 }
341
342 #
343 # Comment
344
345 protected function blockComment($Line)
346 {
347 if ($this->markupEscaped)
348 {
349 return;
350 }
351
352 if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
353 {
354 $Block = array(
355 'markup' => $Line['body'],
356 );
357
358 if (preg_match('/-->$/', $Line['text']))
359 {
360 $Block['closed'] = true;
361 }
362
363 return $Block;
364 }
365 }
366
367 protected function blockCommentContinue($Line, array $Block)
368 {
369 if (isset($Block['closed']))
370 {
371 return;
372 }
373
374 $Block['markup'] .= "\n" . $Line['body'];
375
376 if (preg_match('/-->$/', $Line['text']))
377 {
378 $Block['closed'] = true;
379 }
380
381 return $Block;
382 }
383
384 #
385 # Fenced Code
386
387 protected function blockFencedCode($Line)
388 {
389 if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
390 {
391 $Element = array(
392 'name' => 'code',
393 'text' => '',
394 );
395
396 if (isset($matches[1]))
397 {
398 $class = 'language-'.$matches[1];
399
400 $Element['attributes'] = array(
401 'class' => $class,
402 );
403 }
404
405 $Block = array(
406 'char' => $Line['text'][0],
407 'element' => array(
408 'name' => 'pre',
409 'handler' => 'element',
410 'text' => $Element,
411 ),
412 );
413
414 return $Block;
415 }
416 }
417
418 protected function blockFencedCodeContinue($Line, $Block)
419 {
420 if (isset($Block['complete']))
421 {
422 return;
423 }
424
425 if (isset($Block['interrupted']))
426 {
427 $Block['element']['text']['text'] .= "\n";
428
429 unset($Block['interrupted']);
430 }
431
432 if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
433 {
434 $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
435
436 $Block['complete'] = true;
437
438 return $Block;
439 }
440
441 $Block['element']['text']['text'] .= "\n".$Line['body'];;
442
443 return $Block;
444 }
445
446 protected function blockFencedCodeComplete($Block)
447 {
448 $text = $Block['element']['text']['text'];
449
450 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
451
452 $Block['element']['text']['text'] = $text;
453
454 return $Block;
455 }
456
457 #
458 # Header
459
460 protected function blockHeader($Line)
461 {
462 if (isset($Line['text'][1]))
463 {
464 $level = 1;
465
466 while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
467 {
468 $level ++;
469 }
470
471 if ($level > 6)
472 {
473 return;
474 }
475
476 $text = trim($Line['text'], '# ');
477
478 $Block = array(
479 'element' => array(
480 'name' => 'h' . min(6, $level),
481 'text' => $text,
482 'handler' => 'line',
483 ),
484 );
485
486 return $Block;
487 }
488 }
489
490 #
491 # List
492
493 protected function blockList($Line)
494 {
495 list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
496
497 if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
498 {
499 $Block = array(
500 'indent' => $Line['indent'],
501 'pattern' => $pattern,
502 'element' => array(
503 'name' => $name,
504 'handler' => 'elements',
505 ),
506 );
507
508 $Block['li'] = array(
509 'name' => 'li',
510 'handler' => 'li',
511 'text' => array(
512 $matches[2],
513 ),
514 );
515
516 $Block['element']['text'] []= & $Block['li'];
517
518 return $Block;
519 }
520 }
521
522 protected function blockListContinue($Line, array $Block)
523 {
524 if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
525 {
526 if (isset($Block['interrupted']))
527 {
528 $Block['li']['text'] []= '';
529
530 unset($Block['interrupted']);
531 }
532
533 unset($Block['li']);
534
535 $text = isset($matches[1]) ? $matches[1] : '';
536
537 $Block['li'] = array(
538 'name' => 'li',
539 'handler' => 'li',
540 'text' => array(
541 $text,
542 ),
543 );
544
545 $Block['element']['text'] []= & $Block['li'];
546
547 return $Block;
548 }
549
550 if ($Line['text'][0] === '[' and $this->blockReference($Line))
551 {
552 return $Block;
553 }
554
555 if ( ! isset($Block['interrupted']))
556 {
557 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
558
559 $Block['li']['text'] []= $text;
560
561 return $Block;
562 }
563
564 if ($Line['indent'] > 0)
565 {
566 $Block['li']['text'] []= '';
567
568 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
569
570 $Block['li']['text'] []= $text;
571
572 unset($Block['interrupted']);
573
574 return $Block;
575 }
576 }
577
578 #
579 # Quote
580
581 protected function blockQuote($Line)
582 {
583 if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
584 {
585 $Block = array(
586 'element' => array(
587 'name' => 'blockquote',
588 'handler' => 'lines',
589 'text' => (array) $matches[1],
590 ),
591 );
592
593 return $Block;
594 }
595 }
596
597 protected function blockQuoteContinue($Line, array $Block)
598 {
599 if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
600 {
601 if (isset($Block['interrupted']))
602 {
603 $Block['element']['text'] []= '';
604
605 unset($Block['interrupted']);
606 }
607
608 $Block['element']['text'] []= $matches[1];
609
610 return $Block;
611 }
612
613 if ( ! isset($Block['interrupted']))
614 {
615 $Block['element']['text'] []= $Line['text'];
616
617 return $Block;
618 }
619 }
620
621 #
622 # Rule
623
624 protected function blockRule($Line)
625 {
626 if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
627 {
628 $Block = array(
629 'element' => array(
630 'name' => 'hr'
631 ),
632 );
633
634 return $Block;
635 }
636 }
637
638 #
639 # Setext
640
641 protected function blockSetextHeader($Line, array $Block = null)
642 {
643 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
644 {
645 return;
646 }
647
648 if (chop($Line['text'], $Line['text'][0]) === '')
649 {
650 $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
651
652 return $Block;
653 }
654 }
655
656 #
657 # Markup
658
659 protected function blockMarkup($Line)
660 {
661 if ($this->markupEscaped)
662 {
663 return;
664 }
665
666 if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
667 {
668 $element = strtolower($matches[1]);
669
670 if (in_array($element, $this->textLevelElements))
671 {
672 return;
673 }
674
675 $Block = array(
676 'name' => $matches[1],
677 'depth' => 0,
678 'markup' => $Line['text'],
679 );
680
681 $length = strlen($matches[0]);
682
683 $remainder = substr($Line['text'], $length);
684
685 if (trim($remainder) === '')
686 {
687 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
688 {
689 $Block['closed'] = true;
690
691 $Block['void'] = true;
692 }
693 }
694 else
695 {
696 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
697 {
698 return;
699 }
700
701 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
702 {
703 $Block['closed'] = true;
704 }
705 }
706
707 return $Block;
708 }
709 }
710
711 protected function blockMarkupContinue($Line, array $Block)
712 {
713 if (isset($Block['closed']))
714 {
715 return;
716 }
717
718 if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
719 {
720 $Block['depth'] ++;
721 }
722
723 if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
724 {
725 if ($Block['depth'] > 0)
726 {
727 $Block['depth'] --;
728 }
729 else
730 {
731 $Block['closed'] = true;
732 }
733 }
734
735 if (isset($Block['interrupted']))
736 {
737 $Block['markup'] .= "\n";
738
739 unset($Block['interrupted']);
740 }
741
742 $Block['markup'] .= "\n".$Line['body'];
743
744 return $Block;
745 }
746
747 #
748 # Reference
749
750 protected function blockReference($Line)
751 {
752 if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
753 {
754 $id = strtolower($matches[1]);
755
756 $Data = array(
757 'url' => $matches[2],
758 'title' => null,
759 );
760
761 if (isset($matches[3]))
762 {
763 $Data['title'] = $matches[3];
764 }
765
766 $this->DefinitionData['Reference'][$id] = $Data;
767
768 $Block = array(
769 'hidden' => true,
770 );
771
772 return $Block;
773 }
774 }
775
776 #
777 # Table
778
779 protected function blockTable($Line, array $Block = null)
780 {
781 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
782 {
783 return;
784 }
785
786 if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
787 {
788 $alignments = array();
789
790 $divider = $Line['text'];
791
792 $divider = trim($divider);
793 $divider = trim($divider, '|');
794
795 $dividerCells = explode('|', $divider);
796
797 foreach ($dividerCells as $dividerCell)
798 {
799 $dividerCell = trim($dividerCell);
800
801 if ($dividerCell === '')
802 {
803 continue;
804 }
805
806 $alignment = null;
807
808 if ($dividerCell[0] === ':')
809 {
810 $alignment = 'left';
811 }
812
813 if (substr($dividerCell, - 1) === ':')
814 {
815 $alignment = $alignment === 'left' ? 'center' : 'right';
816 }
817
818 $alignments []= $alignment;
819 }
820
821 # ~
822
823 $HeaderElements = array();
824
825 $header = $Block['element']['text'];
826
827 $header = trim($header);
828 $header = trim($header, '|');
829
830 $headerCells = explode('|', $header);
831
832 foreach ($headerCells as $index => $headerCell)
833 {
834 $headerCell = trim($headerCell);
835
836 $HeaderElement = array(
837 'name' => 'th',
838 'text' => $headerCell,
839 'handler' => 'line',
840 );
841
842 if (isset($alignments[$index]))
843 {
844 $alignment = $alignments[$index];
845
846 $HeaderElement['attributes'] = array(
847 'style' => 'text-align: '.$alignment.';',
848 );
849 }
850
851 $HeaderElements []= $HeaderElement;
852 }
853
854 # ~
855
856 $Block = array(
857 'alignments' => $alignments,
858 'identified' => true,
859 'element' => array(
860 'name' => 'table',
861 'handler' => 'elements',
862 ),
863 );
864
865 $Block['element']['text'] []= array(
866 'name' => 'thead',
867 'handler' => 'elements',
868 );
869
870 $Block['element']['text'] []= array(
871 'name' => 'tbody',
872 'handler' => 'elements',
873 'text' => array(),
874 );
875
876 $Block['element']['text'][0]['text'] []= array(
877 'name' => 'tr',
878 'handler' => 'elements',
879 'text' => $HeaderElements,
880 );
881
882 return $Block;
883 }
884 }
885
886 protected function blockTableContinue($Line, array $Block)
887 {
888 if (isset($Block['interrupted']))
889 {
890 return;
891 }
892
893 if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
894 {
895 $Elements = array();
896
897 $row = $Line['text'];
898
899 $row = trim($row);
900 $row = trim($row, '|');
901
902 preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
903
904 foreach ($matches[0] as $index => $cell)
905 {
906 $cell = trim($cell);
907
908 $Element = array(
909 'name' => 'td',
910 'handler' => 'line',
911 'text' => $cell,
912 );
913
914 if (isset($Block['alignments'][$index]))
915 {
916 $Element['attributes'] = array(
917 'style' => 'text-align: '.$Block['alignments'][$index].';',
918 );
919 }
920
921 $Elements []= $Element;
922 }
923
924 $Element = array(
925 'name' => 'tr',
926 'handler' => 'elements',
927 'text' => $Elements,
928 );
929
930 $Block['element']['text'][1]['text'] []= $Element;
931
932 return $Block;
933 }
934 }
935
936 #
937 # ~
938 #
939
940 protected function paragraph($Line)
941 {
942 $Block = array(
943 'element' => array(
944 'name' => 'p',
945 'text' => $Line['text'],
946 'handler' => 'line',
947 ),
948 );
949
950 return $Block;
951 }
952
953 #
954 # Inline Elements
955 #
956
957 protected $InlineTypes = array(
958 '"' => array('SpecialCharacter'),
959 '!' => array('Image'),
960 '&' => array('SpecialCharacter'),
961 '*' => array('Emphasis'),
962 ':' => array('Url'),
963 '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
964 '>' => array('SpecialCharacter'),
965 '[' => array('Link'),
966 '_' => array('Emphasis'),
967 '`' => array('Code'),
968 '~' => array('Strikethrough'),
969 '\\' => array('EscapeSequence'),
970 );
971
972 # ~
973
974 protected $inlineMarkerList = '!"*_&[:<>`~\\';
975
976 #
977 # ~
978 #
979
980 public function line($text)
981 {
982 $markup = '';
983
984 # $excerpt is based on the first occurrence of a marker
985
986 while ($excerpt = strpbrk($text, $this->inlineMarkerList))
987 {
988 $marker = $excerpt[0];
989
990 $markerPosition = strpos($text, $marker);
991
992 $Excerpt = array('text' => $excerpt, 'context' => $text);
993
994 foreach ($this->InlineTypes[$marker] as $inlineType)
995 {
996 $Inline = $this->{'inline'.$inlineType}($Excerpt);
997
998 if ( ! isset($Inline))
999 {
1000 continue;
1001 }
1002
1003 # makes sure that the inline belongs to "our" marker
1004
1005 if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1006 {
1007 continue;
1008 }
1009
1010 # sets a default inline position
1011
1012 if ( ! isset($Inline['position']))
1013 {
1014 $Inline['position'] = $markerPosition;
1015 }
1016
1017 # the text that comes before the inline
1018 $unmarkedText = substr($text, 0, $Inline['position']);
1019
1020 # compile the unmarked text
1021 $markup .= $this->unmarkedText($unmarkedText);
1022
1023 # compile the inline
1024 $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1025
1026 # remove the examined text
1027 $text = substr($text, $Inline['position'] + $Inline['extent']);
1028
1029 continue 2;
1030 }
1031
1032 # the marker does not belong to an inline
1033
1034 $unmarkedText = substr($text, 0, $markerPosition + 1);
1035
1036 $markup .= $this->unmarkedText($unmarkedText);
1037
1038 $text = substr($text, $markerPosition + 1);
1039 }
1040
1041 $markup .= $this->unmarkedText($text);
1042
1043 return $markup;
1044 }
1045
1046 #
1047 # ~
1048 #
1049
1050 protected function inlineCode($Excerpt)
1051 {
1052 $marker = $Excerpt['text'][0];
1053
1054 if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1055 {
1056 $text = $matches[2];
1057 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
1058 $text = preg_replace("/[ ]*\n/", ' ', $text);
1059
1060 return array(
1061 'extent' => strlen($matches[0]),
1062 'element' => array(
1063 'name' => 'code',
1064 'text' => $text,
1065 ),
1066 );
1067 }
1068 }
1069
1070 protected function inlineEmailTag($Excerpt)
1071 {
1072 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1073 {
1074 $url = $matches[1];
1075
1076 if ( ! isset($matches[2]))
1077 {
1078 $url = 'mailto:' . $url;
1079 }
1080
1081 return array(
1082 'extent' => strlen($matches[0]),
1083 'element' => array(
1084 'name' => 'a',
1085 'text' => $matches[1],
1086 'attributes' => array(
1087 'href' => $url,
1088 ),
1089 ),
1090 );
1091 }
1092 }
1093
1094 protected function inlineEmphasis($Excerpt)
1095 {
1096 if ( ! isset($Excerpt['text'][1]))
1097 {
1098 return;
1099 }
1100
1101 $marker = $Excerpt['text'][0];
1102
1103 if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1104 {
1105 $emphasis = 'strong';
1106 }
1107 elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1108 {
1109 $emphasis = 'em';
1110 }
1111 else
1112 {
1113 return;
1114 }
1115
1116 return array(
1117 'extent' => strlen($matches[0]),
1118 'element' => array(
1119 'name' => $emphasis,
1120 'handler' => 'line',
1121 'text' => $matches[1],
1122 ),
1123 );
1124 }
1125
1126 protected function inlineEscapeSequence($Excerpt)
1127 {
1128 if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1129 {
1130 return array(
1131 'markup' => $Excerpt['text'][1],
1132 'extent' => 2,
1133 );
1134 }
1135 }
1136
1137 protected function inlineImage($Excerpt)
1138 {
1139 if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1140 {
1141 return;
1142 }
1143
1144 $Excerpt['text']= substr($Excerpt['text'], 1);
1145
1146 $Link = $this->inlineLink($Excerpt);
1147
1148 if ($Link === null)
1149 {
1150 return;
1151 }
1152
1153 $Inline = array(
1154 'extent' => $Link['extent'] + 1,
1155 'element' => array(
1156 'name' => 'img',
1157 'attributes' => array(
1158 'src' => $Link['element']['attributes']['href'],
1159 'alt' => $Link['element']['text'],
1160 ),
1161 ),
1162 );
1163
1164 $Inline['element']['attributes'] += $Link['element']['attributes'];
1165
1166 unset($Inline['element']['attributes']['href']);
1167
1168 return $Inline;
1169 }
1170
1171 protected function inlineLink($Excerpt)
1172 {
1173 $Element = array(
1174 'name' => 'a',
1175 'handler' => 'line',
1176 'text' => null,
1177 'attributes' => array(
1178 'href' => null,
1179 'title' => null,
1180 ),
1181 );
1182
1183 $extent = 0;
1184
1185 $remainder = $Excerpt['text'];
1186
1187 if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
1188 {
1189 $Element['text'] = $matches[1];
1190
1191 $extent += strlen($matches[0]);
1192
1193 $remainder = substr($remainder, $extent);
1194 }
1195 else
1196 {
1197 return;
1198 }
1199
1200 if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
1201 {
1202 $Element['attributes']['href'] = $matches[1];
1203
1204 if (isset($matches[2]))
1205 {
1206 $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1207 }
1208
1209 $extent += strlen($matches[0]);
1210 }
1211 else
1212 {
1213 if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1214 {
1215 $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1216 $definition = strtolower($definition);
1217
1218 $extent += strlen($matches[0]);
1219 }
1220 else
1221 {
1222 $definition = strtolower($Element['text']);
1223 }
1224
1225 if ( ! isset($this->DefinitionData['Reference'][$definition]))
1226 {
1227 return;
1228 }
1229
1230 $Definition = $this->DefinitionData['Reference'][$definition];
1231
1232 $Element['attributes']['href'] = $Definition['url'];
1233 $Element['attributes']['title'] = $Definition['title'];
1234 }
1235
1236 $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
1237
1238 return array(
1239 'extent' => $extent,
1240 'element' => $Element,
1241 );
1242 }
1243
1244 protected function inlineMarkup($Excerpt)
1245 {
1246 if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
1247 {
1248 return;
1249 }
1250
1251 if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1252 {
1253 return array(
1254 'markup' => $matches[0],
1255 'extent' => strlen($matches[0]),
1256 );
1257 }
1258
1259 if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1260 {
1261 return array(
1262 'markup' => $matches[0],
1263 'extent' => strlen($matches[0]),
1264 );
1265 }
1266
1267 if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1268 {
1269 return array(
1270 'markup' => $matches[0],
1271 'extent' => strlen($matches[0]),
1272 );
1273 }
1274 }
1275
1276 protected function inlineSpecialCharacter($Excerpt)
1277 {
1278 if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1279 {
1280 return array(
1281 'markup' => '&amp;',
1282 'extent' => 1,
1283 );
1284 }
1285
1286 $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1287
1288 if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1289 {
1290 return array(
1291 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1292 'extent' => 1,
1293 );
1294 }
1295 }
1296
1297 protected function inlineStrikethrough($Excerpt)
1298 {
1299 if ( ! isset($Excerpt['text'][1]))
1300 {
1301 return;
1302 }
1303
1304 if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1305 {
1306 return array(
1307 'extent' => strlen($matches[0]),
1308 'element' => array(
1309 'name' => 'del',
1310 'text' => $matches[1],
1311 'handler' => 'line',
1312 ),
1313 );
1314 }
1315 }
1316
1317 protected function inlineUrl($Excerpt)
1318 {
1319 if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1320 {
1321 return;
1322 }
1323
1324 if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1325 {
1326 $Inline = array(
1327 'extent' => strlen($matches[0][0]),
1328 'position' => $matches[0][1],
1329 'element' => array(
1330 'name' => 'a',
1331 'text' => $matches[0][0],
1332 'attributes' => array(
1333 'href' => $matches[0][0],
1334 ),
1335 ),
1336 );
1337
1338 return $Inline;
1339 }
1340 }
1341
1342 protected function inlineUrlTag($Excerpt)
1343 {
1344 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1345 {
1346 $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
1347
1348 return array(
1349 'extent' => strlen($matches[0]),
1350 'element' => array(
1351 'name' => 'a',
1352 'text' => $url,
1353 'attributes' => array(
1354 'href' => $url,
1355 ),
1356 ),
1357 );
1358 }
1359 }
1360
1361 # ~
1362
1363 protected function unmarkedText($text)
1364 {
1365 if ($this->breaksEnabled)
1366 {
1367 $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1368 }
1369 else
1370 {
1371 $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1372 $text = str_replace(" \n", "\n", $text);
1373 }
1374
1375 return $text;
1376 }
1377
1378 #
1379 # Handlers
1380 #
1381
1382 protected function element(array $Element)
1383 {
1384 $markup = '<'.$Element['name'];
1385
1386 if (isset($Element['attributes']))
1387 {
1388 foreach ($Element['attributes'] as $name => $value)
1389 {
1390 if ($value === null)
1391 {
1392 continue;
1393 }
1394
1395 $markup .= ' '.$name.'="'.$value.'"';
1396 }
1397 }
1398
1399 if (isset($Element['text']))
1400 {
1401 $markup .= '>';
1402
1403 if (isset($Element['handler']))
1404 {
1405 $markup .= $this->{$Element['handler']}($Element['text']);
1406 }
1407 else
1408 {
1409 $markup .= $Element['text'];
1410 }
1411
1412 $markup .= '</'.$Element['name'].'>';
1413 }
1414 else
1415 {
1416 $markup .= ' />';
1417 }
1418
1419 return $markup;
1420 }
1421
1422 protected function elements(array $Elements)
1423 {
1424 $markup = '';
1425
1426 foreach ($Elements as $Element)
1427 {
1428 $markup .= "\n" . $this->element($Element);
1429 }
1430
1431 $markup .= "\n";
1432
1433 return $markup;
1434 }
1435
1436 # ~
1437
1438 protected function li($lines)
1439 {
1440 $markup = $this->lines($lines);
1441
1442 $trimmedMarkup = trim($markup);
1443
1444 if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1445 {
1446 $markup = $trimmedMarkup;
1447 $markup = substr($markup, 3);
1448
1449 $position = strpos($markup, "</p>");
1450
1451 $markup = substr_replace($markup, '', $position, 4);
1452 }
1453
1454 return $markup;
1455 }
1456
1457 #
1458 # Deprecated Methods
1459 #
1460
1461 function parse($text)
1462 {
1463 $markup = $this->text($text);
1464
1465 return $markup;
1466 }
1467
1468 #
1469 # Static Methods
1470 #
1471
1472 static function instance($name = 'default')
1473 {
1474 if (isset(self::$instances[$name]))
1475 {
1476 return self::$instances[$name];
1477 }
1478
1479 $instance = new static();
1480
1481 self::$instances[$name] = $instance;
1482
1483 return $instance;
1484 }
1485
1486 private static $instances = array();
1487
1488 #
1489 # Fields
1490 #
1491
1492 protected $DefinitionData;
1493
1494 #
1495 # Read-Only
1496
1497 protected $specialCharacters = array(
1498 '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1499 );
1500
1501 protected $StrongRegex = array(
1502 '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1503 '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1504 );
1505
1506 protected $EmRegex = array(
1507 '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1508 '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1509 );
1510
1511 protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1512
1513 protected $voidElements = array(
1514 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1515 );
1516
1517 protected $textLevelElements = array(
1518 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1519 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1520 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1521 'q', 'rt', 'ins', 'font', 'strong',
1522 's', 'tt', 'sub', 'mark',
1523 'u', 'xm', 'sup', 'nobr',
1524 'var', 'ruby',
1525 'wbr', 'span',
1526 'time',
1527 );
1528} \ No newline at end of file
diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md
index 4f021871..bc9427e2 100644
--- a/plugins/markdown/README.md
+++ b/plugins/markdown/README.md
@@ -20,26 +20,65 @@ The directory structure should look like:
20 |--- markdown.css 20 |--- markdown.css
21 |--- markdown.meta 21 |--- markdown.meta
22 |--- markdown.php 22 |--- markdown.php
23 |--- Parsedown.php
24 |--- README.md 23 |--- README.md
25``` 24```
26 25
27To enable the plugin, just check it in the plugin administration page. 26To enable the plugin, just check it in the plugin administration page.
28 27
29You can also add `markdown` to your list of enabled plugins in `data/config.php` 28You can also add `markdown` to your list of enabled plugins in `data/config.json.php`
30(`ENABLED_PLUGINS` array). 29(`general.enabled_plugins` list).
31 30
32This should look like: 31This should look like:
33 32
34``` 33```
35$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown') 34"general": {
35 "enabled_plugins": [
36 "markdown",
37 [...]
38 ],
39}
36``` 40```
37 41
42Parsedown parsing library is imported using Composer. If you installed Shaarli using `git`,
43or the `master` branch, run
44
45 composer update --no-dev --prefer-dist
46
38### No Markdown tag 47### No Markdown tag
39 48
40If the tag `.nomarkdown` is set for a shaare, it won't be converted to Markdown syntax. 49If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
41 50
42> Note: it's a private tag (leading dot), so it won't be displayed to visitors. 51> Note: this is a special tag, so it won't be displayed in link list.
52
53### HTML escape
54
55By default, HTML tags are escaped. You can enable HTML tags rendering
56by setting `security.markdwon_escape` to `false` in `data/config.json.php`:
57
58```json
59{
60 "security": {
61 "markdown_escape": false
62 }
63}
64```
65
66With this setting, Markdown support HTML tags. For example:
67
68 > <strong>strong</strong><strike>strike</strike>
69
70Will render as:
71
72> <strong>strong</strong><strike>strike</strike>
73
74
75**Warning:**
76
77 * This setting might present **security risks** (XSS) on shared instances, even though tags
78 such as script, iframe, etc should be disabled.
79 * If you want to shaare HTML code, it is necessary to use inline code or code blocks.
80 * If your shaared descriptions contained HTML tags before enabling the markdown plugin,
81enabling it might break your page.
43 82
44### Known issue 83### Known issue
45 84
diff --git a/plugins/markdown/markdown.meta b/plugins/markdown/markdown.meta
index e3904ed8..8df2ed0b 100644
--- a/plugins/markdown/markdown.meta
+++ b/plugins/markdown/markdown.meta
@@ -1 +1,4 @@
1description="Render shaare description with Markdown syntax." 1description="Render shaare description with Markdown syntax.<br><strong>Warning</strong>:
2If your shaared descriptions containing HTML tags before enabling the markdown plugin,
3enabling it might break your page.
4See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>."
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index 57fcce32..de7c823d 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -6,30 +6,28 @@
6 * Shaare's descriptions are parsed with Markdown. 6 * Shaare's descriptions are parsed with Markdown.
7 */ 7 */
8 8
9require_once 'Parsedown.php';
10
11/* 9/*
12 * If this tag is used on a shaare, the description won't be processed by Parsedown. 10 * If this tag is used on a shaare, the description won't be processed by Parsedown.
13 * Using a private tag so it won't appear for visitors.
14 */ 11 */
15define('NO_MD_TAG', '.nomarkdown'); 12define('NO_MD_TAG', 'nomarkdown');
16 13
17/** 14/**
18 * Parse linklist descriptions. 15 * Parse linklist descriptions.
19 * 16 *
20 * @param array $data linklist data. 17 * @param array $data linklist data.
18 * @param ConfigManager $conf instance.
21 * 19 *
22 * @return mixed linklist data parsed in markdown (and converted to HTML). 20 * @return mixed linklist data parsed in markdown (and converted to HTML).
23 */ 21 */
24function hook_markdown_render_linklist($data) 22function hook_markdown_render_linklist($data, $conf)
25{ 23{
26 foreach ($data['links'] as &$value) { 24 foreach ($data['links'] as &$value) {
27 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { 25 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
26 $value = stripNoMarkdownTag($value);
28 continue; 27 continue;
29 } 28 }
30 $value['description'] = process_markdown($value['description']); 29 $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true));
31 } 30 }
32
33 return $data; 31 return $data;
34} 32}
35 33
@@ -37,16 +35,18 @@ function hook_markdown_render_linklist($data)
37 * Parse feed linklist descriptions. 35 * Parse feed linklist descriptions.
38 * 36 *
39 * @param array $data linklist data. 37 * @param array $data linklist data.
38 * @param ConfigManager $conf instance.
40 * 39 *
41 * @return mixed linklist data parsed in markdown (and converted to HTML). 40 * @return mixed linklist data parsed in markdown (and converted to HTML).
42 */ 41 */
43function hook_markdown_render_feed($data) 42function hook_markdown_render_feed($data, $conf)
44{ 43{
45 foreach ($data['links'] as &$value) { 44 foreach ($data['links'] as &$value) {
46 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { 45 if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
46 $value = stripNoMarkdownTag($value);
47 continue; 47 continue;
48 } 48 }
49 $value['description'] = process_markdown($value['description']); 49 $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true));
50 } 50 }
51 51
52 return $data; 52 return $data;
@@ -55,19 +55,24 @@ function hook_markdown_render_feed($data)
55/** 55/**
56 * Parse daily descriptions. 56 * Parse daily descriptions.
57 * 57 *
58 * @param array $data daily data. 58 * @param array $data daily data.
59 * @param ConfigManager $conf instance.
59 * 60 *
60 * @return mixed daily data parsed in markdown (and converted to HTML). 61 * @return mixed daily data parsed in markdown (and converted to HTML).
61 */ 62 */
62function hook_markdown_render_daily($data) 63function hook_markdown_render_daily($data, $conf)
63{ 64{
64 // Manipulate columns data 65 // Manipulate columns data
65 foreach ($data['cols'] as &$value) { 66 foreach ($data['cols'] as &$value) {
66 foreach ($value as &$value2) { 67 foreach ($value as &$value2) {
67 if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) { 68 if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) {
69 $value2 = stripNoMarkdownTag($value2);
68 continue; 70 continue;
69 } 71 }
70 $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); 72 $value2['formatedDescription'] = process_markdown(
73 $value2['formatedDescription'],
74 $conf->get('security.markdown_escape', true)
75 );
71 } 76 }
72 } 77 }
73 78
@@ -83,7 +88,30 @@ function hook_markdown_render_daily($data)
83 */ 88 */
84function noMarkdownTag($tags) 89function noMarkdownTag($tags)
85{ 90{
86 return strpos($tags, NO_MD_TAG) !== false; 91 return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags);
92}
93
94/**
95 * Remove the no-markdown meta tag so it won't be displayed.
96 *
97 * @param array $link Link data.
98 *
99 * @return array Updated link without no markdown tag.
100 */
101function stripNoMarkdownTag($link)
102{
103 if (! empty($link['taglist'])) {
104 $offset = array_search(NO_MD_TAG, $link['taglist']);
105 if ($offset !== false) {
106 unset($link['taglist'][$offset]);
107 }
108 }
109
110 if (!empty($link['tags'])) {
111 str_replace(NO_MD_TAG, '', $link['tags']);
112 }
113
114 return $link;
87} 115}
88 116
89/** 117/**
@@ -138,7 +166,45 @@ function hook_markdown_render_editlink($data)
138 */ 166 */
139function reverse_text2clickable($description) 167function reverse_text2clickable($description)
140{ 168{
141 return preg_replace('!<a +href="([^ ]*)">[^ ]+</a>!m', '$1', $description); 169 $descriptionLines = explode(PHP_EOL, $description);
170 $descriptionOut = '';
171 $codeBlockOn = false;
172 $lineCount = 0;
173
174 foreach ($descriptionLines as $descriptionLine) {
175 // Detect line of code: starting with 4 spaces,
176 // except lists which can start with +/*/- or `2.` after spaces.
177 $codeLineOn = preg_match('/^ +(?=[^\+\*\-])(?=(?!\d\.).)/', $descriptionLine) > 0;
178 // Detect and toggle block of code
179 if (!$codeBlockOn) {
180 $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0;
181 }
182 elseif (preg_match('/^```/', $descriptionLine) > 0) {
183 $codeBlockOn = false;
184 }
185
186 $hashtagTitle = ' title="Hashtag [^"]+"';
187 // Reverse `inline code` hashtags.
188 $descriptionLine = preg_replace(
189 '!(`[^`\n]*)<a href="[^ ]*"'. $hashtagTitle .'>([^<]+)</a>([^`\n]*`)!m',
190 '$1$2$3',
191 $descriptionLine
192 );
193
194 // Reverse all links in code blocks, only non hashtag elsewhere.
195 $hashtagFilter = (!$codeBlockOn && !$codeLineOn) ? '(?!'. $hashtagTitle .')': '(?:'. $hashtagTitle .')?';
196 $descriptionLine = preg_replace(
197 '#<a href="[^ ]*"'. $hashtagFilter .'>([^<]+)</a>#m',
198 '$1',
199 $descriptionLine
200 );
201
202 $descriptionOut .= $descriptionLine;
203 if ($lineCount++ < count($descriptionLines) - 1) {
204 $descriptionOut .= PHP_EOL;
205 }
206 }
207 return $descriptionOut;
142} 208}
143 209
144/** 210/**
@@ -190,7 +256,7 @@ function sanitize_html($description)
190 $description); 256 $description);
191 } 257 }
192 $description = preg_replace( 258 $description = preg_replace(
193 '#(<[^>]+)on[a-z]*="[^"]*"#is', 259 '#(<[^>]+)on[a-z]*="?[^ "]*"?#is',
194 '$1', 260 '$1',
195 $description); 261 $description);
196 return $description; 262 return $description;
@@ -205,20 +271,21 @@ function sanitize_html($description)
205 * 5. Wrap description in 'markdown' CSS class. 271 * 5. Wrap description in 'markdown' CSS class.
206 * 272 *
207 * @param string $description input description text. 273 * @param string $description input description text.
274 * @param bool $escape escape HTML entities
208 * 275 *
209 * @return string HTML processed $description. 276 * @return string HTML processed $description.
210 */ 277 */
211function process_markdown($description) 278function process_markdown($description, $escape = true)
212{ 279{
213 $parsedown = new Parsedown(); 280 $parsedown = new Parsedown();
214 281
215 $processedDescription = $description; 282 $processedDescription = $description;
216 $processedDescription = reverse_text2clickable($processedDescription);
217 $processedDescription = reverse_nl2br($processedDescription); 283 $processedDescription = reverse_nl2br($processedDescription);
218 $processedDescription = reverse_space2nbsp($processedDescription); 284 $processedDescription = reverse_space2nbsp($processedDescription);
285 $processedDescription = reverse_text2clickable($processedDescription);
219 $processedDescription = unescape($processedDescription); 286 $processedDescription = unescape($processedDescription);
220 $processedDescription = $parsedown 287 $processedDescription = $parsedown
221 ->setMarkupEscaped(false) 288 ->setMarkupEscaped($escape)
222 ->setBreaksEnabled(true) 289 ->setBreaksEnabled(true)
223 ->text($processedDescription); 290 ->text($processedDescription);
224 $processedDescription = sanitize_html($processedDescription); 291 $processedDescription = sanitize_html($processedDescription);
diff --git a/plugins/piwik/piwik.meta b/plugins/piwik/piwik.meta
new file mode 100644
index 00000000..20b9628e
--- /dev/null
+++ b/plugins/piwik/piwik.meta
@@ -0,0 +1,4 @@
1description="A plugin that adds Piwik tracking code to Shaarli pages."
2parameters="PIWIK_URL;PIWIK_SITEID"
3parameter.PIWIK_URL="Piwik URL"
4parameter.PIWIK_SITEID="Piwik site ID"
diff --git a/plugins/piwik/piwik.php b/plugins/piwik/piwik.php
new file mode 100644
index 00000000..7c44909c
--- /dev/null
+++ b/plugins/piwik/piwik.php
@@ -0,0 +1,71 @@
1<?php
2/**
3 * Piwik plugin.
4 * Adds tracking code on each page.
5 */
6
7/**
8 * Initialization function.
9 * It will be called when the plugin is loaded.
10 * This function can be used to return a list of initialization errors.
11 *
12 * @param $conf ConfigManager instance.
13 *
14 * @return array List of errors (optional).
15 */
16function piwik_init($conf)
17{
18 $piwikUrl = $conf->get('plugins.PIWIK_URL');
19 $piwikSiteid = $conf->get('plugins.PIWIK_SITEID');
20 if (empty($piwikUrl) || empty($piwikSiteid)) {
21 $error = 'Piwik plugin error: ' .
22 'Please define PIWIK_URL and PIWIK_SITEID in the plugin administration page.';
23 return array($error);
24 }
25}
26
27/**
28 * Hook render_footer.
29 * Executed on every page redering.
30 *
31 * Template placeholders:
32 * - text
33 * - endofpage
34 * - js_files
35 *
36 * Data:
37 * - _PAGE_: current page
38 * - _LOGGEDIN_: true/false
39 *
40 * @param array $data data passed to plugin
41 *
42 * @return array altered $data.
43 */
44function hook_piwik_render_footer($data, $conf)
45{
46 $piwikUrl = $conf->get('plugins.PIWIK_URL');
47 $piwikSiteid = $conf->get('plugins.PIWIK_SITEID');
48 if (empty($piwikUrl) || empty($piwikSiteid)) {
49 return $data;
50 }
51
52 // Free elements at the end of the page.
53 $data['endofpage'][] = '<!-- Piwik -->' .
54'<script type="text/javascript">' .
55' var _paq = _paq || [];' .
56' _paq.push([\'trackPageView\']);' .
57' _paq.push([\'enableLinkTracking\']);' .
58' (function() {' .
59' var u="//' . $piwikUrl . '/";' .
60' _paq.push([\'setTrackerUrl\', u+\'piwik.php\']);' .
61' _paq.push([\'setSiteId\', \'' . $piwikSiteid . '\']);' .
62' var d=document, g=d.createElement(\'script\'), s=d.getElementsByTagName(\'script\')[0];' .
63' g.type=\'text/javascript\'; g.async=true; g.defer=true; g.src=u+\'piwik.js\'; s.parentNode.insertBefore(g,s);' .
64' })();' .
65'</script>' .
66'<noscript><p><img src="//' . $piwikUrl . '/piwik.php?idsite=' . $piwikSiteid . '" style="border:0;" alt="" /></p></noscript>' .
67'<!-- End Piwik Code -->';
68
69 return $data;
70}
71
diff --git a/plugins/playvideos/playvideos.html b/plugins/playvideos/playvideos.html
deleted file mode 100644
index fda3d54f..00000000
--- a/plugins/playvideos/playvideos.html
+++ /dev/null
@@ -1 +0,0 @@
1<a href="#" id="playvideos">► Play Videos</a> \ No newline at end of file
diff --git a/plugins/playvideos/playvideos.php b/plugins/playvideos/playvideos.php
index 0a80aa58..64484504 100644
--- a/plugins/playvideos/playvideos.php
+++ b/plugins/playvideos/playvideos.php
@@ -16,7 +16,15 @@
16function hook_playvideos_render_header($data) 16function hook_playvideos_render_header($data)
17{ 17{
18 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) { 18 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
19 $data['buttons_toolbar'][] = file_get_contents(PluginManager::$PLUGINS_PATH . '/playvideos/playvideos.html'); 19 $playvideo = array(
20 'attr' => array(
21 'href' => '#',
22 'title' => 'Video player',
23 'id' => 'playvideos',
24 ),
25 'html' => '► Play Videos'
26 );
27 $data['buttons_toolbar'][] = $playvideo;
20 } 28 }
21 29
22 return $data; 30 return $data;
@@ -37,4 +45,4 @@ function hook_playvideos_render_footer($data)
37 } 45 }
38 46
39 return $data; 47 return $data;
40} \ No newline at end of file 48}
diff --git a/plugins/qrcode/qrcode.html b/plugins/qrcode/qrcode.html
index cebc5644..dc214ed1 100644
--- a/plugins/qrcode/qrcode.html
+++ b/plugins/qrcode/qrcode.html
@@ -1,5 +1,5 @@
1<div class="linkqrcode"> 1<div class="linkqrcode">
2 <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s"> 2 <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
3 <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code"> 3 <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code" alt="QRCode">
4 </a> 4 </a>
5</div> 5</div>
diff --git a/plugins/readityourself/config.php.dist b/plugins/readityourself/config.php.dist
deleted file mode 100644
index d6b5cb85..00000000
--- a/plugins/readityourself/config.php.dist
+++ /dev/null
@@ -1,3 +0,0 @@
1<?php
2
3$GLOBALS['plugins']['READITYOUSELF_URL'] = 'http://someurl.com'; \ No newline at end of file
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
index e8c5f784..5e200715 100644
--- a/plugins/readityourself/readityourself.html
+++ b/plugins/readityourself/readityourself.html
@@ -1 +1 @@
<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" /></a></span> <span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" alt="readityourself" /></a></span>
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php
index c8df4c4f..961c5bda 100644
--- a/plugins/readityourself/readityourself.php
+++ b/plugins/readityourself/readityourself.php
@@ -8,34 +8,42 @@
8// it seems kinda dead. 8// it seems kinda dead.
9// Not tested. 9// Not tested.
10 10
11// don't raise unnecessary warnings 11/**
12if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) { 12 * Init function, return an error if the server is not set.
13 include PluginManager::$PLUGINS_PATH . '/readityourself/config.php'; 13 *
14} 14 * @param $conf ConfigManager instance.
15 15 *
16if (empty($GLOBALS['plugins']['READITYOUSELF_URL'])) { 16 * @return array Eventual error.
17 $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '. 17 */
18 'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '. 18function readityourself_init($conf)
19 'in "plugins/readityourself/config.php" or in your Shaarli config.php file.'; 19{
20 $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
21 if (empty($riyUrl)) {
22 $error = 'Readityourself plugin error: '.
23 'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
24 return array($error);
25 }
20} 26}
21 27
22/** 28/**
23 * Add readityourself icon to link_plugin when rendering linklist. 29 * Add readityourself icon to link_plugin when rendering linklist.
24 * 30 *
25 * @param mixed $data - linklist data. 31 * @param mixed $data Linklist data.
32 * @param ConfigManager $conf Configuration Manager instance.
26 * 33 *
27 * @return mixed - linklist data with readityourself plugin. 34 * @return mixed - linklist data with readityourself plugin.
28 */ 35 */
29function hook_readityourself_render_linklist($data) 36function hook_readityourself_render_linklist($data, $conf)
30{ 37{
31 if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) { 38 $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
39 if (empty($riyUrl)) {
32 return $data; 40 return $data;
33 } 41 }
34 42
35 $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html'); 43 $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
36 44
37 foreach ($data['links'] as &$value) { 45 foreach ($data['links'] as &$value) {
38 $readityourself = sprintf($readityourself_html, $GLOBALS['plugins']['READITYOUSELF_URL'], $value['url'], PluginManager::$PLUGINS_PATH); 46 $readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
39 $value['link_plugin'][] = $readityourself; 47 $value['link_plugin'][] = $readityourself;
40 } 48 }
41 49
diff --git a/plugins/wallabag/README.md b/plugins/wallabag/README.md
index 5bc35be1..ea21a519 100644
--- a/plugins/wallabag/README.md
+++ b/plugins/wallabag/README.md
@@ -4,39 +4,34 @@ For each link in your Shaarli, adds a button to save the target page in your [wa
4 4
5### Installation 5### Installation
6 6
7Clone this repository inside your `tpl/plugins/` directory, or download the archive and unpack it there. 7Clone this repository inside your `tpl/plugins/` directory, or download the archive and unpack it there.
8The directory structure should look like: 8The directory structure should look like:
9 9
10``` 10```bash
11└── tpl 11└── tpl
12 └── plugins 12 └── plugins
13    └── wallabag 13 └── wallabag
14    ├── README.md 14 ├── README.md
15 ├── config.php.dist 15 ├── wallabag.html
16    ├── wallabag.html 16 ├── wallabag.meta
17    ├── wallabag.php 17 ├── wallabag.php
18    └── wallabag.png 18 ├── wallabag.php
19 └── WallabagInstance.php
19``` 20```
20 21
21To enable the plugin, add `'wallabag'` to your list of enabled plugins in `data/options.php` (`PLUGINS` array). 22To enable the plugin, you can either:
22This should look like:
23 23
24``` 24 * enable it in the plugins administration page (`?do=pluginadmin`).
25$GLOBALS['config']['PLUGINS'] = array('qrcode', 'any_other_plugin', 'wallabag') 25 * add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section).
26```
27 26
28### Configuration 27### Configuration
29 28
30Copy `config.php.dist` into `config.php` and setup your instance. 29Go to the plugin administration page, and edit the following settings (with the plugin enabled).
31 30
32*Wallabag instance URL* 31**WALLABAG_URL**: *Wallabag instance URL*
33``` 32Example value: `http://v2.wallabag.org`
34$GLOBALS['config']['WALLABAG_URL'] = 'http://v2.wallabag.org' ;
35```
36 33
37*Wallabag version*: either `1` (for 1.x) or `2` (for 2.x) 34**WALLABAG_VERSION**: *Wallabag version*
38``` 35Value: either `1` (for 1.x) or `2` (for 2.x)
39$GLOBALS['config']['WALLABAG_VERSION'] = 2;
40```
41 36
42> Note: these settings can also be set in `data/config.php`. \ No newline at end of file 37> Note: these settings can also be set in `data/config.json.php`, in the plugins section.
diff --git a/plugins/wallabag/config.php.dist b/plugins/wallabag/config.php.dist
deleted file mode 100644
index a602708f..00000000
--- a/plugins/wallabag/config.php.dist
+++ /dev/null
@@ -1,4 +0,0 @@
1<?php
2
3$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org';
4$GLOBALS['plugins']['WALLABAG_VERSION'] = 1; \ No newline at end of file
diff --git a/plugins/wallabag/wallabag.html b/plugins/wallabag/wallabag.html
index c7b1d044..e861536d 100644
--- a/plugins/wallabag/wallabag.html
+++ b/plugins/wallabag/wallabag.html
@@ -1 +1 @@
<span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" /></a></span> <span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" alt="wallabag" /></a></span>
diff --git a/plugins/wallabag/wallabag.meta b/plugins/wallabag/wallabag.meta
index 26e1ea63..9c93f81c 100644
--- a/plugins/wallabag/wallabag.meta
+++ b/plugins/wallabag/wallabag.meta
@@ -1,2 +1,4 @@
1description="For each link, add a Wallabag icon to save it in your instance." 1description="For each link, add a Wallabag icon to save it in your instance."
2parameters="WALLABAG_URL;WALLABAG_VERSION" \ No newline at end of file 2parameters="WALLABAG_URL;WALLABAG_VERSION"
3parameter.WALLABAG_URL="Wallabag API URL"
4parameter.WALLABAG_VERSION="Wallabag API version (1 or 2)" \ No newline at end of file
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php
index 0d6fc66d..641e4cc2 100644
--- a/plugins/wallabag/wallabag.php
+++ b/plugins/wallabag/wallabag.php
@@ -6,34 +6,40 @@
6 6
7require_once 'WallabagInstance.php'; 7require_once 'WallabagInstance.php';
8 8
9// don't raise unnecessary warnings 9/**
10if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) { 10 * Init function, return an error if the server is not set.
11 include PluginManager::$PLUGINS_PATH . '/wallabag/config.php'; 11 *
12} 12 * @param $conf ConfigManager instance.
13 13 *
14if (empty($GLOBALS['plugins']['WALLABAG_URL'])) { 14 * @return array Eventual error.
15 $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '. 15 */
16 'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '. 16function wallabag_init($conf)
17 'in "plugins/wallabag/config.php" or in your Shaarli config.php file.'; 17{
18 $wallabagUrl = $conf->get('plugins.WALLABAG_URL');
19 if (empty($wallabagUrl)) {
20 $error = 'Wallabag plugin error: '.
21 'Please define the "WALLABAG_URL" setting in the plugin administration page.';
22 return array($error);
23 }
18} 24}
19 25
20/** 26/**
21 * Add wallabag icon to link_plugin when rendering linklist. 27 * Add wallabag icon to link_plugin when rendering linklist.
22 * 28 *
23 * @param mixed $data - linklist data. 29 * @param mixed $data Linklist data.
30 * @param ConfigManager $conf Configuration Manager instance.
24 * 31 *
25 * @return mixed - linklist data with wallabag plugin. 32 * @return mixed - linklist data with wallabag plugin.
26 */ 33 */
27function hook_wallabag_render_linklist($data) 34function hook_wallabag_render_linklist($data, $conf)
28{ 35{
29 if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) { 36 $wallabagUrl = $conf->get('plugins.WALLABAG_URL');
37 if (empty($wallabagUrl)) {
30 return $data; 38 return $data;
31 } 39 }
32 40
33 $version = isset($GLOBALS['plugins']['WALLABAG_VERSION']) 41 $version = $conf->get('plugins.WALLABAG_VERSION');
34 ? $GLOBALS['plugins']['WALLABAG_VERSION'] 42 $wallabagInstance = new WallabagInstance($wallabagUrl, $version);
35 : '';
36 $wallabagInstance = new WallabagInstance($GLOBALS['plugins']['WALLABAG_URL'], $version);
37 43
38 $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); 44 $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');
39 45
diff --git a/shaarli_version.php b/shaarli_version.php
index 184e5220..1c1c5d74 100644
--- a/shaarli_version.php
+++ b/shaarli_version.php
@@ -1 +1 @@
<?php /* 0.7.0 */ <?php /* 0.8.4 */ ?>
diff --git a/tests/.htaccess b/tests/.htaccess
index b584d98c..f601c1ee 100644
--- a/tests/.htaccess
+++ b/tests/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php
index 6064357d..861b8d4e 100644
--- a/tests/ApplicationUtilsTest.php
+++ b/tests/ApplicationUtilsTest.php
@@ -3,6 +3,7 @@
3 * ApplicationUtils' tests 3 * ApplicationUtils' tests
4 */ 4 */
5 5
6require_once 'application/config/ConfigManager.php';
6require_once 'application/ApplicationUtils.php'; 7require_once 'application/ApplicationUtils.php';
7 8
8/** 9/**
@@ -59,7 +60,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
59 $testTimeout 60 $testTimeout
60 ) 61 )
61 ); 62 );
62 $this->assertRegexp( 63 $this->assertRegExp(
63 self::$versionPattern, 64 self::$versionPattern,
64 ApplicationUtils::getLatestGitVersionCode( 65 ApplicationUtils::getLatestGitVersionCode(
65 'https://raw.githubusercontent.com/shaarli/Shaarli/' 66 'https://raw.githubusercontent.com/shaarli/Shaarli/'
@@ -74,9 +75,12 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
74 */ 75 */
75 public function testGetLatestGitVersionCodeInvalidUrl() 76 public function testGetLatestGitVersionCodeInvalidUrl()
76 { 77 {
78 $oldlog = ini_get('error_log');
79 ini_set('error_log', '/dev/null');
77 $this->assertFalse( 80 $this->assertFalse(
78 ApplicationUtils::getLatestGitVersionCode('htttp://null.io', 1) 81 ApplicationUtils::getLatestGitVersionCode('htttp://null.io', 1)
79 ); 82 );
83 ini_set('error_log', $oldlog);
80 } 84 }
81 85
82 /** 86 /**
@@ -275,21 +279,21 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
275 */ 279 */
276 public function testCheckCurrentResourcePermissions() 280 public function testCheckCurrentResourcePermissions()
277 { 281 {
278 $config = array( 282 $conf = new ConfigManager('');
279 'CACHEDIR' => 'cache', 283 $conf->set('resource.thumbnails_cache', 'cache');
280 'CONFIG_FILE' => 'data/config.php', 284 $conf->set('resource.config', 'data/config.php');
281 'DATADIR' => 'data', 285 $conf->set('resource.data_dir', 'data');
282 'DATASTORE' => 'data/datastore.php', 286 $conf->set('resource.datastore', 'data/datastore.php');
283 'IPBANS_FILENAME' => 'data/ipbans.php', 287 $conf->set('resource.ban_file', 'data/ipbans.php');
284 'LOG_FILE' => 'data/log.txt', 288 $conf->set('resource.log', 'data/log.txt');
285 'PAGECACHE' => 'pagecache', 289 $conf->set('resource.page_cache', 'pagecache');
286 'RAINTPL_TMP' => 'tmp', 290 $conf->set('resource.raintpl_tmp', 'tmp');
287 'RAINTPL_TPL' => 'tpl', 291 $conf->set('resource.raintpl_tpl', 'tpl');
288 'UPDATECHECK_FILENAME' => 'data/lastupdatecheck.txt' 292 $conf->set('resource.update_check', 'data/lastupdatecheck.txt');
289 ); 293
290 $this->assertEquals( 294 $this->assertEquals(
291 array(), 295 array(),
292 ApplicationUtils::checkResourcePermissions($config) 296 ApplicationUtils::checkResourcePermissions($conf)
293 ); 297 );
294 } 298 }
295 299
@@ -298,18 +302,17 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
298 */ 302 */
299 public function testCheckCurrentResourcePermissionsErrors() 303 public function testCheckCurrentResourcePermissionsErrors()
300 { 304 {
301 $config = array( 305 $conf = new ConfigManager('');
302 'CACHEDIR' => 'null/cache', 306 $conf->set('resource.thumbnails_cache', 'null/cache');
303 'CONFIG_FILE' => 'null/data/config.php', 307 $conf->set('resource.config', 'null/data/config.php');
304 'DATADIR' => 'null/data', 308 $conf->set('resource.data_dir', 'null/data');
305 'DATASTORE' => 'null/data/store.php', 309 $conf->set('resource.datastore', 'null/data/store.php');
306 'IPBANS_FILENAME' => 'null/data/ipbans.php', 310 $conf->set('resource.ban_file', 'null/data/ipbans.php');
307 'LOG_FILE' => 'null/data/log.txt', 311 $conf->set('resource.log', 'null/data/log.txt');
308 'PAGECACHE' => 'null/pagecache', 312 $conf->set('resource.page_cache', 'null/pagecache');
309 'RAINTPL_TMP' => 'null/tmp', 313 $conf->set('resource.raintpl_tmp', 'null/tmp');
310 'RAINTPL_TPL' => 'null/tpl', 314 $conf->set('resource.raintpl_tpl', 'null/tpl');
311 'UPDATECHECK_FILENAME' => 'null/data/lastupdatecheck.txt' 315 $conf->set('resource.update_check', 'null/data/lastupdatecheck.txt');
312 );
313 $this->assertEquals( 316 $this->assertEquals(
314 array( 317 array(
315 '"null/tpl" directory is not readable', 318 '"null/tpl" directory is not readable',
@@ -322,7 +325,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
322 '"null/tmp" directory is not readable', 325 '"null/tmp" directory is not readable',
323 '"null/tmp" directory is not writable' 326 '"null/tmp" directory is not writable'
324 ), 327 ),
325 ApplicationUtils::checkResourcePermissions($config) 328 ApplicationUtils::checkResourcePermissions($conf)
326 ); 329 );
327 } 330 }
328} 331}
diff --git a/tests/CacheTest.php b/tests/CacheTest.php
index 26c43225..992e26a5 100644
--- a/tests/CacheTest.php
+++ b/tests/CacheTest.php
@@ -64,10 +64,13 @@ class CacheTest extends PHPUnit_Framework_TestCase
64 */ 64 */
65 public function testPurgeCachedPagesMissingDir() 65 public function testPurgeCachedPagesMissingDir()
66 { 66 {
67 $oldlog = ini_get('error_log');
68 ini_set('error_log', '/dev/null');
67 $this->assertEquals( 69 $this->assertEquals(
68 'Cannot purge sandbox/dummycache_missing: no directory', 70 'Cannot purge sandbox/dummycache_missing: no directory',
69 purgeCachedPages(self::$testCacheDir.'_missing') 71 purgeCachedPages(self::$testCacheDir.'_missing')
70 ); 72 );
73 ini_set('error_log', $oldlog);
71 } 74 }
72 75
73 /** 76 /**
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
deleted file mode 100644
index 7200aae6..00000000
--- a/tests/ConfigTest.php
+++ /dev/null
@@ -1,244 +0,0 @@
1<?php
2/**
3 * Config' tests
4 */
5
6require_once 'application/Config.php';
7
8/**
9 * Unitary tests for Shaarli config related functions
10 */
11class ConfigTest extends PHPUnit_Framework_TestCase
12{
13 // Configuration input set.
14 private static $configFields;
15
16 /**
17 * Executed before each test.
18 */
19 public function setUp()
20 {
21 self::$configFields = array(
22 'login' => 'login',
23 'hash' => 'hash',
24 'salt' => 'salt',
25 'timezone' => 'Europe/Paris',
26 'title' => 'title',
27 'titleLink' => 'titleLink',
28 'redirector' => '',
29 'disablesessionprotection' => false,
30 'privateLinkByDefault' => false,
31 'config' => array(
32 'CONFIG_FILE' => 'tests/config.php',
33 'DATADIR' => 'tests',
34 'config1' => 'config1data',
35 'config2' => 'config2data',
36 )
37 );
38 }
39
40 /**
41 * Executed after each test.
42 *
43 * @return void
44 */
45 public function tearDown()
46 {
47 if (is_file(self::$configFields['config']['CONFIG_FILE'])) {
48 unlink(self::$configFields['config']['CONFIG_FILE']);
49 }
50 }
51
52 /**
53 * Test writeConfig function, valid use case, while being logged in.
54 */
55 public function testWriteConfig()
56 {
57 writeConfig(self::$configFields, true);
58
59 include self::$configFields['config']['CONFIG_FILE'];
60 $this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
61 $this->assertEquals(self::$configFields['hash'], $GLOBALS['hash']);
62 $this->assertEquals(self::$configFields['salt'], $GLOBALS['salt']);
63 $this->assertEquals(self::$configFields['timezone'], $GLOBALS['timezone']);
64 $this->assertEquals(self::$configFields['title'], $GLOBALS['title']);
65 $this->assertEquals(self::$configFields['titleLink'], $GLOBALS['titleLink']);
66 $this->assertEquals(self::$configFields['redirector'], $GLOBALS['redirector']);
67 $this->assertEquals(self::$configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
68 $this->assertEquals(self::$configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
69 $this->assertEquals(self::$configFields['config']['config1'], $GLOBALS['config']['config1']);
70 $this->assertEquals(self::$configFields['config']['config2'], $GLOBALS['config']['config2']);
71 }
72
73 /**
74 * Test writeConfig option while logged in:
75 * 1. init fields.
76 * 2. update fields, add new sub config, add new root config.
77 * 3. rewrite config.
78 * 4. check result.
79 */
80 public function testWriteConfigFieldUpdate()
81 {
82 writeConfig(self::$configFields, true);
83 self::$configFields['title'] = 'ok';
84 self::$configFields['config']['config1'] = 'ok';
85 self::$configFields['config']['config_new'] = 'ok';
86 self::$configFields['new'] = 'should not be saved';
87 writeConfig(self::$configFields, true);
88
89 include self::$configFields['config']['CONFIG_FILE'];
90 $this->assertEquals('ok', $GLOBALS['title']);
91 $this->assertEquals('ok', $GLOBALS['config']['config1']);
92 $this->assertEquals('ok', $GLOBALS['config']['config_new']);
93 $this->assertFalse(isset($GLOBALS['new']));
94 }
95
96 /**
97 * Test writeConfig function with an empty array.
98 *
99 * @expectedException MissingFieldConfigException
100 */
101 public function testWriteConfigEmpty()
102 {
103 writeConfig(array(), true);
104 }
105
106 /**
107 * Test writeConfig function with a missing mandatory field.
108 *
109 * @expectedException MissingFieldConfigException
110 */
111 public function testWriteConfigMissingField()
112 {
113 unset(self::$configFields['login']);
114 writeConfig(self::$configFields, true);
115 }
116
117 /**
118 * Test writeConfig function while being logged out, and there is no config file existing.
119 */
120 public function testWriteConfigLoggedOutNoFile()
121 {
122 writeConfig(self::$configFields, false);
123 }
124
125 /**
126 * Test writeConfig function while being logged out, and a config file already exists.
127 *
128 * @expectedException UnauthorizedConfigException
129 */
130 public function testWriteConfigLoggedOutWithFile()
131 {
132 file_put_contents(self::$configFields['config']['CONFIG_FILE'], '');
133 writeConfig(self::$configFields, false);
134 }
135
136 /**
137 * Test save_plugin_config with valid data.
138 *
139 * @throws PluginConfigOrderException
140 */
141 public function testSavePluginConfigValid()
142 {
143 $data = array(
144 'order_plugin1' => 2, // no plugin related
145 'plugin2' => 0, // new - at the end
146 'plugin3' => 0, // 2nd
147 'order_plugin3' => 8,
148 'plugin4' => 0, // 1st
149 'order_plugin4' => 5,
150 );
151
152 $expected = array(
153 'plugin3',
154 'plugin4',
155 'plugin2',
156 );
157
158 $out = save_plugin_config($data);
159 $this->assertEquals($expected, $out);
160 }
161
162 /**
163 * Test save_plugin_config with invalid data.
164 *
165 * @expectedException PluginConfigOrderException
166 */
167 public function testSavePluginConfigInvalid()
168 {
169 $data = array(
170 'plugin2' => 0,
171 'plugin3' => 0,
172 'order_plugin3' => 0,
173 'plugin4' => 0,
174 'order_plugin4' => 0,
175 );
176
177 save_plugin_config($data);
178 }
179
180 /**
181 * Test save_plugin_config without data.
182 */
183 public function testSavePluginConfigEmpty()
184 {
185 $this->assertEquals(array(), save_plugin_config(array()));
186 }
187
188 /**
189 * Test validate_plugin_order with valid data.
190 */
191 public function testValidatePluginOrderValid()
192 {
193 $data = array(
194 'order_plugin1' => 2,
195 'plugin2' => 0,
196 'plugin3' => 0,
197 'order_plugin3' => 1,
198 'plugin4' => 0,
199 'order_plugin4' => 5,
200 );
201
202 $this->assertTrue(validate_plugin_order($data));
203 }
204
205 /**
206 * Test validate_plugin_order with invalid data.
207 */
208 public function testValidatePluginOrderInvalid()
209 {
210 $data = array(
211 'order_plugin1' => 2,
212 'order_plugin3' => 1,
213 'order_plugin4' => 1,
214 );
215
216 $this->assertFalse(validate_plugin_order($data));
217 }
218
219 /**
220 * Test load_plugin_parameter_values.
221 */
222 public function testLoadPluginParameterValues()
223 {
224 $plugins = array(
225 'plugin_name' => array(
226 'parameters' => array(
227 'param1' => true,
228 'param2' => false,
229 'param3' => '',
230 )
231 )
232 );
233
234 $parameters = array(
235 'param1' => 'value1',
236 'param2' => 'value2',
237 );
238
239 $result = load_plugin_parameter_values($plugins, $parameters);
240 $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
241 $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
242 $this->assertEquals('', $result['plugin_name']['parameters']['param3']);
243 }
244}
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index 069b1581..06a44506 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -76,7 +76,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
76 // Test headers (RSS) 76 // Test headers (RSS)
77 $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); 77 $this->assertEquals(self::$RSS_LANGUAGE, $data['language']);
78 $this->assertEmpty($data['pubsubhub_url']); 78 $this->assertEmpty($data['pubsubhub_url']);
79 $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']); 79 $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']);
80 $this->assertEquals(true, $data['show_dates']); 80 $this->assertEquals(true, $data['show_dates']);
81 $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); 81 $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']);
82 $this->assertEquals('http://host.tld/', $data['index_url']); 82 $this->assertEquals('http://host.tld/', $data['index_url']);
@@ -84,23 +84,30 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 84 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
85 85
86 // Test first link (note link) 86 // Test first link (note link)
87 $link = array_shift($data['links']); 87 $link = reset($data['links']);
88 $this->assertEquals('20150310_114651', $link['linkdate']); 88 $this->assertEquals(41, $link['id']);
89 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 90 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
90 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 91 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
91 $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']); 92 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
93 $pub = DateTime::createFromFormat(DateTime::RSS, $link['pub_iso_date']);
94 $up = DateTime::createFromFormat(DateTime::ATOM, $link['up_iso_date']);
95 $this->assertEquals($pub, $up);
92 $this->assertContains('Stallman has a beard', $link['description']); 96 $this->assertContains('Stallman has a beard', $link['description']);
93 $this->assertContains('Permalink', $link['description']); 97 $this->assertContains('Permalink', $link['description']);
94 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 98 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
95 $this->assertEquals(1, count($link['taglist'])); 99 $this->assertEquals(1, count($link['taglist']));
96 $this->assertEquals('stuff', $link['taglist'][0]); 100 $this->assertEquals('sTuff', $link['taglist'][0]);
97 101
98 // Test URL with external link. 102 // Test URL with external link.
99 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']); 103 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
100 104
101 // Test multitags. 105 // Test multitags.
102 $this->assertEquals(5, count($data['links']['20141125_084734']['taglist'])); 106 $this->assertEquals(5, count($data['links'][6]['taglist']));
103 $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]); 107 $this->assertEquals('css', $data['links'][6]['taglist'][0]);
108
109 // Test update date
110 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
104 } 111 }
105 112
106 /** 113 /**
@@ -112,8 +119,10 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
112 $feedBuilder->setLocale(self::$LOCALE); 119 $feedBuilder->setLocale(self::$LOCALE);
113 $data = $feedBuilder->buildData(); 120 $data = $feedBuilder->buildData();
114 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 121 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
115 $link = array_shift($data['links']); 122 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
116 $this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']); 123 $link = reset($data['links']);
124 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
125 $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
117 } 126 }
118 127
119 /** 128 /**
@@ -130,7 +139,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
130 $data = $feedBuilder->buildData(); 139 $data = $feedBuilder->buildData();
131 $this->assertEquals(1, count($data['links'])); 140 $this->assertEquals(1, count($data['links']));
132 $link = array_shift($data['links']); 141 $link = array_shift($data['links']);
133 $this->assertEquals('20150310_114651', $link['linkdate']); 142 $this->assertEquals(41, $link['id']);
143 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
134 } 144 }
135 145
136 /** 146 /**
@@ -146,7 +156,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
146 $data = $feedBuilder->buildData(); 156 $data = $feedBuilder->buildData();
147 $this->assertEquals(1, count($data['links'])); 157 $this->assertEquals(1, count($data['links']));
148 $link = array_shift($data['links']); 158 $link = array_shift($data['links']);
149 $this->assertEquals('20150310_114651', $link['linkdate']); 159 $this->assertEquals(41, $link['id']);
160 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
150 } 161 }
151 162
152 /** 163 /**
@@ -162,15 +173,17 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
162 $this->assertTrue($data['usepermalinks']); 173 $this->assertTrue($data['usepermalinks']);
163 // First link is a permalink 174 // First link is a permalink
164 $link = array_shift($data['links']); 175 $link = array_shift($data['links']);
165 $this->assertEquals('20150310_114651', $link['linkdate']); 176 $this->assertEquals(41, $link['id']);
177 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
166 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 178 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
167 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 179 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
168 $this->assertContains('Direct link', $link['description']); 180 $this->assertContains('Direct link', $link['description']);
169 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 181 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
170 // Second link is a direct link 182 // Second link is a direct link
171 $link = array_shift($data['links']); 183 $link = array_shift($data['links']);
172 $this->assertEquals('20150310_114633', $link['linkdate']); 184 $this->assertEquals(8, $link['id']);
173 $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']); 185 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
186 $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
174 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']); 187 $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
175 $this->assertContains('Direct link', $link['description']); 188 $this->assertContains('Direct link', $link['description']);
176 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); 189 $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
@@ -209,4 +222,38 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
209 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 222 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
210 $this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']); 223 $this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']);
211 } 224 }
225
226 /**
227 * Test buildData when Shaarli is served from a subdirectory
228 */
229 public function testBuildDataServerSubdir()
230 {
231 $serverInfo = array(
232 'HTTPS' => 'Off',
233 'SERVER_NAME' => 'host.tld',
234 'SERVER_PORT' => '8080',
235 'SCRIPT_NAME' => '/~user/shaarli/index.php',
236 'REQUEST_URI' => '/~user/shaarli/index.php?do=feed',
237 );
238 $feedBuilder = new FeedBuilder(
239 self::$linkDB,
240 FeedBuilder::$FEED_ATOM,
241 $serverInfo,
242 null,
243 false
244 );
245 $feedBuilder->setLocale(self::$LOCALE);
246 $data = $feedBuilder->buildData();
247
248 $this->assertEquals(
249 'http://host.tld:8080/~user/shaarli/index.php?do=feed',
250 $data['self_link']
251 );
252
253 // Test first link (note link)
254 $link = array_shift($data['links']);
255 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['guid']);
256 $this->assertEquals('http://host.tld:8080/~user/shaarli/?WDWyig', $link['url']);
257 $this->assertContains('http://host.tld:8080/~user/shaarli/?addtag=hashtag', $link['description']);
258 }
212} 259}
diff --git a/tests/HttpUtils/GetIpAdressFromProxyTest.php b/tests/HttpUtils/GetIpAdressFromProxyTest.php
new file mode 100644
index 00000000..6a74a45a
--- /dev/null
+++ b/tests/HttpUtils/GetIpAdressFromProxyTest.php
@@ -0,0 +1,58 @@
1<?php
2
3require_once 'application/HttpUtils.php';
4
5/**
6 * Unitary tests for getIpAddressFromProxy()
7 */
8class GetIpAdressFromProxyTest extends PHPUnit_Framework_TestCase {
9
10 /**
11 * Test without proxy
12 */
13 public function testWithoutProxy()
14 {
15 $this->assertFalse(getIpAddressFromProxy(array(), array()));
16 }
17
18 /**
19 * Test with a single IP in proxy header.
20 */
21 public function testWithOneForwardedIp()
22 {
23 $ip = '1.1.1.1';
24 $server = array('HTTP_X_FORWARDED_FOR' => $ip);
25 $this->assertEquals($ip, getIpAddressFromProxy($server, array()));
26 }
27
28 /**
29 * Test with a multiple IPs in proxy header.
30 */
31 public function testWithMultipleForwardedIp()
32 {
33 $ip = '1.1.1.1';
34 $ip2 = '2.2.2.2';
35
36 $server = array('HTTP_X_FORWARDED_FOR' => $ip .','. $ip2);
37 $this->assertEquals($ip2, getIpAddressFromProxy($server, array()));
38
39 $server = array('HTTP_X_FORWARDED_FOR' => $ip .' , '. $ip2);
40 $this->assertEquals($ip2, getIpAddressFromProxy($server, array()));
41 }
42
43 /**
44 * Test with a trusted IP address.
45 */
46 public function testWithTrustedIp()
47 {
48 $ip = '1.1.1.1';
49 $ip2 = '2.2.2.2';
50
51 $server = array('HTTP_X_FORWARDED_FOR' => $ip);
52 $this->assertFalse(getIpAddressFromProxy($server, array($ip)));
53
54 $server = array('HTTP_X_FORWARDED_FOR' => $ip .','. $ip2);
55 $this->assertEquals($ip2, getIpAddressFromProxy($server, array($ip)));
56 $this->assertFalse(getIpAddressFromProxy($server, array($ip, $ip2)));
57 }
58}
diff --git a/tests/LanguagesTest.php b/tests/LanguagesTest.php
new file mode 100644
index 00000000..79c136c8
--- /dev/null
+++ b/tests/LanguagesTest.php
@@ -0,0 +1,41 @@
1<?php
2
3require_once 'application/Languages.php';
4
5/**
6 * Class LanguagesTest.
7 */
8class LanguagesTest extends PHPUnit_Framework_TestCase
9{
10 /**
11 * Test t() with a simple non identified value.
12 */
13 public function testTranslateSingleNotID()
14 {
15 $text = 'abcdé 564 fgK';
16 $this->assertEquals($text, t($text));
17 }
18
19 /**
20 * Test t() with a non identified plural form.
21 */
22 public function testTranslatePluralNotID()
23 {
24 $text = '%s sandwich';
25 $nText = '%s sandwiches';
26 $this->assertEquals('0 sandwich', t($text, $nText));
27 $this->assertEquals('1 sandwich', t($text, $nText, 1));
28 $this->assertEquals('2 sandwiches', t($text, $nText, 2));
29 }
30
31 /**
32 * Test t() with a non identified invalid plural form.
33 */
34 public function testTranslatePluralNotIDInvalid()
35 {
36 $text = 'sandwich';
37 $nText = 'sandwiches';
38 $this->assertEquals('sandwich', t($text, $nText, 1));
39 $this->assertEquals('sandwiches', t($text, $nText, 2));
40 }
41}
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index b055fe91..1f62a34a 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -101,7 +101,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
101 * Attempt to instantiate a LinkDB whereas the datastore is not writable 101 * Attempt to instantiate a LinkDB whereas the datastore is not writable
102 * 102 *
103 * @expectedException IOException 103 * @expectedException IOException
104 * @expectedExceptionMessageRegExp /Error accessing null/ 104 * @expectedExceptionMessageRegExp /Error accessing\nnull/
105 */ 105 */
106 public function testConstructDatastoreNotWriteable() 106 public function testConstructDatastoreNotWriteable()
107 { 107 {
@@ -117,7 +117,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
117 unlink(self::$testDatastore); 117 unlink(self::$testDatastore);
118 $this->assertFileNotExists(self::$testDatastore); 118 $this->assertFileNotExists(self::$testDatastore);
119 119
120 $checkDB = self::getMethod('_checkDB'); 120 $checkDB = self::getMethod('check');
121 $checkDB->invokeArgs($linkDB, array()); 121 $checkDB->invokeArgs($linkDB, array());
122 $this->assertFileExists(self::$testDatastore); 122 $this->assertFileExists(self::$testDatastore);
123 123
@@ -134,7 +134,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
134 $datastoreSize = filesize(self::$testDatastore); 134 $datastoreSize = filesize(self::$testDatastore);
135 $this->assertGreaterThan(0, $datastoreSize); 135 $this->assertGreaterThan(0, $datastoreSize);
136 136
137 $checkDB = self::getMethod('_checkDB'); 137 $checkDB = self::getMethod('check');
138 $checkDB->invokeArgs($linkDB, array()); 138 $checkDB->invokeArgs($linkDB, array());
139 139
140 // ensure the datastore is left unmodified 140 // ensure the datastore is left unmodified
@@ -180,21 +180,22 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
180 /** 180 /**
181 * Save the links to the DB 181 * Save the links to the DB
182 */ 182 */
183 public function testSaveDB() 183 public function testSave()
184 { 184 {
185 $testDB = new LinkDB(self::$testDatastore, true, false); 185 $testDB = new LinkDB(self::$testDatastore, true, false);
186 $dbSize = sizeof($testDB); 186 $dbSize = sizeof($testDB);
187 187
188 $link = array( 188 $link = array(
189 'id' => 42,
189 'title'=>'an additional link', 190 'title'=>'an additional link',
190 'url'=>'http://dum.my', 191 'url'=>'http://dum.my',
191 'description'=>'One more', 192 'description'=>'One more',
192 'private'=>0, 193 'private'=>0,
193 'linkdate'=>'20150518_190000', 194 'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
194 'tags'=>'unit test' 195 'tags'=>'unit test'
195 ); 196 );
196 $testDB[$link['linkdate']] = $link; 197 $testDB[$link['id']] = $link;
197 $testDB->savedb('tests'); 198 $testDB->save('tests');
198 199
199 $testDB = new LinkDB(self::$testDatastore, true, false); 200 $testDB = new LinkDB(self::$testDatastore, true, false);
200 $this->assertEquals($dbSize + 1, sizeof($testDB)); 201 $this->assertEquals($dbSize + 1, sizeof($testDB));
@@ -238,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
238 public function testDays() 239 public function testDays()
239 { 240 {
240 $this->assertEquals( 241 $this->assertEquals(
241 array('20121206', '20130614', '20150310'), 242 array('20100310', '20121206', '20130614', '20150310'),
242 self::$publicLinkDB->days() 243 self::$publicLinkDB->days()
243 ); 244 );
244 245
245 $this->assertEquals( 246 $this->assertEquals(
246 array('20121206', '20130614', '20141125', '20150310'), 247 array('20100310', '20121206', '20130614', '20141125', '20150310'),
247 self::$privateLinkDB->days() 248 self::$privateLinkDB->days()
248 ); 249 );
249 } 250 }
@@ -256,7 +257,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
256 $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/'); 257 $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
257 258
258 $this->assertNotEquals(false, $link); 259 $this->assertNotEquals(false, $link);
259 $this->assertEquals( 260 $this->assertContains(
260 'A free software media publishing platform', 261 'A free software media publishing platform',
261 $link['description'] 262 $link['description']
262 ); 263 );
@@ -290,7 +291,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
290 'stallman' => 1, 291 'stallman' => 1,
291 'free' => 1, 292 'free' => 1,
292 '-exclude' => 1, 293 '-exclude' => 1,
293 'stuff' => 2, 294 'hashtag' => 2,
295 // The DB contains a link with `sTuff` and another one with `stuff` tag.
296 // They need to be grouped with the first case found - order by date DESC: `sTuff`.
297 'sTuff' => 2,
298 'ut' => 1,
294 ), 299 ),
295 self::$publicLinkDB->allTags() 300 self::$publicLinkDB->allTags()
296 ); 301 );
@@ -310,9 +315,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
310 'w3c' => 1, 315 'w3c' => 1,
311 'css' => 1, 316 'css' => 1,
312 'Mercurial' => 1, 317 'Mercurial' => 1,
313 'stuff' => 2, 318 'sTuff' => 2,
314 '-exclude' => 1, 319 '-exclude' => 1,
315 '.hidden' => 1, 320 '.hidden' => 1,
321 'hashtag' => 2,
322 'tag1' => 1,
323 'tag2' => 1,
324 'tag3' => 1,
325 'tag4' => 1,
326 'ut' => 1,
316 ), 327 ),
317 self::$privateLinkDB->allTags() 328 self::$privateLinkDB->allTags()
318 ); 329 );
@@ -403,6 +414,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
403 1, 414 1,
404 count(self::$publicLinkDB->filterHash($request)) 415 count(self::$publicLinkDB->filterHash($request))
405 ); 416 );
417 $request = smallHash('20150310_114633' . 8);
418 $this->assertEquals(
419 1,
420 count(self::$publicLinkDB->filterHash($request))
421 );
406 } 422 }
407 423
408 /** 424 /**
@@ -425,4 +441,23 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
425 { 441 {
426 self::$publicLinkDB->filterHash(''); 442 self::$publicLinkDB->filterHash('');
427 } 443 }
444
445 /**
446 * Test reorder with asc/desc parameter.
447 */
448 public function testReorderLinksDesc()
449 {
450 self::$privateLinkDB->reorder('ASC');
451 $linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
452 $cpt = 0;
453 foreach (self::$privateLinkDB as $key => $value) {
454 $this->assertEquals($linkIds[$cpt++], $key);
455 }
456 self::$privateLinkDB->reorder('DESC');
457 $linkIds = array_reverse($linkIds);
458 $cpt = 0;
459 foreach (self::$privateLinkDB as $key => $value) {
460 $this->assertEquals($linkIds[$cpt++], $key);
461 }
462 }
428} 463}
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 1620bb78..21d680a5 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -159,7 +159,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
159 159
160 $this->assertEquals( 160 $this->assertEquals(
161 'MediaGoblin', 161 'MediaGoblin',
162 $links['20130614_184135']['title'] 162 $links[7]['title']
163 ); 163 );
164 } 164 }
165 165
@@ -286,7 +286,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
286 ); 286 );
287 287
288 $this->assertEquals( 288 $this->assertEquals(
289 6, 289 7,
290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution')) 290 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
291 ); 291 );
292 } 292 }
@@ -346,7 +346,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
346 ); 346 );
347 347
348 $this->assertEquals( 348 $this->assertEquals(
349 6, 349 7,
350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free')) 350 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
351 ); 351 );
352 } 352 }
@@ -387,4 +387,30 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
387 )) 387 ))
388 ); 388 );
389 } 389 }
390
391 /**
392 * Filter links by #hashtag.
393 */
394 public function testFilterByHashtag()
395 {
396 $hashtag = 'hashtag';
397 $this->assertEquals(
398 3,
399 count(self::$linkFilter->filter(
400 LinkFilter::$FILTER_TAG,
401 $hashtag
402 ))
403 );
404
405 $hashtag = 'private';
406 $this->assertEquals(
407 1,
408 count(self::$linkFilter->filter(
409 LinkFilter::$FILTER_TAG,
410 $hashtag,
411 false,
412 true
413 ))
414 );
415 }
390} 416}
diff --git a/tests/LinkUtilsTest.php b/tests/LinkUtilsTest.php
index d1b022fd..7c0d4b0b 100644
--- a/tests/LinkUtilsTest.php
+++ b/tests/LinkUtilsTest.php
@@ -93,4 +93,92 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
93 $refDB = new ReferenceLinkDB(); 93 $refDB = new ReferenceLinkDB();
94 $this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks())); 94 $this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks()));
95 } 95 }
96
97 /**
98 * Test text2clickable without a redirector being set.
99 */
100 public function testText2clickableWithoutRedirector()
101 {
102 $text = 'stuff http://hello.there/is=someone#here otherstuff';
103 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
104 $processedText = text2clickable($text, '');
105 $this->assertEquals($expectedText, $processedText);
106 }
107
108 /**
109 * Test text2clickable a redirector set.
110 */
111 public function testText2clickableWithRedirector()
112 {
113 $text = 'stuff http://hello.there/is=someone#here otherstuff';
114 $redirector = 'http://redirector.to';
115 $expectedText = 'stuff <a href="'.
116 $redirector .
117 urlencode('http://hello.there/is=someone#here') .
118 '">http://hello.there/is=someone#here</a> otherstuff';
119 $processedText = text2clickable($text, $redirector);
120 $this->assertEquals($expectedText, $processedText);
121 }
122
123 /**
124 * Test testSpace2nbsp.
125 */
126 public function testSpace2nbsp()
127 {
128 $text = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
129 $expectedText = '&nbsp; Are you &nbsp; thrilled &nbsp;by flags &nbsp; ?'. PHP_EOL .'&nbsp;Really?';
130 $processedText = space2nbsp($text);
131 $this->assertEquals($expectedText, $processedText);
132 }
133
134 /**
135 * Test hashtags auto-link.
136 */
137 public function testHashtagAutolink()
138 {
139 $index = 'http://domain.tld/';
140 $rawDescription = '#hashtag\n
141 # nothashtag\n
142 test#nothashtag #hashtag \#nothashtag\n
143 test #hashtag #hashtag test #hashtag.test\n
144 #hashtag #hashtag-nothashtag #hashtag_hashtag\n
145 What is #ашок anyway?\n
146 カタカナ #カタカナ」カタカナ\n';
147 $autolinkedDescription = hashtag_autolink($rawDescription, $index);
148
149 $this->assertContains($this->getHashtagLink('hashtag', $index), $autolinkedDescription);
150 $this->assertNotContains(' #hashtag', $autolinkedDescription);
151 $this->assertNotContains('>#nothashtag', $autolinkedDescription);
152 $this->assertContains($this->getHashtagLink('ашок', $index), $autolinkedDescription);
153 $this->assertContains($this->getHashtagLink('カタカナ', $index), $autolinkedDescription);
154 $this->assertContains($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription);
155 $this->assertNotContains($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription);
156 }
157
158 /**
159 * Test hashtags auto-link without index URL.
160 */
161 public function testHashtagAutolinkNoIndex()
162 {
163 $rawDescription = 'blabla #hashtag x#nothashtag';
164 $autolinkedDescription = hashtag_autolink($rawDescription);
165
166 $this->assertContains($this->getHashtagLink('hashtag'), $autolinkedDescription);
167 $this->assertNotContains(' #hashtag', $autolinkedDescription);
168 $this->assertNotContains('>#nothashtag', $autolinkedDescription);
169 }
170
171 /**
172 * Util function to build an hashtag link.
173 *
174 * @param string $hashtag Hashtag name.
175 * @param string $index Index URL.
176 *
177 * @return string HTML hashtag link.
178 */
179 private function getHashtagLink($hashtag, $index = '')
180 {
181 $hashtagLink = '<a href="'. $index .'?addtag=$1" title="Hashtag $1">#$1</a>';
182 return str_replace('$1', $hashtag, $hashtagLink);
183 }
96} 184}
diff --git a/tests/NetscapeBookmarkUtilsTest.php b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
index 41e6d84c..6a47bbb9 100644
--- a/tests/NetscapeBookmarkUtilsTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
@@ -3,9 +3,9 @@
3require_once 'application/NetscapeBookmarkUtils.php'; 3require_once 'application/NetscapeBookmarkUtils.php';
4 4
5/** 5/**
6 * Netscape bookmark import and export 6 * Netscape bookmark export
7 */ 7 */
8class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase 8class BookmarkExportTest extends PHPUnit_Framework_TestCase
9{ 9{
10 /** 10 /**
11 * @var string datastore to test write operations 11 * @var string datastore to test write operations
@@ -50,7 +50,7 @@ class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase
50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, ''); 50 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); 51 $this->assertEquals(self::$refDb->countLinks(), sizeof($links));
52 foreach ($links as $link) { 52 foreach ($links as $link) {
53 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 53 $date = $link['created'];
54 $this->assertEquals( 54 $this->assertEquals(
55 $date->getTimestamp(), 55 $date->getTimestamp(),
56 $link['timestamp'] 56 $link['timestamp']
@@ -70,7 +70,7 @@ class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase
70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, ''); 70 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); 71 $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
72 foreach ($links as $link) { 72 foreach ($links as $link) {
73 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 73 $date = $link['created'];
74 $this->assertEquals( 74 $this->assertEquals(
75 $date->getTimestamp(), 75 $date->getTimestamp(),
76 $link['timestamp'] 76 $link['timestamp']
@@ -90,7 +90,7 @@ class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase
90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); 90 $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); 91 $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
92 foreach ($links as $link) { 92 foreach ($links as $link) {
93 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 93 $date = $link['created'];
94 $this->assertEquals( 94 $this->assertEquals(
95 $date->getTimestamp(), 95 $date->getTimestamp(),
96 $link['timestamp'] 96 $link['timestamp']
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
new file mode 100644
index 00000000..0ca07eac
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -0,0 +1,590 @@
1<?php
2
3require_once 'application/NetscapeBookmarkUtils.php';
4
5
6/**
7 * Utility function to load a file's metadata in a $_FILES-like array
8 *
9 * @param string $filename Basename of the file
10 *
11 * @return array A $_FILES-like array
12 */
13function file2array($filename)
14{
15 return array(
16 'filetoupload' => array(
17 'name' => $filename,
18 'tmp_name' => __DIR__ . '/input/' . $filename,
19 'size' => filesize(__DIR__ . '/input/' . $filename)
20 )
21 );
22}
23
24
25/**
26 * Netscape bookmark import
27 */
28class BookmarkImportTest extends PHPUnit_Framework_TestCase
29{
30 /**
31 * @var string datastore to test write operations
32 */
33 protected static $testDatastore = 'sandbox/datastore.php';
34
35 /**
36 * @var LinkDB private LinkDB instance
37 */
38 protected $linkDb = null;
39
40 /**
41 * @var string Dummy page cache
42 */
43 protected $pagecache = 'tests';
44
45 /**
46 * @var string Save the current timezone.
47 */
48 protected static $defaultTimeZone;
49
50 public static function setUpBeforeClass()
51 {
52 self::$defaultTimeZone = date_default_timezone_get();
53 // Timezone without DST for test consistency
54 date_default_timezone_set('Africa/Nairobi');
55 }
56
57 /**
58 * Resets test data before each test
59 */
60 protected function setUp()
61 {
62 if (file_exists(self::$testDatastore)) {
63 unlink(self::$testDatastore);
64 }
65 // start with an empty datastore
66 file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>');
67 $this->linkDb = new LinkDB(self::$testDatastore, true, false);
68 }
69
70 public static function tearDownAfterClass()
71 {
72 date_default_timezone_set(self::$defaultTimeZone);
73 }
74
75 /**
76 * Attempt to import bookmarks from an empty file
77 */
78 public function testImportEmptyData()
79 {
80 $files = file2array('empty.htm');
81 $this->assertEquals(
82 'File empty.htm (0 bytes) has an unknown file format.'
83 .' Nothing was imported.',
84 NetscapeBookmarkUtils::import(NULL, $files, NULL, NULL)
85 );
86 $this->assertEquals(0, count($this->linkDb));
87 }
88
89 /**
90 * Attempt to import bookmarks from a file with no Doctype
91 */
92 public function testImportNoDoctype()
93 {
94 $files = file2array('no_doctype.htm');
95 $this->assertEquals(
96 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.',
97 NetscapeBookmarkUtils::import(NULL, $files, NULL, NULL)
98 );
99 $this->assertEquals(0, count($this->linkDb));
100 }
101
102 /**
103 * Ensure IE dumps are supported
104 */
105 public function testImportInternetExplorerEncoding()
106 {
107 $files = file2array('internet_explorer_encoding.htm');
108 $this->assertEquals(
109 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
110 .' 1 links imported, 0 links overwritten, 0 links skipped.',
111 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
112 );
113 $this->assertEquals(1, count($this->linkDb));
114 $this->assertEquals(0, count_private($this->linkDb));
115
116 $this->assertEquals(
117 array(
118 'id' => 0,
119 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
120 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
121 'url' => 'http://hginit.com/',
122 'description' => '',
123 'private' => 0,
124 'tags' => '',
125 'shorturl' => 'La37cg',
126 ),
127 $this->linkDb->getLinkFromUrl('http://hginit.com/')
128 );
129 }
130
131 /**
132 * Import bookmarks nested in a folder hierarchy
133 */
134 public function testImportNested()
135 {
136 $files = file2array('netscape_nested.htm');
137 $this->assertEquals(
138 'File netscape_nested.htm (1337 bytes) was successfully processed:'
139 .' 8 links imported, 0 links overwritten, 0 links skipped.',
140 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
141 );
142 $this->assertEquals(8, count($this->linkDb));
143 $this->assertEquals(2, count_private($this->linkDb));
144
145 $this->assertEquals(
146 array(
147 'id' => 0,
148 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
149 'title' => 'Nested 1',
150 'url' => 'http://nest.ed/1',
151 'description' => '',
152 'private' => 0,
153 'tags' => 'tag1 tag2',
154 'shorturl' => 'KyDNKA',
155 ),
156 $this->linkDb->getLinkFromUrl('http://nest.ed/1')
157 );
158 $this->assertEquals(
159 array(
160 'id' => 1,
161 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
162 'title' => 'Nested 1-1',
163 'url' => 'http://nest.ed/1-1',
164 'description' => '',
165 'private' => 0,
166 'tags' => 'folder1 tag1 tag2',
167 'shorturl' => 'T2LnXg',
168 ),
169 $this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
170 );
171 $this->assertEquals(
172 array(
173 'id' => 2,
174 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
175 'title' => 'Nested 1-2',
176 'url' => 'http://nest.ed/1-2',
177 'description' => '',
178 'private' => 0,
179 'tags' => 'folder1 tag3 tag4',
180 'shorturl' => '46SZxA',
181 ),
182 $this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
183 );
184 $this->assertEquals(
185 array(
186 'id' => 3,
187 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
188 'title' => 'Nested 2-1',
189 'url' => 'http://nest.ed/2-1',
190 'description' => 'First link of the second section',
191 'private' => 1,
192 'tags' => 'folder2',
193 'shorturl' => '4UHOSw',
194 ),
195 $this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
196 );
197 $this->assertEquals(
198 array(
199 'id' => 4,
200 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
201 'title' => 'Nested 2-2',
202 'url' => 'http://nest.ed/2-2',
203 'description' => 'Second link of the second section',
204 'private' => 1,
205 'tags' => 'folder2',
206 'shorturl' => 'yfzwbw',
207 ),
208 $this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
209 );
210 $this->assertEquals(
211 array(
212 'id' => 5,
213 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
214 'title' => 'Nested 3-1',
215 'url' => 'http://nest.ed/3-1',
216 'description' => '',
217 'private' => 0,
218 'tags' => 'folder3 folder3-1 tag3',
219 'shorturl' => 'UwxIUQ',
220 ),
221 $this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
222 );
223 $this->assertEquals(
224 array(
225 'id' => 6,
226 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
227 'title' => 'Nested 3-2',
228 'url' => 'http://nest.ed/3-2',
229 'description' => '',
230 'private' => 0,
231 'tags' => 'folder3 folder3-1',
232 'shorturl' => 'p8dyZg',
233 ),
234 $this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
235 );
236 $this->assertEquals(
237 array(
238 'id' => 7,
239 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
240 'title' => 'Nested 2',
241 'url' => 'http://nest.ed/2',
242 'description' => '',
243 'private' => 0,
244 'tags' => 'tag4',
245 'shorturl' => 'Gt3Uug',
246 ),
247 $this->linkDb->getLinkFromUrl('http://nest.ed/2')
248 );
249 }
250
251 /**
252 * Import bookmarks with the default privacy setting (reuse from file)
253 *
254 * The $_POST array is not set.
255 */
256 public function testImportDefaultPrivacyNoPost()
257 {
258 $files = file2array('netscape_basic.htm');
259 $this->assertEquals(
260 'File netscape_basic.htm (482 bytes) was successfully processed:'
261 .' 2 links imported, 0 links overwritten, 0 links skipped.',
262 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
263 );
264
265 $this->assertEquals(2, count($this->linkDb));
266 $this->assertEquals(1, count_private($this->linkDb));
267
268 $this->assertEquals(
269 array(
270 'id' => 0,
271 // Old link - UTC+4 (note that TZ in the import file is ignored).
272 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
273 'title' => 'Secret stuff',
274 'url' => 'https://private.tld',
275 'description' => "Super-secret stuff you're not supposed to know about",
276 'private' => 1,
277 'tags' => 'private secret',
278 'shorturl' => 'EokDtA',
279 ),
280 $this->linkDb->getLinkFromUrl('https://private.tld')
281 );
282 $this->assertEquals(
283 array(
284 'id' => 1,
285 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
286 'title' => 'Public stuff',
287 'url' => 'http://public.tld',
288 'description' => '',
289 'private' => 0,
290 'tags' => 'public hello world',
291 'shorturl' => 'Er9ddA',
292 ),
293 $this->linkDb->getLinkFromUrl('http://public.tld')
294 );
295 }
296
297 /**
298 * Import bookmarks with the default privacy setting (reuse from file)
299 */
300 public function testImportKeepPrivacy()
301 {
302 $post = array('privacy' => 'default');
303 $files = file2array('netscape_basic.htm');
304 $this->assertEquals(
305 'File netscape_basic.htm (482 bytes) was successfully processed:'
306 .' 2 links imported, 0 links overwritten, 0 links skipped.',
307 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
308 );
309 $this->assertEquals(2, count($this->linkDb));
310 $this->assertEquals(1, count_private($this->linkDb));
311
312 $this->assertEquals(
313 array(
314 'id' => 0,
315 // Note that TZ in the import file is ignored.
316 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
317 'title' => 'Secret stuff',
318 'url' => 'https://private.tld',
319 'description' => "Super-secret stuff you're not supposed to know about",
320 'private' => 1,
321 'tags' => 'private secret',
322 'shorturl' => 'EokDtA',
323 ),
324 $this->linkDb->getLinkFromUrl('https://private.tld')
325 );
326 $this->assertEquals(
327 array(
328 'id' => 1,
329 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
330 'title' => 'Public stuff',
331 'url' => 'http://public.tld',
332 'description' => '',
333 'private' => 0,
334 'tags' => 'public hello world',
335 'shorturl' => 'Er9ddA',
336 ),
337 $this->linkDb->getLinkFromUrl('http://public.tld')
338 );
339 }
340
341 /**
342 * Import links as public
343 */
344 public function testImportAsPublic()
345 {
346 $post = array('privacy' => 'public');
347 $files = file2array('netscape_basic.htm');
348 $this->assertEquals(
349 'File netscape_basic.htm (482 bytes) was successfully processed:'
350 .' 2 links imported, 0 links overwritten, 0 links skipped.',
351 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
352 );
353 $this->assertEquals(2, count($this->linkDb));
354 $this->assertEquals(0, count_private($this->linkDb));
355 $this->assertEquals(
356 0,
357 $this->linkDb[0]['private']
358 );
359 $this->assertEquals(
360 0,
361 $this->linkDb[1]['private']
362 );
363 }
364
365 /**
366 * Import links as private
367 */
368 public function testImportAsPrivate()
369 {
370 $post = array('privacy' => 'private');
371 $files = file2array('netscape_basic.htm');
372 $this->assertEquals(
373 'File netscape_basic.htm (482 bytes) was successfully processed:'
374 .' 2 links imported, 0 links overwritten, 0 links skipped.',
375 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
376 );
377 $this->assertEquals(2, count($this->linkDb));
378 $this->assertEquals(2, count_private($this->linkDb));
379 $this->assertEquals(
380 1,
381 $this->linkDb['0']['private']
382 );
383 $this->assertEquals(
384 1,
385 $this->linkDb['1']['private']
386 );
387 }
388
389 /**
390 * Overwrite private links so they become public
391 */
392 public function testOverwriteAsPublic()
393 {
394 $files = file2array('netscape_basic.htm');
395
396 // import links as private
397 $post = array('privacy' => 'private');
398 $this->assertEquals(
399 'File netscape_basic.htm (482 bytes) was successfully processed:'
400 .' 2 links imported, 0 links overwritten, 0 links skipped.',
401 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
402 );
403 $this->assertEquals(2, count($this->linkDb));
404 $this->assertEquals(2, count_private($this->linkDb));
405 $this->assertEquals(
406 1,
407 $this->linkDb[0]['private']
408 );
409 $this->assertEquals(
410 1,
411 $this->linkDb[1]['private']
412 );
413 // re-import as public, enable overwriting
414 $post = array(
415 'privacy' => 'public',
416 'overwrite' => 'true'
417 );
418 $this->assertEquals(
419 'File netscape_basic.htm (482 bytes) was successfully processed:'
420 .' 2 links imported, 2 links overwritten, 0 links skipped.',
421 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
422 );
423 $this->assertEquals(2, count($this->linkDb));
424 $this->assertEquals(0, count_private($this->linkDb));
425 $this->assertEquals(
426 0,
427 $this->linkDb[0]['private']
428 );
429 $this->assertEquals(
430 0,
431 $this->linkDb[1]['private']
432 );
433 }
434
435 /**
436 * Overwrite public links so they become private
437 */
438 public function testOverwriteAsPrivate()
439 {
440 $files = file2array('netscape_basic.htm');
441
442 // import links as public
443 $post = array('privacy' => 'public');
444 $this->assertEquals(
445 'File netscape_basic.htm (482 bytes) was successfully processed:'
446 .' 2 links imported, 0 links overwritten, 0 links skipped.',
447 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
448 );
449 $this->assertEquals(2, count($this->linkDb));
450 $this->assertEquals(0, count_private($this->linkDb));
451 $this->assertEquals(
452 0,
453 $this->linkDb['0']['private']
454 );
455 $this->assertEquals(
456 0,
457 $this->linkDb['1']['private']
458 );
459
460 // re-import as private, enable overwriting
461 $post = array(
462 'privacy' => 'private',
463 'overwrite' => 'true'
464 );
465 $this->assertEquals(
466 'File netscape_basic.htm (482 bytes) was successfully processed:'
467 .' 2 links imported, 2 links overwritten, 0 links skipped.',
468 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
469 );
470 $this->assertEquals(2, count($this->linkDb));
471 $this->assertEquals(2, count_private($this->linkDb));
472 $this->assertEquals(
473 1,
474 $this->linkDb['0']['private']
475 );
476 $this->assertEquals(
477 1,
478 $this->linkDb['1']['private']
479 );
480 }
481
482 /**
483 * Attept to import the same links twice without enabling overwriting
484 */
485 public function testSkipOverwrite()
486 {
487 $post = array('privacy' => 'public');
488 $files = file2array('netscape_basic.htm');
489 $this->assertEquals(
490 'File netscape_basic.htm (482 bytes) was successfully processed:'
491 .' 2 links imported, 0 links overwritten, 0 links skipped.',
492 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
493 );
494 $this->assertEquals(2, count($this->linkDb));
495 $this->assertEquals(0, count_private($this->linkDb));
496
497 // re-import as private, DO NOT enable overwriting
498 $post = array('privacy' => 'private');
499 $this->assertEquals(
500 'File netscape_basic.htm (482 bytes) was successfully processed:'
501 .' 0 links imported, 0 links overwritten, 2 links skipped.',
502 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
503 );
504 $this->assertEquals(2, count($this->linkDb));
505 $this->assertEquals(0, count_private($this->linkDb));
506 }
507
508 /**
509 * Add user-specified tags to all imported bookmarks
510 */
511 public function testSetDefaultTags()
512 {
513 $post = array(
514 'privacy' => 'public',
515 'default_tags' => 'tag1,tag2 tag3'
516 );
517 $files = file2array('netscape_basic.htm');
518 $this->assertEquals(
519 'File netscape_basic.htm (482 bytes) was successfully processed:'
520 .' 2 links imported, 0 links overwritten, 0 links skipped.',
521 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
522 );
523 $this->assertEquals(2, count($this->linkDb));
524 $this->assertEquals(0, count_private($this->linkDb));
525 $this->assertEquals(
526 'tag1 tag2 tag3 private secret',
527 $this->linkDb['0']['tags']
528 );
529 $this->assertEquals(
530 'tag1 tag2 tag3 public hello world',
531 $this->linkDb['1']['tags']
532 );
533 }
534
535 /**
536 * The user-specified tags contain characters to be escaped
537 */
538 public function testSanitizeDefaultTags()
539 {
540 $post = array(
541 'privacy' => 'public',
542 'default_tags' => 'tag1&,tag2 "tag3"'
543 );
544 $files = file2array('netscape_basic.htm');
545 $this->assertEquals(
546 'File netscape_basic.htm (482 bytes) was successfully processed:'
547 .' 2 links imported, 0 links overwritten, 0 links skipped.',
548 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->pagecache)
549 );
550 $this->assertEquals(2, count($this->linkDb));
551 $this->assertEquals(0, count_private($this->linkDb));
552 $this->assertEquals(
553 'tag1&amp; tag2 &quot;tag3&quot; private secret',
554 $this->linkDb['0']['tags']
555 );
556 $this->assertEquals(
557 'tag1&amp; tag2 &quot;tag3&quot; public hello world',
558 $this->linkDb['1']['tags']
559 );
560 }
561
562 /**
563 * Ensure each imported bookmark has a unique id
564 *
565 * See https://github.com/shaarli/Shaarli/issues/351
566 */
567 public function testImportSameDate()
568 {
569 $files = file2array('same_date.htm');
570 $this->assertEquals(
571 'File same_date.htm (453 bytes) was successfully processed:'
572 .' 3 links imported, 0 links overwritten, 0 links skipped.',
573 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
574 );
575 $this->assertEquals(3, count($this->linkDb));
576 $this->assertEquals(0, count_private($this->linkDb));
577 $this->assertEquals(
578 0,
579 $this->linkDb[0]['id']
580 );
581 $this->assertEquals(
582 1,
583 $this->linkDb[1]['id']
584 );
585 $this->assertEquals(
586 2,
587 $this->linkDb[2]['id']
588 );
589 }
590}
diff --git a/tests/NetscapeBookmarkUtils/input/empty.htm b/tests/NetscapeBookmarkUtils/input/empty.htm
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/empty.htm
diff --git a/tests/NetscapeBookmarkUtils/input/internet_explorer_encoding.htm b/tests/NetscapeBookmarkUtils/input/internet_explorer_encoding.htm
new file mode 100644
index 00000000..18703cf6
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/internet_explorer_encoding.htm
@@ -0,0 +1,9 @@
1<!DOCTYPE NETSCAPE-Bookmark-file-1>
2<!-- This is an automatically generated file.
3It will be read and overwritten.
4Do Not Edit! -->
5<TITLE>Bookmarks</TITLE>
6<H1>Bookmarks</H1>
7<DL><p>
8 <DT><A HREF="http://hginit.com/" ADD_DATE="1466271584" LAST_VISIT="1466271584" LAST_MODIFIED="1466271584" >Hg Init a Mercurial tutorial by Joel Spolsky</A>
9</DL><p>
diff --git a/tests/NetscapeBookmarkUtils/input/netscape_basic.htm b/tests/NetscapeBookmarkUtils/input/netscape_basic.htm
new file mode 100644
index 00000000..affe0cf8
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/netscape_basic.htm
@@ -0,0 +1,11 @@
1<!DOCTYPE NETSCAPE-Bookmark-file-1>
2<!-- This is an automatically generated file.
3It will be read and overwritten.
4Do Not Edit! -->
5<TITLE>Bookmarks</TITLE>
6<H1>Bookmarks</H1>
7<DL><p>
8<DT><A HREF="https://private.tld" ADD_DATE="10/Oct/2000:13:55:36 +0300" PRIVATE="1" TAGS="private secret">Secret stuff</A>
9<DD>Super-secret stuff you're not supposed to know about
10<DT><A HREF="http://public.tld" ADD_DATE="1456433748" PRIVATE="0" TAGS="public hello world">Public stuff</A>
11</DL><p>
diff --git a/tests/NetscapeBookmarkUtils/input/netscape_nested.htm b/tests/NetscapeBookmarkUtils/input/netscape_nested.htm
new file mode 100644
index 00000000..b486fe18
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/netscape_nested.htm
@@ -0,0 +1,31 @@
1<!DOCTYPE NETSCAPE-Bookmark-file-1>
2<!-- This is an automatically generated file.
3It will be read and overwritten.
4Do Not Edit! -->
5<TITLE>Bookmarks</TITLE>
6<H1>Bookmarks</H1>
7<DL><p>
8 <DT><A HREF="http://nest.ed/1" ADD_DATE="1456433741" PRIVATE="0" TAGS="tag1,tag2">Nested 1</A>
9 <DT><H3 ADD_DATE="1456433722" LAST_MODIFIED="1456433739">Folder1</H3>
10 <DL><p>
11 <DT><A HREF="http://nest.ed/1-1" ADD_DATE="1456433742" PRIVATE="0" TAGS="tag1,tag2">Nested 1-1</A>
12 <DT><A HREF="http://nest.ed/1-2" ADD_DATE="1456433747" PRIVATE="0" TAGS="tag3,tag4">Nested 1-2</A>
13 </DL><p>
14 <DT><H3 ADD_DATE="1456433722">Folder2</H3>
15 <DD>This second folder contains wonderful links!
16 <DL><p>
17 <DT><A HREF="http://nest.ed/2-1" ADD_DATE="1454433742" PRIVATE="1">Nested 2-1</A>
18 <DD>First link of the second section
19 <DT><A HREF="http://nest.ed/2-2" ADD_DATE="1453233747" PRIVATE="1">Nested 2-2</A>
20 <DD>Second link of the second section
21 </DL><p>
22 <DT><H3>Folder3</H3>
23 <DL><p>
24 <DT><H3>Folder3-1</H3>
25 <DL><p>
26 <DT><A HREF="http://nest.ed/3-1" ADD_DATE="1454433742" PRIVATE="0" TAGS="tag3">Nested 3-1</A>
27 <DT><A HREF="http://nest.ed/3-2" ADD_DATE="1453233747" PRIVATE="0">Nested 3-2</A>
28 </DL><p>
29 </DL><p>
30 <DT><A HREF="http://nest.ed/2" ADD_DATE="1456733741" PRIVATE="0" TAGS="tag4">Nested 2</A>
31</DL><p>
diff --git a/tests/NetscapeBookmarkUtils/input/no_doctype.htm b/tests/NetscapeBookmarkUtils/input/no_doctype.htm
new file mode 100644
index 00000000..766d398b
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/no_doctype.htm
@@ -0,0 +1,7 @@
1<TITLE>Bookmarks</TITLE>
2<H1>Bookmarks</H1>
3<DL><p>
4<DT><A HREF="https://private.tld" ADD_DATE="10/Oct/2000:13:55:36 +0300" PRIVATE="1" TAGS="private secret">Secret stuff</A>
5<DD>Super-secret stuff you're not supposed to know about
6<DT><A HREF="http://public.tld" ADD_DATE="1456433748" PRIVATE="0" TAGS="public hello world">Public stuff</A>
7</DL><p>
diff --git a/tests/NetscapeBookmarkUtils/input/same_date.htm b/tests/NetscapeBookmarkUtils/input/same_date.htm
new file mode 100644
index 00000000..9d58a582
--- /dev/null
+++ b/tests/NetscapeBookmarkUtils/input/same_date.htm
@@ -0,0 +1,11 @@
1<!DOCTYPE NETSCAPE-Bookmark-file-1>
2<!-- This is an automatically generated file.
3It will be read and overwritten.
4Do Not Edit! -->
5<TITLE>Bookmarks</TITLE>
6<H1>Bookmarks</H1>
7<DL><p>
8<DT><A HREF="https://fir.st" ADD_DATE="1456433748" PRIVATE="0">Today's first link</A>
9<DT><A HREF="https://seco.nd" ADD_DATE="1456433748" PRIVATE="0">Today's second link</A>
10<DT><A HREF="https://thi.rd" ADD_DATE="1456433748" PRIVATE="0">Today's third link</A>
11</DL><p>
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php
index 348082c7..ddf48185 100644
--- a/tests/PluginManagerTest.php
+++ b/tests/PluginManagerTest.php
@@ -24,29 +24,38 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
24 private static $pluginName = 'test'; 24 private static $pluginName = 'test';
25 25
26 /** 26 /**
27 * @var PluginManager $pluginManager Plugin Mananger instance.
28 */
29 protected $pluginManager;
30
31 public function setUp()
32 {
33 $conf = new ConfigManager('');
34 $this->pluginManager = new PluginManager($conf);
35 }
36
37 /**
27 * Test plugin loading and hook execution. 38 * Test plugin loading and hook execution.
28 * 39 *
29 * @return void 40 * @return void
30 */ 41 */
31 public function testPlugin() 42 public function testPlugin()
32 { 43 {
33 $pluginManager = PluginManager::getInstance();
34
35 PluginManager::$PLUGINS_PATH = self::$pluginPath; 44 PluginManager::$PLUGINS_PATH = self::$pluginPath;
36 $pluginManager->load(array(self::$pluginName)); 45 $this->pluginManager->load(array(self::$pluginName));
37 46
38 $this->assertTrue(function_exists('hook_test_random')); 47 $this->assertTrue(function_exists('hook_test_random'));
39 48
40 $data = array(0 => 'woot'); 49 $data = array(0 => 'woot');
41 $pluginManager->executeHooks('random', $data); 50 $this->pluginManager->executeHooks('random', $data);
42 $this->assertEquals('woot', $data[1]); 51 $this->assertEquals('woot', $data[1]);
43 52
44 $data = array(0 => 'woot'); 53 $data = array(0 => 'woot');
45 $pluginManager->executeHooks('random', $data, array('target' => 'test')); 54 $this->pluginManager->executeHooks('random', $data, array('target' => 'test'));
46 $this->assertEquals('page test', $data[1]); 55 $this->assertEquals('page test', $data[1]);
47 56
48 $data = array(0 => 'woot'); 57 $data = array(0 => 'woot');
49 $pluginManager->executeHooks('random', $data, array('loggedin' => true)); 58 $this->pluginManager->executeHooks('random', $data, array('loggedin' => true));
50 $this->assertEquals('loggedin', $data[1]); 59 $this->assertEquals('loggedin', $data[1]);
51 } 60 }
52 61
@@ -57,11 +66,8 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
57 */ 66 */
58 public function testPluginNotFound() 67 public function testPluginNotFound()
59 { 68 {
60 $pluginManager = PluginManager::getInstance(); 69 $this->pluginManager->load(array());
61 70 $this->pluginManager->load(array('nope', 'renope'));
62 $pluginManager->load(array());
63
64 $pluginManager->load(array('nope', 'renope'));
65 } 71 }
66 72
67 /** 73 /**
@@ -69,17 +75,21 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
69 */ 75 */
70 public function testGetPluginsMeta() 76 public function testGetPluginsMeta()
71 { 77 {
72 $pluginManager = PluginManager::getInstance();
73
74 PluginManager::$PLUGINS_PATH = self::$pluginPath; 78 PluginManager::$PLUGINS_PATH = self::$pluginPath;
75 $pluginManager->load(array(self::$pluginName)); 79 $this->pluginManager->load(array(self::$pluginName));
76 80
77 $expectedParameters = array( 81 $expectedParameters = array(
78 'pop' => '', 82 'pop' => array(
79 'hip' => '', 83 'value' => '',
84 'desc' => 'pop description',
85 ),
86 'hip' => array(
87 'value' => '',
88 'desc' => '',
89 ),
80 ); 90 );
81 $meta = $pluginManager->getPluginsMeta(); 91 $meta = $this->pluginManager->getPluginsMeta();
82 $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); 92 $this->assertEquals('test plugin', $meta[self::$pluginName]['description']);
83 $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); 93 $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']);
84 } 94 }
85} \ No newline at end of file 95}
diff --git a/tests/Updater/DummyUpdater.php b/tests/Updater/DummyUpdater.php
index e9ef2aaa..a0be4413 100644
--- a/tests/Updater/DummyUpdater.php
+++ b/tests/Updater/DummyUpdater.php
@@ -11,14 +11,14 @@ class DummyUpdater extends Updater
11 /** 11 /**
12 * Object constructor. 12 * Object constructor.
13 * 13 *
14 * @param array $doneUpdates Updates which are already done. 14 * @param array $doneUpdates Updates which are already done.
15 * @param array $config Shaarli's configuration array. 15 * @param LinkDB $linkDB LinkDB instance.
16 * @param LinkDB $linkDB LinkDB instance. 16 * @param ConfigManager $conf Configuration Manager instance.
17 * @param boolean $isLoggedIn True if the user is logged in. 17 * @param boolean $isLoggedIn True if the user is logged in.
18 */ 18 */
19 public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn) 19 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
20 { 20 {
21 parent::__construct($doneUpdates, $config, $linkDB, $isLoggedIn); 21 parent::__construct($doneUpdates, $linkDB, $conf, $isLoggedIn);
22 22
23 // Retrieve all update methods. 23 // Retrieve all update methods.
24 // For unit test, only retrieve final methods, 24 // For unit test, only retrieve final methods,
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index a29d9067..a3e8a4d2 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -1,5 +1,6 @@
1<?php 1<?php
2 2
3require_once 'application/config/ConfigManager.php';
3require_once 'tests/Updater/DummyUpdater.php'; 4require_once 'tests/Updater/DummyUpdater.php';
4 5
5/** 6/**
@@ -9,58 +10,26 @@ require_once 'tests/Updater/DummyUpdater.php';
9class UpdaterTest extends PHPUnit_Framework_TestCase 10class UpdaterTest extends PHPUnit_Framework_TestCase
10{ 11{
11 /** 12 /**
12 * @var array Configuration input set. 13 * @var string Path to test datastore.
13 */ 14 */
14 private static $configFields; 15 protected static $testDatastore = 'sandbox/datastore.php';
15 16
16 /** 17 /**
17 * @var string Path to test datastore. 18 * @var string Config file path (without extension).
18 */ 19 */
19 protected static $testDatastore = 'sandbox/datastore.php'; 20 protected static $configFile = 'tests/utils/config/configJson';
20 21
21 /** 22 /**
22 * Executed before each test. 23 * @var ConfigManager
23 */ 24 */
24 public function setUp() 25 protected $conf;
25 {
26 self::$configFields = array(
27 'login' => 'login',
28 'hash' => 'hash',
29 'salt' => 'salt',
30 'timezone' => 'Europe/Paris',
31 'title' => 'title',
32 'titleLink' => 'titleLink',
33 'redirector' => '',
34 'disablesessionprotection' => false,
35 'privateLinkByDefault' => false,
36 'config' => array(
37 'CONFIG_FILE' => 'tests/Updater/config.php',
38 'DATADIR' => 'tests/Updater',
39 'PAGECACHE' => 'sandbox/pagecache',
40 'config1' => 'config1data',
41 'config2' => 'config2data',
42 )
43 );
44 }
45 26
46 /** 27 /**
47 * Executed after each test. 28 * Executed before each test.
48 *
49 * @return void
50 */ 29 */
51 public function tearDown() 30 public function setUp()
52 { 31 {
53 if (is_file(self::$configFields['config']['CONFIG_FILE'])) { 32 $this->conf = new ConfigManager(self::$configFile);
54 unlink(self::$configFields['config']['CONFIG_FILE']);
55 }
56
57 if (is_file(self::$configFields['config']['DATADIR'] . '/options.php')) {
58 unlink(self::$configFields['config']['DATADIR'] . '/options.php');
59 }
60
61 if (is_file(self::$configFields['config']['DATADIR'] . '/updates.json')) {
62 unlink(self::$configFields['config']['DATADIR'] . '/updates.json');
63 }
64 } 33 }
65 34
66 /** 35 /**
@@ -69,9 +38,10 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
69 public function testReadEmptyUpdatesFile() 38 public function testReadEmptyUpdatesFile()
70 { 39 {
71 $this->assertEquals(array(), read_updates_file('')); 40 $this->assertEquals(array(), read_updates_file(''));
72 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 41 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
73 touch($updatesFile); 42 touch($updatesFile);
74 $this->assertEquals(array(), read_updates_file($updatesFile)); 43 $this->assertEquals(array(), read_updates_file($updatesFile));
44 unlink($updatesFile);
75 } 45 }
76 46
77 /** 47 /**
@@ -79,7 +49,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
79 */ 49 */
80 public function testReadWriteUpdatesFile() 50 public function testReadWriteUpdatesFile()
81 { 51 {
82 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 52 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
83 $updatesMethods = array('m1', 'm2', 'm3'); 53 $updatesMethods = array('m1', 'm2', 'm3');
84 54
85 write_updates_file($updatesFile, $updatesMethods); 55 write_updates_file($updatesFile, $updatesMethods);
@@ -91,6 +61,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
91 write_updates_file($updatesFile, $updatesMethods); 61 write_updates_file($updatesFile, $updatesMethods);
92 $readMethods = read_updates_file($updatesFile); 62 $readMethods = read_updates_file($updatesFile);
93 $this->assertEquals($readMethods, $updatesMethods); 63 $this->assertEquals($readMethods, $updatesMethods);
64 unlink($updatesFile);
94 } 65 }
95 66
96 /** 67 /**
@@ -112,10 +83,15 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
112 */ 83 */
113 public function testWriteUpdatesFileNotWritable() 84 public function testWriteUpdatesFileNotWritable()
114 { 85 {
115 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 86 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
116 touch($updatesFile); 87 touch($updatesFile);
117 chmod($updatesFile, 0444); 88 chmod($updatesFile, 0444);
118 @write_updates_file($updatesFile, array('test')); 89 try {
90 @write_updates_file($updatesFile, array('test'));
91 } catch (Exception $e) {
92 unlink($updatesFile);
93 throw $e;
94 }
119 } 95 }
120 96
121 /** 97 /**
@@ -131,10 +107,10 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
131 'updateMethodDummy3', 107 'updateMethodDummy3',
132 'updateMethodException', 108 'updateMethodException',
133 ); 109 );
134 $updater = new DummyUpdater($updates, array(), array(), true); 110 $updater = new DummyUpdater($updates, array(), $this->conf, true);
135 $this->assertEquals(array(), $updater->update()); 111 $this->assertEquals(array(), $updater->update());
136 112
137 $updater = new DummyUpdater(array(), array(), array(), false); 113 $updater = new DummyUpdater(array(), array(), $this->conf, false);
138 $this->assertEquals(array(), $updater->update()); 114 $this->assertEquals(array(), $updater->update());
139 } 115 }
140 116
@@ -149,7 +125,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
149 'updateMethodDummy2', 125 'updateMethodDummy2',
150 'updateMethodDummy3', 126 'updateMethodDummy3',
151 ); 127 );
152 $updater = new DummyUpdater($updates, array(), array(), true); 128 $updater = new DummyUpdater($updates, array(), $this->conf, true);
153 $this->assertEquals($expectedUpdates, $updater->update()); 129 $this->assertEquals($expectedUpdates, $updater->update());
154 } 130 }
155 131
@@ -165,7 +141,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
165 ); 141 );
166 $expectedUpdate = array('updateMethodDummy2'); 142 $expectedUpdate = array('updateMethodDummy2');
167 143
168 $updater = new DummyUpdater($updates, array(), array(), true); 144 $updater = new DummyUpdater($updates, array(), $this->conf, true);
169 $this->assertEquals($expectedUpdate, $updater->update()); 145 $this->assertEquals($expectedUpdate, $updater->update());
170 } 146 }
171 147
@@ -182,7 +158,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
182 'updateMethodDummy3', 158 'updateMethodDummy3',
183 ); 159 );
184 160
185 $updater = new DummyUpdater($updates, array(), array(), true); 161 $updater = new DummyUpdater($updates, array(), $this->conf, true);
186 $updater->update(); 162 $updater->update();
187 } 163 }
188 164
@@ -195,26 +171,28 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
195 */ 171 */
196 public function testUpdateMergeDeprecatedConfig() 172 public function testUpdateMergeDeprecatedConfig()
197 { 173 {
198 // init 174 $this->conf->setConfigFile('tests/utils/config/configPhp');
199 writeConfig(self::$configFields, true); 175 $this->conf->reset();
200 $configCopy = self::$configFields;
201 $invert = !$configCopy['privateLinkByDefault'];
202 $configCopy['privateLinkByDefault'] = $invert;
203 176
204 // Use writeConfig to create a options.php 177 $optionsFile = 'tests/Updater/options.php';
205 $configCopy['config']['CONFIG_FILE'] = 'tests/Updater/options.php'; 178 $options = '<?php
206 writeConfig($configCopy, true); 179$GLOBALS[\'privateLinkByDefault\'] = true;';
180 file_put_contents($optionsFile, $options);
207 181
208 $this->assertTrue(is_file($configCopy['config']['CONFIG_FILE'])); 182 // tmp config file.
183 $this->conf->setConfigFile('tests/Updater/config');
209 184
210 // merge configs 185 // merge configs
211 $updater = new Updater(array(), self::$configFields, array(), true); 186 $updater = new Updater(array(), array(), $this->conf, true);
187 // This writes a new config file in tests/Updater/config.php
212 $updater->updateMethodMergeDeprecatedConfigFile(); 188 $updater->updateMethodMergeDeprecatedConfigFile();
213 189
214 // make sure updated field is changed 190 // make sure updated field is changed
215 include self::$configFields['config']['CONFIG_FILE']; 191 $this->conf->reload();
216 $this->assertEquals($invert, $GLOBALS['privateLinkByDefault']); 192 $this->assertTrue($this->conf->get('privacy.default_private_links'));
217 $this->assertFalse(is_file($configCopy['config']['CONFIG_FILE'])); 193 $this->assertFalse(is_file($optionsFile));
194 // Delete the generated file.
195 unlink($this->conf->getConfigFileExt());
218 } 196 }
219 197
220 /** 198 /**
@@ -222,23 +200,254 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
222 */ 200 */
223 public function testMergeDeprecatedConfigNoFile() 201 public function testMergeDeprecatedConfigNoFile()
224 { 202 {
225 writeConfig(self::$configFields, true); 203 $updater = new Updater(array(), array(), $this->conf, true);
226
227 $updater = new Updater(array(), self::$configFields, array(), true);
228 $updater->updateMethodMergeDeprecatedConfigFile(); 204 $updater->updateMethodMergeDeprecatedConfigFile();
229 205
230 include self::$configFields['config']['CONFIG_FILE']; 206 $this->assertEquals('root', $this->conf->get('credentials.login'));
231 $this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
232 } 207 }
233 208
209 /**
210 * Test renameDashTags update method.
211 */
234 public function testRenameDashTags() 212 public function testRenameDashTags()
235 { 213 {
236 $refDB = new ReferenceLinkDB(); 214 $refDB = new ReferenceLinkDB();
237 $refDB->write(self::$testDatastore); 215 $refDB->write(self::$testDatastore);
238 $linkDB = new LinkDB(self::$testDatastore, true, false); 216 $linkDB = new LinkDB(self::$testDatastore, true, false);
217
239 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 218 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
240 $updater = new Updater(array(), self::$configFields, $linkDB, true); 219 $updater = new Updater(array(), $linkDB, $this->conf, true);
241 $updater->updateMethodRenameDashTags(); 220 $updater->updateMethodRenameDashTags();
242 $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 221 $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
243 } 222 }
223
224 /**
225 * Convert old PHP config file to JSON config.
226 */
227 public function testConfigToJson()
228 {
229 $configFile = 'tests/utils/config/configPhp';
230 $this->conf->setConfigFile($configFile);
231 $this->conf->reset();
232
233 // The ConfigIO is initialized with ConfigPhp.
234 $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp);
235
236 $updater = new Updater(array(), array(), $this->conf, false);
237 $done = $updater->updateMethodConfigToJson();
238 $this->assertTrue($done);
239
240 // The ConfigIO has been updated to ConfigJson.
241 $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson);
242 $this->assertTrue(file_exists($this->conf->getConfigFileExt()));
243
244 // Check JSON config data.
245 $this->conf->reload();
246 $this->assertEquals('root', $this->conf->get('credentials.login'));
247 $this->assertEquals('lala', $this->conf->get('redirector.url'));
248 $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore'));
249 $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION'));
250
251 rename($configFile . '.save.php', $configFile . '.php');
252 unlink($this->conf->getConfigFileExt());
253 }
254
255 /**
256 * Launch config conversion update with an existing JSON file => nothing to do.
257 */
258 public function testConfigToJsonNothingToDo()
259 {
260 $filetime = filemtime($this->conf->getConfigFileExt());
261 $updater = new Updater(array(), array(), $this->conf, false);
262 $done = $updater->updateMethodConfigToJson();
263 $this->assertTrue($done);
264 $expected = filemtime($this->conf->getConfigFileExt());
265 $this->assertEquals($expected, $filetime);
266 }
267
268 /**
269 * Test escapeUnescapedConfig with valid data.
270 */
271 public function testEscapeConfig()
272 {
273 $sandbox = 'sandbox/config';
274 copy(self::$configFile .'.json.php', $sandbox .'.json.php');
275 $this->conf = new ConfigManager($sandbox);
276 $title = '<script>alert("title");</script>';
277 $headerLink = '<script>alert("header_link");</script>';
278 $redirectorUrl = '<script>alert("redirector");</script>';
279 $this->conf->set('general.title', $title);
280 $this->conf->set('general.header_link', $headerLink);
281 $this->conf->set('redirector.url', $redirectorUrl);
282 $updater = new Updater(array(), array(), $this->conf, true);
283 $done = $updater->updateMethodEscapeUnescapedConfig();
284 $this->assertTrue($done);
285 $this->conf->reload();
286 $this->assertEquals(escape($title), $this->conf->get('general.title'));
287 $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link'));
288 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
289 unlink($sandbox .'.json.php');
290 }
291
292 /**
293 * Test updateMethodDatastoreIds().
294 */
295 public function testDatastoreIds()
296 {
297 $links = array(
298 '20121206_182539' => array(
299 'linkdate' => '20121206_182539',
300 'title' => 'Geek and Poke',
301 'url' => 'http://geek-and-poke.com/',
302 'description' => 'desc',
303 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ',
304 'updated' => '20121206_190301',
305 'private' => false,
306 ),
307 '20121206_172539' => array(
308 'linkdate' => '20121206_172539',
309 'title' => 'UserFriendly - Samba',
310 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306',
311 'description' => '',
312 'tags' => 'samba cartoon web',
313 'private' => false,
314 ),
315 '20121206_142300' => array(
316 'linkdate' => '20121206_142300',
317 'title' => 'UserFriendly - Web Designer',
318 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206',
319 'description' => 'Naming conventions... #private',
320 'tags' => 'samba cartoon web',
321 'private' => true,
322 ),
323 );
324 $refDB = new ReferenceLinkDB();
325 $refDB->setLinks($links);
326 $refDB->write(self::$testDatastore);
327 $linkDB = new LinkDB(self::$testDatastore, true, false);
328
329 $checksum = hash_file('sha1', self::$testDatastore);
330
331 $this->conf->set('resource.data_dir', 'sandbox');
332 $this->conf->set('resource.datastore', self::$testDatastore);
333
334 $updater = new Updater(array(), $linkDB, $this->conf, true);
335 $this->assertTrue($updater->updateMethodDatastoreIds());
336
337 $linkDB = new LinkDB(self::$testDatastore, true, false);
338
339 $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php');
340 $backup = $backup[0];
341
342 $this->assertFileExists($backup);
343 $this->assertEquals($checksum, hash_file('sha1', $backup));
344 unlink($backup);
345
346 $this->assertEquals(3, count($linkDB));
347 $this->assertTrue(isset($linkDB[0]));
348 $this->assertFalse(isset($linkDB[0]['linkdate']));
349 $this->assertEquals(0, $linkDB[0]['id']);
350 $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']);
351 $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']);
352 $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
353 $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
354 $this->assertTrue($linkDB[0]['private']);
355 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']);
356
357 $this->assertTrue(isset($linkDB[1]));
358 $this->assertFalse(isset($linkDB[1]['linkdate']));
359 $this->assertEquals(1, $linkDB[1]['id']);
360 $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
361 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']);
362
363 $this->assertTrue(isset($linkDB[2]));
364 $this->assertFalse(isset($linkDB[2]['linkdate']));
365 $this->assertEquals(2, $linkDB[2]['id']);
366 $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
367 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']);
368 $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']);
369 }
370
371 /**
372 * Test updateMethodDatastoreIds() with the update already applied: nothing to do.
373 */
374 public function testDatastoreIdsNothingToDo()
375 {
376 $refDB = new ReferenceLinkDB();
377 $refDB->write(self::$testDatastore);
378 $linkDB = new LinkDB(self::$testDatastore, true, false);
379
380 $this->conf->set('resource.data_dir', 'sandbox');
381 $this->conf->set('resource.datastore', self::$testDatastore);
382
383 $checksum = hash_file('sha1', self::$testDatastore);
384 $updater = new Updater(array(), $linkDB, $this->conf, true);
385 $this->assertTrue($updater->updateMethodDatastoreIds());
386 $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore));
387 }
388
389 /**
390 * Test updateMethodEscapeMarkdown with markdown plugin enabled
391 * => setting markdown_escape set to false.
392 */
393 public function testEscapeMarkdownSettingToFalse()
394 {
395 $sandboxConf = 'sandbox/config';
396 copy(self::$configFile . '.json.php', $sandboxConf . '.json.php');
397 $this->conf = new ConfigManager($sandboxConf);
398
399 $this->conf->set('general.enabled_plugins', array('markdown'));
400 $updater = new Updater(array(), array(), $this->conf, true);
401 $this->assertTrue($updater->updateMethodEscapeMarkdown());
402 $this->assertFalse($this->conf->get('security.markdown_escape'));
403
404 // reload from file
405 $this->conf = new ConfigManager($sandboxConf);
406 $this->assertFalse($this->conf->get('security.markdown_escape'));
407 }
408
409 /**
410 * Test updateMethodEscapeMarkdown with markdown plugin disabled
411 * => setting markdown_escape set to true.
412 */
413 public function testEscapeMarkdownSettingToTrue()
414 {
415 $sandboxConf = 'sandbox/config';
416 copy(self::$configFile . '.json.php', $sandboxConf . '.json.php');
417 $this->conf = new ConfigManager($sandboxConf);
418
419 $this->conf->set('general.enabled_plugins', array());
420 $updater = new Updater(array(), array(), $this->conf, true);
421 $this->assertTrue($updater->updateMethodEscapeMarkdown());
422 $this->assertTrue($this->conf->get('security.markdown_escape'));
423
424 // reload from file
425 $this->conf = new ConfigManager($sandboxConf);
426 $this->assertTrue($this->conf->get('security.markdown_escape'));
427 }
428
429 /**
430 * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled)
431 */
432 public function testEscapeMarkdownSettingNothingToDoEnabled()
433 {
434 $sandboxConf = 'sandbox/config';
435 copy(self::$configFile . '.json.php', $sandboxConf . '.json.php');
436 $this->conf = new ConfigManager($sandboxConf);
437 $this->conf->set('security.markdown_escape', true);
438 $updater = new Updater(array(), array(), $this->conf, true);
439 $this->assertTrue($updater->updateMethodEscapeMarkdown());
440 $this->assertTrue($this->conf->get('security.markdown_escape'));
441 }
442
443 /**
444 * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled)
445 */
446 public function testEscapeMarkdownSettingNothingToDoDisabled()
447 {
448 $this->conf->set('security.markdown_escape', false);
449 $updater = new Updater(array(), array(), $this->conf, true);
450 $this->assertTrue($updater->updateMethodEscapeMarkdown());
451 $this->assertFalse($this->conf->get('security.markdown_escape'));
452 }
244} 453}
diff --git a/tests/Url/UrlTest.php b/tests/Url/UrlTest.php
index ce82265e..05862372 100644
--- a/tests/Url/UrlTest.php
+++ b/tests/Url/UrlTest.php
@@ -85,6 +85,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
85 $this->assertUrlIsCleaned('?utm_term=1n4l'); 85 $this->assertUrlIsCleaned('?utm_term=1n4l');
86 86
87 $this->assertUrlIsCleaned('?xtor=some-url'); 87 $this->assertUrlIsCleaned('?xtor=some-url');
88 $this->assertUrlIsCleaned('?PHPSESSID=012345678910111213');
88 } 89 }
89 90
90 /** 91 /**
@@ -183,9 +184,9 @@ class UrlTest extends PHPUnit_Framework_TestCase
183 } 184 }
184 185
185 /** 186 /**
186 * Test IndToAscii. 187 * Test International Domain Name to ASCII conversion
187 */ 188 */
188 function testIndToAscii() 189 function testIdnToAscii()
189 { 190 {
190 $ind = 'http://www.académie-française.fr/'; 191 $ind = 'http://www.académie-française.fr/';
191 $expected = 'http://www.xn--acadmie-franaise-npb1a.fr/'; 192 $expected = 'http://www.xn--acadmie-franaise-npb1a.fr/';
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index 3073b5eb..6a7870c4 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -253,41 +253,4 @@ class UtilsTest extends PHPUnit_Framework_TestCase
253 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') 253 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
254 ); 254 );
255 } 255 }
256
257 /**
258 * Test text2clickable without a redirector being set.
259 */
260 public function testText2clickableWithoutRedirector()
261 {
262 $text = 'stuff http://hello.there/is=someone#here otherstuff';
263 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
264 $processedText = text2clickable($text, '');
265 $this->assertEquals($expectedText, $processedText);
266 }
267
268 /**
269 * Test text2clickable a redirector set.
270 */
271 public function testText2clickableWithRedirector()
272 {
273 $text = 'stuff http://hello.there/is=someone#here otherstuff';
274 $redirector = 'http://redirector.to';
275 $expectedText = 'stuff <a href="'.
276 $redirector .
277 urlencode('http://hello.there/is=someone#here') .
278 '">http://hello.there/is=someone#here</a> otherstuff';
279 $processedText = text2clickable($text, $redirector);
280 $this->assertEquals($expectedText, $processedText);
281 }
282
283 /**
284 * Test testSpace2nbsp.
285 */
286 public function testSpace2nbsp()
287 {
288 $text = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
289 $expectedText = '&nbsp; Are you &nbsp; thrilled &nbsp;by flags &nbsp; ?'. PHP_EOL .'&nbsp;Really?';
290 $processedText = space2nbsp($text);
291 $this->assertEquals($expectedText, $processedText);
292 }
293} 256}
diff --git a/tests/config/ConfigJsonTest.php b/tests/config/ConfigJsonTest.php
new file mode 100644
index 00000000..07f6ab49
--- /dev/null
+++ b/tests/config/ConfigJsonTest.php
@@ -0,0 +1,133 @@
1<?php
2
3require_once 'application/config/ConfigJson.php';
4
5/**
6 * Class ConfigJsonTest
7 */
8class ConfigJsonTest extends PHPUnit_Framework_TestCase
9{
10 /**
11 * @var ConfigJson
12 */
13 protected $configIO;
14
15 public function setUp()
16 {
17 $this->configIO = new ConfigJson();
18 }
19
20 /**
21 * Read a simple existing config file.
22 */
23 public function testRead()
24 {
25 $conf = $this->configIO->read('tests/utils/config/configJson.json.php');
26 $this->assertEquals('root', $conf['credentials']['login']);
27 $this->assertEquals('lala', $conf['redirector']['url']);
28 $this->assertEquals('tests/utils/config/datastore.php', $conf['resource']['datastore']);
29 $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
30 }
31
32 /**
33 * Read a non existent config file -> empty array.
34 */
35 public function testReadNonExistent()
36 {
37 $this->assertEquals(array(), $this->configIO->read('nope'));
38 }
39
40 /**
41 * Read a non existent config file -> empty array.
42 *
43 * @expectedException Exception
44 * @expectedExceptionMessage An error occurred while parsing JSON file: error code #4
45 */
46 public function testReadInvalidJson()
47 {
48 $this->configIO->read('tests/utils/config/configInvalid.json.php');
49 }
50
51 /**
52 * Write a new config file.
53 */
54 public function testWriteNew()
55 {
56 $dataFile = 'tests/utils/config/configWrite.json.php';
57 $data = array(
58 'credentials' => array(
59 'login' => 'root',
60 ),
61 'resource' => array(
62 'datastore' => 'data/datastore.php',
63 ),
64 'redirector' => array(
65 'url' => 'lala',
66 ),
67 'plugins' => array(
68 'WALLABAG_VERSION' => '1',
69 )
70 );
71 $this->configIO->write($dataFile, $data);
72 // PHP 5.3 doesn't support json pretty print.
73 if (defined('JSON_PRETTY_PRINT')) {
74 $expected = '{
75 "credentials": {
76 "login": "root"
77 },
78 "resource": {
79 "datastore": "data\/datastore.php"
80 },
81 "redirector": {
82 "url": "lala"
83 },
84 "plugins": {
85 "WALLABAG_VERSION": "1"
86 }
87}';
88 } else {
89 $expected = '{"credentials":{"login":"root"},"resource":{"datastore":"data\/datastore.php"},"redirector":{"url":"lala"},"plugins":{"WALLABAG_VERSION":"1"}}';
90 }
91 $expected = ConfigJson::getPhpHeaders() . $expected . ConfigJson::getPhpSuffix();
92 $this->assertEquals($expected, file_get_contents($dataFile));
93 unlink($dataFile);
94 }
95
96 /**
97 * Overwrite an existing setting.
98 */
99 public function testOverwrite()
100 {
101 $source = 'tests/utils/config/configJson.json.php';
102 $dest = 'tests/utils/config/configOverwrite.json.php';
103 copy($source, $dest);
104 $conf = $this->configIO->read($dest);
105 $conf['redirector']['url'] = 'blabla';
106 $this->configIO->write($dest, $conf);
107 $conf = $this->configIO->read($dest);
108 $this->assertEquals('blabla', $conf['redirector']['url']);
109 unlink($dest);
110 }
111
112 /**
113 * Write to invalid path.
114 *
115 * @expectedException IOException
116 */
117 public function testWriteInvalidArray()
118 {
119 $conf = array('conf' => 'value');
120 @$this->configIO->write(array(), $conf);
121 }
122
123 /**
124 * Write to invalid path.
125 *
126 * @expectedException IOException
127 */
128 public function testWriteInvalidBlank()
129 {
130 $conf = array('conf' => 'value');
131 @$this->configIO->write('', $conf);
132 }
133}
diff --git a/tests/config/ConfigManagerTest.php b/tests/config/ConfigManagerTest.php
new file mode 100644
index 00000000..436e3d67
--- /dev/null
+++ b/tests/config/ConfigManagerTest.php
@@ -0,0 +1,172 @@
1<?php
2
3/**
4 * Unit tests for Class ConfigManagerTest
5 *
6 * Note: it only test the manager with ConfigJson,
7 * ConfigPhp is only a workaround to handle the transition to JSON type.
8 */
9class ConfigManagerTest extends PHPUnit_Framework_TestCase
10{
11 /**
12 * @var ConfigManager
13 */
14 protected $conf;
15
16 public function setUp()
17 {
18 $this->conf = new ConfigManager('tests/utils/config/configJson');
19 }
20
21 /**
22 * Simple config test:
23 * 1. Set settings.
24 * 2. Check settings value.
25 */
26 public function testSetGet()
27 {
28 $this->conf->set('paramInt', 42);
29 $this->conf->set('paramString', 'value1');
30 $this->conf->set('paramBool', false);
31 $this->conf->set('paramArray', array('foo' => 'bar'));
32 $this->conf->set('paramNull', null);
33
34 $this->assertEquals(42, $this->conf->get('paramInt'));
35 $this->assertEquals('value1', $this->conf->get('paramString'));
36 $this->assertFalse($this->conf->get('paramBool'));
37 $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
38 $this->assertEquals(null, $this->conf->get('paramNull'));
39 }
40
41 /**
42 * Set/write/get config test:
43 * 1. Set settings.
44 * 2. Write it to the config file.
45 * 3. Read the file.
46 * 4. Check settings value.
47 */
48 public function testSetWriteGet()
49 {
50 $this->conf->set('paramInt', 42);
51 $this->conf->set('paramString', 'value1');
52 $this->conf->set('paramBool', false);
53 $this->conf->set('paramArray', array('foo' => 'bar'));
54 $this->conf->set('paramNull', null);
55
56 $this->conf->setConfigFile('tests/utils/config/configTmp');
57 $this->conf->write(true);
58 $this->conf->reload();
59 unlink($this->conf->getConfigFileExt());
60
61 $this->assertEquals(42, $this->conf->get('paramInt'));
62 $this->assertEquals('value1', $this->conf->get('paramString'));
63 $this->assertFalse($this->conf->get('paramBool'));
64 $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
65 $this->assertEquals(null, $this->conf->get('paramNull'));
66 }
67
68 /**
69 * Test set/write/get with nested keys.
70 */
71 public function testSetWriteGetNested()
72 {
73 $this->conf->set('foo.bar.key.stuff', 'testSetWriteGetNested');
74
75 $this->conf->setConfigFile('tests/utils/config/configTmp');
76 $this->conf->write(true);
77 $this->conf->reload();
78 unlink($this->conf->getConfigFileExt());
79
80 $this->assertEquals('testSetWriteGetNested', $this->conf->get('foo.bar.key.stuff'));
81 }
82
83 /**
84 * Set with an empty key.
85 *
86 * @expectedException Exception
87 * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
88 */
89 public function testSetEmptyKey()
90 {
91 $this->conf->set('', 'stuff');
92 }
93
94 /**
95 * Set with an array key.
96 *
97 * @expectedException Exception
98 * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
99 */
100 public function testSetArrayKey()
101 {
102 $this->conf->set(array('foo' => 'bar'), 'stuff');
103 }
104
105 /**
106 * Try to write the config without mandatory parameter (e.g. 'login').
107 *
108 * @expectedException MissingFieldConfigException
109 */
110 public function testWriteMissingParameter()
111 {
112 $this->conf->setConfigFile('tests/utils/config/configTmp');
113 $this->assertFalse(file_exists($this->conf->getConfigFileExt()));
114 $this->conf->reload();
115
116 $this->conf->write(true);
117 }
118
119 /**
120 * Try to get non existent config keys.
121 */
122 public function testGetNonExistent()
123 {
124 $this->assertEquals('', $this->conf->get('nope.test'));
125 $this->assertEquals('default', $this->conf->get('nope.test', 'default'));
126 }
127
128 /**
129 * Test the 'exists' method with existent values.
130 */
131 public function testExistsOk()
132 {
133 $this->assertTrue($this->conf->exists('credentials.login'));
134 $this->assertTrue($this->conf->exists('config.foo'));
135 }
136
137 /**
138 * Test the 'exists' method with non existent or invalid values.
139 */
140 public function testExistsKo()
141 {
142 $this->assertFalse($this->conf->exists('nope'));
143 $this->assertFalse($this->conf->exists('nope.nope'));
144 $this->assertFalse($this->conf->exists(''));
145 $this->assertFalse($this->conf->exists(false));
146 }
147
148 /**
149 * Reset the ConfigManager instance.
150 */
151 public function testReset()
152 {
153 $confIO = $this->conf->getConfigIO();
154 $this->conf->reset();
155 $this->assertFalse($confIO === $this->conf->getConfigIO());
156 }
157
158 /**
159 * Reload the config from file.
160 */
161 public function testReload()
162 {
163 $this->conf->setConfigFile('tests/utils/config/configTmp');
164 $newConf = ConfigJson::getPhpHeaders() . '{ "key": "value" }';
165 file_put_contents($this->conf->getConfigFileExt(), $newConf);
166 $this->conf->reload();
167 unlink($this->conf->getConfigFileExt());
168 // Previous conf no longer exists, and new values have been loaded.
169 $this->assertFalse($this->conf->exists('credentials.login'));
170 $this->assertEquals('value', $this->conf->get('key'));
171 }
172}
diff --git a/tests/config/ConfigPhpTest.php b/tests/config/ConfigPhpTest.php
new file mode 100644
index 00000000..58cd8d2a
--- /dev/null
+++ b/tests/config/ConfigPhpTest.php
@@ -0,0 +1,82 @@
1<?php
2
3require_once 'application/config/ConfigPhp.php';
4
5/**
6 * Class ConfigPhpTest
7 */
8class ConfigPhpTest extends PHPUnit_Framework_TestCase
9{
10 /**
11 * @var ConfigPhp
12 */
13 protected $configIO;
14
15 public function setUp()
16 {
17 $this->configIO = new ConfigPhp();
18 }
19
20 /**
21 * Read a simple existing config file.
22 */
23 public function testRead()
24 {
25 $conf = $this->configIO->read('tests/utils/config/configPhp.php');
26 $this->assertEquals('root', $conf['login']);
27 $this->assertEquals('lala', $conf['redirector']);
28 $this->assertEquals('data/datastore.php', $conf['config']['DATASTORE']);
29 $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
30 }
31
32 /**
33 * Read a non existent config file -> empty array.
34 */
35 public function testReadNonExistent()
36 {
37 $this->assertEquals(array(), $this->configIO->read('nope'));
38 }
39
40 /**
41 * Write a new config file.
42 */
43 public function testWriteNew()
44 {
45 $dataFile = 'tests/utils/config/configWrite.php';
46 $data = array(
47 'login' => 'root',
48 'redirector' => 'lala',
49 'config' => array(
50 'DATASTORE' => 'data/datastore.php',
51 ),
52 'plugins' => array(
53 'WALLABAG_VERSION' => '1',
54 )
55 );
56 $this->configIO->write($dataFile, $data);
57 $expected = '<?php
58$GLOBALS[\'login\'] = \'root\';
59$GLOBALS[\'redirector\'] = \'lala\';
60$GLOBALS[\'config\'][\'DATASTORE\'] = \'data/datastore.php\';
61$GLOBALS[\'plugins\'][\'WALLABAG_VERSION\'] = \'1\';
62';
63 $this->assertEquals($expected, file_get_contents($dataFile));
64 unlink($dataFile);
65 }
66
67 /**
68 * Overwrite an existing setting.
69 */
70 public function testOverwrite()
71 {
72 $source = 'tests/utils/config/configPhp.php';
73 $dest = 'tests/utils/config/configOverwrite.php';
74 copy($source, $dest);
75 $conf = $this->configIO->read($dest);
76 $conf['redirector'] = 'blabla';
77 $this->configIO->write($dest, $conf);
78 $conf = $this->configIO->read($dest);
79 $this->assertEquals('blabla', $conf['redirector']);
80 unlink($dest);
81 }
82}
diff --git a/tests/config/ConfigPluginTest.php b/tests/config/ConfigPluginTest.php
new file mode 100644
index 00000000..3b37cd79
--- /dev/null
+++ b/tests/config/ConfigPluginTest.php
@@ -0,0 +1,121 @@
1<?php
2/**
3 * Config' tests
4 */
5
6require_once 'application/config/ConfigPlugin.php';
7
8/**
9 * Unitary tests for Shaarli config related functions
10 */
11class ConfigPluginTest extends PHPUnit_Framework_TestCase
12{
13 /**
14 * Test save_plugin_config with valid data.
15 *
16 * @throws PluginConfigOrderException
17 */
18 public function testSavePluginConfigValid()
19 {
20 $data = array(
21 'order_plugin1' => 2, // no plugin related
22 'plugin2' => 0, // new - at the end
23 'plugin3' => 0, // 2nd
24 'order_plugin3' => 8,
25 'plugin4' => 0, // 1st
26 'order_plugin4' => 5,
27 );
28
29 $expected = array(
30 'plugin3',
31 'plugin4',
32 'plugin2',
33 );
34
35 $out = save_plugin_config($data);
36 $this->assertEquals($expected, $out);
37 }
38
39 /**
40 * Test save_plugin_config with invalid data.
41 *
42 * @expectedException PluginConfigOrderException
43 */
44 public function testSavePluginConfigInvalid()
45 {
46 $data = array(
47 'plugin2' => 0,
48 'plugin3' => 0,
49 'order_plugin3' => 0,
50 'plugin4' => 0,
51 'order_plugin4' => 0,
52 );
53
54 save_plugin_config($data);
55 }
56
57 /**
58 * Test save_plugin_config without data.
59 */
60 public function testSavePluginConfigEmpty()
61 {
62 $this->assertEquals(array(), save_plugin_config(array()));
63 }
64
65 /**
66 * Test validate_plugin_order with valid data.
67 */
68 public function testValidatePluginOrderValid()
69 {
70 $data = array(
71 'order_plugin1' => 2,
72 'plugin2' => 0,
73 'plugin3' => 0,
74 'order_plugin3' => 1,
75 'plugin4' => 0,
76 'order_plugin4' => 5,
77 );
78
79 $this->assertTrue(validate_plugin_order($data));
80 }
81
82 /**
83 * Test validate_plugin_order with invalid data.
84 */
85 public function testValidatePluginOrderInvalid()
86 {
87 $data = array(
88 'order_plugin1' => 2,
89 'order_plugin3' => 1,
90 'order_plugin4' => 1,
91 );
92
93 $this->assertFalse(validate_plugin_order($data));
94 }
95
96 /**
97 * Test load_plugin_parameter_values.
98 */
99 public function testLoadPluginParameterValues()
100 {
101 $plugins = array(
102 'plugin_name' => array(
103 'parameters' => array(
104 'param1' => array('value' => true),
105 'param2' => array('value' => false),
106 'param3' => array('value' => ''),
107 )
108 )
109 );
110
111 $parameters = array(
112 'param1' => 'value1',
113 'param2' => 'value2',
114 );
115
116 $result = load_plugin_parameter_values($plugins, $parameters);
117 $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']['value']);
118 $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']['value']);
119 $this->assertEquals('', $result['plugin_name']['parameters']['param3']['value']);
120 }
121}
diff --git a/tests/plugins/PluginArchiveorgTest.php b/tests/plugins/PluginArchiveorgTest.php
index dbc52bc8..4daa4c9d 100644
--- a/tests/plugins/PluginArchiveorgTest.php
+++ b/tests/plugins/PluginArchiveorgTest.php
@@ -7,8 +7,8 @@
7require_once 'plugins/archiveorg/archiveorg.php'; 7require_once 'plugins/archiveorg/archiveorg.php';
8 8
9/** 9/**
10 * Class PlugQrcodeTest 10 * Class PluginArchiveorgTest
11 * Unit test for the QR-Code plugin 11 * Unit test for the archiveorg plugin
12 */ 12 */
13class PluginArchiveorgTest extends PHPUnit_Framework_TestCase 13class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
14{ 14{
@@ -21,21 +21,25 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
21 } 21 }
22 22
23 /** 23 /**
24 * Test render_linklist hook. 24 * Test render_linklist hook on external links.
25 */ 25 */
26 function testArchiveorgLinklist() 26 function testArchiveorgLinklistOnExternalLinks()
27 { 27 {
28 $str = 'http://randomstr.com/test'; 28 $str = 'http://randomstr.com/test';
29
29 $data = array( 30 $data = array(
30 'title' => $str, 31 'title' => $str,
31 'links' => array( 32 'links' => array(
32 array( 33 array(
33 'url' => $str, 34 'url' => $str,
35 'private' => 0,
36 'real_url' => $str
34 ) 37 )
35 ) 38 )
36 ); 39 );
37 40
38 $data = hook_archiveorg_render_linklist($data); 41 $data = hook_archiveorg_render_linklist($data);
42
39 $link = $data['links'][0]; 43 $link = $data['links'][0];
40 // data shouldn't be altered 44 // data shouldn't be altered
41 $this->assertEquals($str, $data['title']); 45 $this->assertEquals($str, $data['title']);
@@ -44,5 +48,95 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
44 // plugin data 48 // plugin data
45 $this->assertEquals(1, count($link['link_plugin'])); 49 $this->assertEquals(1, count($link['link_plugin']));
46 $this->assertNotFalse(strpos($link['link_plugin'][0], $str)); 50 $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
51
52 }
53
54 /**
55 * Test render_linklist hook on internal links.
56 */
57 function testArchiveorgLinklistOnInternalLinks()
58 {
59 $internalLink1 = 'http://shaarli.shaarli/?qvMAqg';
60 $internalLinkRealURL1 = '?qvMAqg';
61
62 $internalLink2 = 'http://shaarli.shaarli/?2_7zww';
63 $internalLinkRealURL2 = '?2_7zww';
64
65 $internalLink3 = 'http://shaarli.shaarli/?z7u-_Q';
66 $internalLinkRealURL3 = '?z7u-_Q';
67
68 $data = array(
69 'title' => $internalLink1,
70 'links' => array(
71 array(
72 'url' => $internalLink1,
73 'private' => 0,
74 'real_url' => $internalLinkRealURL1
75 ),
76 array(
77 'url' => $internalLink1,
78 'private' => 1,
79 'real_url' => $internalLinkRealURL1
80 ),
81 array(
82 'url' => $internalLink2,
83 'private' => 0,
84 'real_url' => $internalLinkRealURL2
85 ),
86 array(
87 'url' => $internalLink2,
88 'private' => 1,
89 'real_url' => $internalLinkRealURL2
90 ),
91 array(
92 'url' => $internalLink3,
93 'private' => 0,
94 'real_url' => $internalLinkRealURL3
95 ),
96 array(
97 'url' => $internalLink3,
98 'private' => 1,
99 'real_url' => $internalLinkRealURL3
100 )
101 )
102 );
103
104
105 $data = hook_archiveorg_render_linklist($data);
106
107 // Case n°1: first link type, public
108 $link = $data['links'][0];
109
110 $this->assertEquals(1, count($link['link_plugin']));
111 $this->assertNotFalse(strpos($link['link_plugin'][0], $internalLink1));
112
113 // Case n°2: first link type, private
114 $link = $data['links'][1];
115
116 $this->assertArrayNotHasKey('link_plugin', $link);
117
118 // Case n°3: second link type, public
119 $link = $data['links'][2];
120
121 $this->assertEquals(1, count($link['link_plugin']));
122 $this->assertNotFalse(strpos($link['link_plugin'][0], $internalLink2));
123
124 // Case n°4: second link type, private
125 $link = $data['links'][3];
126
127 $this->assertArrayNotHasKey('link_plugin', $link);
128
129 // Case n°5: third link type, public
130 $link = $data['links'][4];
131
132 $this->assertEquals(1, count($link['link_plugin']));
133 $this->assertNotFalse(strpos($link['link_plugin'][0], $internalLink3));
134
135 // Case n°6: third link type, private
136 $link = $data['links'][5];
137
138 $this->assertArrayNotHasKey('link_plugin', $link);
139
47 } 140 }
141
48} 142}
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php
new file mode 100644
index 00000000..6b7904dd
--- /dev/null
+++ b/tests/plugins/PluginIssoTest.php
@@ -0,0 +1,151 @@
1<?php
2
3require_once 'plugins/isso/isso.php';
4
5/**
6 * Class PluginIssoTest
7 *
8 * Test the Isso plugin (comment system).
9 */
10class PluginIssoTest extends PHPUnit_Framework_TestCase
11{
12 /**
13 * Reset plugin path
14 */
15 function setUp()
16 {
17 PluginManager::$PLUGINS_PATH = 'plugins';
18 }
19
20 /**
21 * Test Isso init without errors.
22 */
23 function testWallabagInitNoError()
24 {
25 $conf = new ConfigManager('');
26 $conf->set('plugins.ISSO_SERVER', 'value');
27 $errors = isso_init($conf);
28 $this->assertEmpty($errors);
29 }
30
31 /**
32 * Test Isso init with errors.
33 */
34 function testWallabagInitError()
35 {
36 $conf = new ConfigManager('');
37 $errors = isso_init($conf);
38 $this->assertNotEmpty($errors);
39 }
40
41 /**
42 * Test render_linklist hook with valid settings to display the comment form.
43 */
44 function testIssoDisplayed()
45 {
46 $conf = new ConfigManager('');
47 $conf->set('plugins.ISSO_SERVER', 'value');
48
49 $str = 'http://randomstr.com/test';
50 $date = '20161118_100001';
51 $data = array(
52 'title' => $str,
53 'links' => array(
54 array(
55 'id' => 12,
56 'url' => $str,
57 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
58 )
59 )
60 );
61
62 $data = hook_isso_render_linklist($data, $conf);
63
64 // data shouldn't be altered
65 $this->assertEquals($str, $data['title']);
66 $this->assertEquals($str, $data['links'][0]['url']);
67
68 // plugin data
69 $this->assertEquals(1, count($data['plugin_end_zone']));
70 $this->assertNotFalse(strpos(
71 $data['plugin_end_zone'][0],
72 'data-isso-id="'. $data['links'][0]['id'] .'"'
73 ));
74 $this->assertNotFalse(strpos(
75 $data['plugin_end_zone'][0],
76 'data-title="'. $data['links'][0]['id'] .'"'
77 ));
78 $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
79 }
80
81 /**
82 * Test isso plugin when multiple links are displayed (shouldn't be displayed).
83 */
84 function testIssoMultipleLinks()
85 {
86 $conf = new ConfigManager('');
87 $conf->set('plugins.ISSO_SERVER', 'value');
88
89 $str = 'http://randomstr.com/test';
90 $date1 = '20161118_100001';
91 $date2 = '20161118_100002';
92 $data = array(
93 'title' => $str,
94 'links' => array(
95 array(
96 'id' => 12,
97 'url' => $str,
98 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
99 ),
100 array(
101 'id' => 13,
102 'url' => $str . '2',
103 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
104 ),
105 )
106 );
107
108 $processed = hook_isso_render_linklist($data, $conf);
109 // data shouldn't be altered
110 $this->assertEquals($data, $processed);
111 }
112
113 /**
114 * Test isso plugin when using search (shouldn't be displayed).
115 */
116 function testIssoNotDisplayedWhenSearch()
117 {
118 $conf = new ConfigManager('');
119 $conf->set('plugins.ISSO_SERVER', 'value');
120
121 $str = 'http://randomstr.com/test';
122 $date = '20161118_100001';
123 $data = array(
124 'title' => $str,
125 'links' => array(
126 array(
127 'id' => 12,
128 'url' => $str,
129 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
130 )
131 ),
132 'search_term' => $str
133 );
134
135 $processed = hook_isso_render_linklist($data, $conf);
136
137 // data shouldn't be altered
138 $this->assertEquals($data, $processed);
139 }
140
141 /**
142 * Test isso plugin without server configuration (shouldn't be displayed).
143 */
144 function testIssoWithoutConf()
145 {
146 $data = 'abc';
147 $conf = new ConfigManager('');
148 $processed = hook_isso_render_linklist($data, $conf);
149 $this->assertEquals($data, $processed);
150 }
151}
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index fa7e1d52..f1e1acf8 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -8,17 +8,23 @@ require_once 'application/Utils.php';
8require_once 'plugins/markdown/markdown.php'; 8require_once 'plugins/markdown/markdown.php';
9 9
10/** 10/**
11 * Class PlugQrcodeTest 11 * Class PluginMarkdownTest
12 * Unit test for the QR-Code plugin 12 * Unit test for the Markdown plugin
13 */ 13 */
14class PluginMarkdownTest extends PHPUnit_Framework_TestCase 14class PluginMarkdownTest extends PHPUnit_Framework_TestCase
15{ 15{
16 /** 16 /**
17 * @var ConfigManager instance.
18 */
19 protected $conf;
20
21 /**
17 * Reset plugin path 22 * Reset plugin path
18 */ 23 */
19 function setUp() 24 function setUp()
20 { 25 {
21 PluginManager::$PLUGINS_PATH = 'plugins'; 26 PluginManager::$PLUGINS_PATH = 'plugins';
27 $this->conf = new ConfigManager('tests/utils/config/configJson');
22 } 28 }
23 29
24 /** 30 /**
@@ -36,7 +42,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
36 ), 42 ),
37 ); 43 );
38 44
39 $data = hook_markdown_render_linklist($data); 45 $data = hook_markdown_render_linklist($data, $this->conf);
40 $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>')); 46 $this->assertNotFalse(strpos($data['links'][0]['description'], '<h1>'));
41 $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>')); 47 $this->assertNotFalse(strpos($data['links'][0]['description'], '<p>'));
42 } 48 }
@@ -61,7 +67,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
61 ), 67 ),
62 ); 68 );
63 69
64 $data = hook_markdown_render_daily($data); 70 $data = hook_markdown_render_daily($data, $this->conf);
65 $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '<h1>')); 71 $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '<h1>'));
66 $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '<p>')); 72 $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '<p>'));
67 } 73 }
@@ -110,6 +116,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
110 $output = escape($input); 116 $output = escape($input);
111 $input .= '<a href="#" onmouseHover="alert(\'xss\');" attr="tt">link</a>'; 117 $input .= '<a href="#" onmouseHover="alert(\'xss\');" attr="tt">link</a>';
112 $output .= '<a href="#" attr="tt">link</a>'; 118 $output .= '<a href="#" attr="tt">link</a>';
119 $input .= '<a href="#" onmouseHover=alert(\'xss\'); attr="tt">link</a>';
120 $output .= '<a href="#" attr="tt">link</a>';
113 $this->assertEquals($output, sanitize_html($input)); 121 $this->assertEquals($output, sanitize_html($input));
114 // Do not touch escaped HTML. 122 // Do not touch escaped HTML.
115 $input = escape($input); 123 $input = escape($input);
@@ -125,12 +133,16 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
125 $data = array( 133 $data = array(
126 'links' => array(array( 134 'links' => array(array(
127 'description' => $str, 135 'description' => $str,
128 'tags' => NO_MD_TAG 136 'tags' => NO_MD_TAG,
137 'taglist' => array(NO_MD_TAG),
129 )) 138 ))
130 ); 139 );
131 140
132 $data = hook_markdown_render_linklist($data); 141 $processed = hook_markdown_render_linklist($data, $this->conf);
133 $this->assertEquals($str, $data['links'][0]['description']); 142 $this->assertEquals($str, $processed['links'][0]['description']);
143
144 $processed = hook_markdown_render_feed($data, $this->conf);
145 $this->assertEquals($str, $processed['links'][0]['description']);
134 146
135 $data = array( 147 $data = array(
136 // Columns data 148 // Columns data
@@ -140,13 +152,82 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
140 // nth link 152 // nth link
141 0 => array( 153 0 => array(
142 'formatedDescription' => $str, 154 'formatedDescription' => $str,
143 'tags' => NO_MD_TAG 155 'tags' => NO_MD_TAG,
156 'taglist' => array(),
144 ), 157 ),
145 ), 158 ),
146 ), 159 ),
147 ); 160 );
148 161
149 $data = hook_markdown_render_daily($data); 162 $data = hook_markdown_render_daily($data, $this->conf);
150 $this->assertEquals($str, $data['cols'][0][0]['formatedDescription']); 163 $this->assertEquals($str, $data['cols'][0][0]['formatedDescription']);
151 } 164 }
165
166 /**
167 * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`).
168 */
169 function testNoMarkdownNotExcactlyMatching()
170 {
171 $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
172 $data = array(
173 'links' => array(array(
174 'description' => $str,
175 'tags' => '.' . NO_MD_TAG,
176 'taglist' => array('.'. NO_MD_TAG),
177 ))
178 );
179
180 $data = hook_markdown_render_feed($data, $this->conf);
181 $this->assertContains('<em>', $data['links'][0]['description']);
182 }
183
184 /**
185 * Test hashtag links processed with markdown.
186 */
187 function testMarkdownHashtagLinks()
188 {
189 $md = file_get_contents('tests/plugins/resources/markdown.md');
190 $md = format_description($md);
191 $html = file_get_contents('tests/plugins/resources/markdown.html');
192
193 $data = process_markdown($md);
194 $this->assertEquals($html, $data);
195 }
196
197 /**
198 * Make sure that the HTML tags are escaped.
199 */
200 public function testMarkdownWithHtmlEscape()
201 {
202 $md = '**strong** <strong>strong</strong>';
203 $html = '<div class="markdown"><p><strong>strong</strong> &lt;strong&gt;strong&lt;/strong&gt;</p></div>';
204 $data = array(
205 'links' => array(
206 0 => array(
207 'description' => $md,
208 ),
209 ),
210 );
211 $data = hook_markdown_render_linklist($data, $this->conf);
212 $this->assertEquals($html, $data['links'][0]['description']);
213 }
214
215 /**
216 * Make sure that the HTML tags aren't escaped with the setting set to false.
217 */
218 public function testMarkdownWithHtmlNoEscape()
219 {
220 $this->conf->set('security.markdown_escape', false);
221 $md = '**strong** <strong>strong</strong>';
222 $html = '<div class="markdown"><p><strong>strong</strong> <strong>strong</strong></p></div>';
223 $data = array(
224 'links' => array(
225 0 => array(
226 'description' => $md,
227 ),
228 ),
229 );
230 $data = hook_markdown_render_linklist($data, $this->conf);
231 $this->assertEquals($html, $data['links'][0]['description']);
232 }
152} 233}
diff --git a/tests/plugins/PluginReadityourselfTest.php b/tests/plugins/PluginReadityourselfTest.php
index 8bf17bf1..532db146 100644
--- a/tests/plugins/PluginReadityourselfTest.php
+++ b/tests/plugins/PluginReadityourselfTest.php
@@ -21,11 +21,33 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
21 } 21 }
22 22
23 /** 23 /**
24 * Test Readityourself init without errors.
25 */
26 function testReadityourselfInitNoError()
27 {
28 $conf = new ConfigManager('');
29 $conf->set('plugins.READITYOUSELF_URL', 'value');
30 $errors = readityourself_init($conf);
31 $this->assertEmpty($errors);
32 }
33
34 /**
35 * Test Readityourself init with errors.
36 */
37 function testReadityourselfInitError()
38 {
39 $conf = new ConfigManager('');
40 $errors = readityourself_init($conf);
41 $this->assertNotEmpty($errors);
42 }
43
44 /**
24 * Test render_linklist hook. 45 * Test render_linklist hook.
25 */ 46 */
26 function testReadityourselfLinklist() 47 function testReadityourselfLinklist()
27 { 48 {
28 $GLOBALS['plugins']['READITYOUSELF_URL'] = 'value'; 49 $conf = new ConfigManager('');
50 $conf->set('plugins.READITYOUSELF_URL', 'value');
29 $str = 'http://randomstr.com/test'; 51 $str = 'http://randomstr.com/test';
30 $data = array( 52 $data = array(
31 'title' => $str, 53 'title' => $str,
@@ -36,7 +58,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
36 ) 58 )
37 ); 59 );
38 60
39 $data = hook_readityourself_render_linklist($data); 61 $data = hook_readityourself_render_linklist($data, $conf);
40 $link = $data['links'][0]; 62 $link = $data['links'][0];
41 // data shouldn't be altered 63 // data shouldn't be altered
42 $this->assertEquals($str, $data['title']); 64 $this->assertEquals($str, $data['title']);
@@ -52,7 +74,8 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
52 */ 74 */
53 function testReadityourselfLinklistWithoutConfig() 75 function testReadityourselfLinklistWithoutConfig()
54 { 76 {
55 unset($GLOBALS['plugins']['READITYOUSELF_URL']); 77 $conf = new ConfigManager('');
78 $conf->set('plugins.READITYOUSELF_URL', null);
56 $str = 'http://randomstr.com/test'; 79 $str = 'http://randomstr.com/test';
57 $data = array( 80 $data = array(
58 'title' => $str, 81 'title' => $str,
@@ -63,7 +86,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
63 ) 86 )
64 ); 87 );
65 88
66 $data = hook_readityourself_render_linklist($data); 89 $data = hook_readityourself_render_linklist($data, $conf);
67 $link = $data['links'][0]; 90 $link = $data['links'][0];
68 // data shouldn't be altered 91 // data shouldn't be altered
69 $this->assertEquals($str, $data['title']); 92 $this->assertEquals($str, $data['title']);
diff --git a/tests/plugins/PluginWallabagTest.php b/tests/plugins/PluginWallabagTest.php
index 5d3a60e0..2c268cbd 100644
--- a/tests/plugins/PluginWallabagTest.php
+++ b/tests/plugins/PluginWallabagTest.php
@@ -21,11 +21,33 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
21 } 21 }
22 22
23 /** 23 /**
24 * Test wallabag init without errors.
25 */
26 function testWallabagInitNoError()
27 {
28 $conf = new ConfigManager('');
29 $conf->set('plugins.WALLABAG_URL', 'value');
30 $errors = wallabag_init($conf);
31 $this->assertEmpty($errors);
32 }
33
34 /**
35 * Test wallabag init with errors.
36 */
37 function testWallabagInitError()
38 {
39 $conf = new ConfigManager('');
40 $errors = wallabag_init($conf);
41 $this->assertNotEmpty($errors);
42 }
43
44 /**
24 * Test render_linklist hook. 45 * Test render_linklist hook.
25 */ 46 */
26 function testWallabagLinklist() 47 function testWallabagLinklist()
27 { 48 {
28 $GLOBALS['plugins']['WALLABAG_URL'] = 'value'; 49 $conf = new ConfigManager('');
50 $conf->set('plugins.WALLABAG_URL', 'value');
29 $str = 'http://randomstr.com/test'; 51 $str = 'http://randomstr.com/test';
30 $data = array( 52 $data = array(
31 'title' => $str, 53 'title' => $str,
@@ -36,7 +58,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
36 ) 58 )
37 ); 59 );
38 60
39 $data = hook_wallabag_render_linklist($data); 61 $data = hook_wallabag_render_linklist($data, $conf);
40 $link = $data['links'][0]; 62 $link = $data['links'][0];
41 // data shouldn't be altered 63 // data shouldn't be altered
42 $this->assertEquals($str, $data['title']); 64 $this->assertEquals($str, $data['title']);
@@ -45,7 +67,6 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
45 // plugin data 67 // plugin data
46 $this->assertEquals(1, count($link['link_plugin'])); 68 $this->assertEquals(1, count($link['link_plugin']));
47 $this->assertNotFalse(strpos($link['link_plugin'][0], urlencode($str))); 69 $this->assertNotFalse(strpos($link['link_plugin'][0], urlencode($str)));
48 $this->assertNotFalse(strpos($link['link_plugin'][0], $GLOBALS['plugins']['WALLABAG_URL'])); 70 $this->assertNotFalse(strpos($link['link_plugin'][0], $conf->get('plugins.WALLABAG_URL')));
49 } 71 }
50} 72}
51
diff --git a/tests/plugins/resources/markdown.html b/tests/plugins/resources/markdown.html
new file mode 100644
index 00000000..07a5a32e
--- /dev/null
+++ b/tests/plugins/resources/markdown.html
@@ -0,0 +1,24 @@
1<div class="markdown"><ul>
2<li>test:
3<ul>
4<li><a href="http://link.tld">zero</a></li>
5<li><a href="http://link.tld">two</a></li>
6<li><a href="http://link.tld">three</a></li>
7</ul></li>
8</ul>
9<ol>
10<li><a href="http://link.tld">zero</a>
11<ol>
12<li><a href="http://link.tld">two</a></li>
13<li><a href="http://link.tld">three</a></li>
14<li><a href="http://link.tld">four</a></li>
15<li>foo &lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt;</li>
16</ol></li>
17</ol>
18<p>&lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt; foo <code>lol #foo</code> &lt;a href=&quot;?addtag=bar&quot; title=&quot;Hashtag bar&quot;&gt;#bar&lt;/a&gt;</p>
19<p>fsdfs <a href="http://link.tld">http://link.tld</a> &lt;a href=&quot;?addtag=foobar&quot; title=&quot;Hashtag foobar&quot;&gt;#foobar&lt;/a&gt; <code>http://link.tld</code></p>
20<pre><code>http://link.tld #foobar
21next #foo</code></pre>
22<p>Block:</p>
23<pre><code>lorem ipsum #foobar http://link.tld
24#foobar http://link.tld</code></pre></div> \ No newline at end of file
diff --git a/tests/plugins/resources/markdown.md b/tests/plugins/resources/markdown.md
new file mode 100644
index 00000000..0b8be7c5
--- /dev/null
+++ b/tests/plugins/resources/markdown.md
@@ -0,0 +1,24 @@
1* test:
2 * [zero](http://link.tld)
3 + [two](http://link.tld)
4 - [three](http://link.tld)
5
61. [zero](http://link.tld)
7 2. [two](http://link.tld)
8 3. [three](http://link.tld)
9 4. [four](http://link.tld)
10 5. foo #foobar
11
12#foobar foo `lol #foo` #bar
13
14fsdfs http://link.tld #foobar `http://link.tld`
15
16 http://link.tld #foobar
17 next #foo
18
19Block:
20
21```
22lorem ipsum #foobar http://link.tld
23#foobar http://link.tld
24``` \ No newline at end of file
diff --git a/tests/plugins/test/test.meta b/tests/plugins/test/test.meta
index ab999ed4..26f243f0 100644
--- a/tests/plugins/test/test.meta
+++ b/tests/plugins/test/test.meta
@@ -1,2 +1,4 @@
1description="test plugin" 1description="test plugin"
2parameters="pop;hip" \ No newline at end of file 2parameters="pop;hip"
3parameter.pop="pop description"
4parameter.hip= \ No newline at end of file
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index dc4f5dfa..36d58c68 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,7 +4,7 @@
4 */ 4 */
5class ReferenceLinkDB 5class ReferenceLinkDB
6{ 6{
7 public static $NB_LINKS_TOTAL = 7; 7 public static $NB_LINKS_TOTAL = 8;
8 8
9 private $_links = array(); 9 private $_links = array();
10 private $_publicCount = 0; 10 private $_publicCount = 0;
@@ -13,86 +13,111 @@ class ReferenceLinkDB
13 /** 13 /**
14 * Populates the test DB with reference data 14 * Populates the test DB with reference data
15 */ 15 */
16 function __construct() 16 public function __construct()
17 { 17 {
18 $this->addLink( 18 $this->addLink(
19 41,
19 'Link title: @website', 20 'Link title: @website',
20 '?WDWyig', 21 '?WDWyig',
21 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.', 22 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
22 0, 23 0,
23 '20150310_114651', 24 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
24 'stuff' 25 'sTuff',
26 null,
27 'WDWyig'
25 ); 28 );
26 29
27 $this->addLink( 30 $this->addLink(
31 42,
32 'Note: I have a big ID but an old date',
33 '?WDWyig',
34 'Used to test links reordering.',
35 0,
36 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'),
37 'ut'
38 );
39
40 $this->addLink(
41 8,
28 'Free as in Freedom 2.0 @website', 42 'Free as in Freedom 2.0 @website',
29 'https://static.fsf.org/nosvn/faif-2.0.pdf', 43 'https://static.fsf.org/nosvn/faif-2.0.pdf',
30 'Richard Stallman and the Free Software Revolution. Read this.', 44 'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
31 0, 45 0,
32 '20150310_114633', 46 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
33 'free gnu software stallman -exclude stuff' 47 'free gnu software stallman -exclude stuff hashtag',
48 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
34 ); 49 );
35 50
36 $this->addLink( 51 $this->addLink(
52 7,
37 'MediaGoblin', 53 'MediaGoblin',
38 'http://mediagoblin.org/', 54 'http://mediagoblin.org/',
39 'A free software media publishing platform', 55 'A free software media publishing platform #hashtagOther',
40 0, 56 0,
41 '20130614_184135', 57 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
42 'gnu media web .hidden' 58 'gnu media web .hidden hashtag',
59 null,
60 'IuWvgA'
43 ); 61 );
44 62
45 $this->addLink( 63 $this->addLink(
64 6,
46 'w3c-markup-validator', 65 'w3c-markup-validator',
47 'https://dvcs.w3.org/hg/markup-validator/summary', 66 'https://dvcs.w3.org/hg/markup-validator/summary',
48 'Mercurial repository for the W3C Validator', 67 'Mercurial repository for the W3C Validator #private',
49 1, 68 1,
50 '20141125_084734', 69 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
51 'css html w3c web Mercurial' 70 'css html w3c web Mercurial'
52 ); 71 );
53 72
54 $this->addLink( 73 $this->addLink(
74 4,
55 'UserFriendly - Web Designer', 75 'UserFriendly - Web Designer',
56 'http://ars.userfriendly.org/cartoons/?id=20121206', 76 'http://ars.userfriendly.org/cartoons/?id=20121206',
57 'Naming conventions...', 77 'Naming conventions... #private',
58 0, 78 0,
59 '20121206_142300', 79 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
60 'dev cartoon web' 80 'dev cartoon web'
61 ); 81 );
62 82
63 $this->addLink( 83 $this->addLink(
84 1,
64 'UserFriendly - Samba', 85 'UserFriendly - Samba',
65 'http://ars.userfriendly.org/cartoons/?id=20010306', 86 'http://ars.userfriendly.org/cartoons/?id=20010306',
66 'Tropical printing', 87 'Tropical printing',
67 0, 88 0,
68 '20121206_172539', 89 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
69 'samba cartoon web' 90 'samba cartoon web'
70 ); 91 );
71 92
72 $this->addLink( 93 $this->addLink(
94 0,
73 'Geek and Poke', 95 'Geek and Poke',
74 'http://geek-and-poke.com/', 96 'http://geek-and-poke.com/',
75 '', 97 '',
76 1, 98 1,
77 '20121206_182539', 99 DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
78 'dev cartoon' 100 'dev cartoon tag1 tag2 tag3 tag4 '
79 ); 101 );
80 } 102 }
81 103
82 /** 104 /**
83 * Adds a new link 105 * Adds a new link
84 */ 106 */
85 protected function addLink($title, $url, $description, $private, $date, $tags) 107 protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '')
86 { 108 {
87 $link = array( 109 $link = array(
110 'id' => $id,
88 'title' => $title, 111 'title' => $title,
89 'url' => $url, 112 'url' => $url,
90 'description' => $description, 113 'description' => $description,
91 'private' => $private, 114 'private' => $private,
92 'linkdate' => $date,
93 'tags' => $tags, 115 'tags' => $tags,
116 'created' => $date,
117 'updated' => $updated,
118 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
94 ); 119 );
95 $this->_links[$date] = $link; 120 $this->_links[$id] = $link;
96 121
97 if ($private) { 122 if ($private) {
98 $this->_privateCount++; 123 $this->_privateCount++;
@@ -140,4 +165,14 @@ class ReferenceLinkDB
140 { 165 {
141 return $this->_links; 166 return $this->_links;
142 } 167 }
168
169 /**
170 * Setter to override link creation.
171 *
172 * @param array $links List of links.
173 */
174 public function setLinks($links)
175 {
176 $this->_links = $links;
177 }
143} 178}
diff --git a/tests/utils/config/configInvalid.json.php b/tests/utils/config/configInvalid.json.php
new file mode 100644
index 00000000..e39d640a
--- /dev/null
+++ b/tests/utils/config/configInvalid.json.php
@@ -0,0 +1,5 @@
1<?php /*
2{
3 bad: bad,
4}
5*/ ?>
diff --git a/tests/utils/config/configJson.json.php b/tests/utils/config/configJson.json.php
new file mode 100644
index 00000000..06a302e8
--- /dev/null
+++ b/tests/utils/config/configJson.json.php
@@ -0,0 +1,34 @@
1<?php /*
2{
3 "credentials": {
4 "login":"root",
5 "hash":"hash",
6 "salt":"salt"
7 },
8 "security": {
9 "session_protection_disabled":false
10 },
11 "general": {
12 "timezone":"Europe\/Paris",
13 "title": "Shaarli",
14 "header_link": "?"
15 },
16 "privacy": {
17 "default_private_links":true
18 },
19 "redirector": {
20 "url":"lala"
21 },
22 "config": {
23 "foo": "bar"
24 },
25 "resource": {
26 "datastore": "tests\/utils\/config\/datastore.php",
27 "data_dir": "tests\/utils\/config"
28 },
29 "plugins": {
30 "WALLABAG_VERSION": 1
31 }
32}
33*/ ?>
34
diff --git a/tests/utils/config/configPhp.php b/tests/utils/config/configPhp.php
new file mode 100644
index 00000000..0e034175
--- /dev/null
+++ b/tests/utils/config/configPhp.php
@@ -0,0 +1,14 @@
1<?php
2$GLOBALS['login'] = 'root';
3$GLOBALS['hash'] = 'hash';
4$GLOBALS['salt'] = 'salt';
5$GLOBALS['timezone'] = 'Europe/Paris';
6$GLOBALS['title'] = 'title';
7$GLOBALS['titleLink'] = 'titleLink';
8$GLOBALS['redirector'] = 'lala';
9$GLOBALS['disablesessionprotection'] = false;
10$GLOBALS['privateLinkByDefault'] = false;
11$GLOBALS['config']['DATADIR'] = 'tests/Updater';
12$GLOBALS['config']['PAGECACHE'] = 'sandbox/pagecache';
13$GLOBALS['config']['DATASTORE'] = 'data/datastore.php';
14$GLOBALS['plugins']['WALLABAG_VERSION'] = '1';
diff --git a/tmp/.htaccess b/tmp/.htaccess
index b584d98c..f601c1ee 100644
--- a/tmp/.htaccess
+++ b/tmp/.htaccess
@@ -1,2 +1,13 @@
1Allow from none 1<IfModule version_module>
2Deny from all 2 <IfVersion >= 2.4>
3 Require all denied
4 </IfVersion>
5 <IfVersion < 2.4>
6 Allow from none
7 Deny from all
8 </IfVersion>
9</IfModule>
10
11<IfModule !version_module>
12 Require all denied
13</IfModule>
diff --git a/tpl/configure.html b/tpl/configure.html
index 77c8b7d9..983bcd08 100644
--- a/tpl/configure.html
+++ b/tpl/configure.html
@@ -3,48 +3,90 @@
3<head>{include="includes"}</head> 3<head>{include="includes"}</head>
4<body onload="document.configform.title.focus();"> 4<body onload="document.configform.title.focus();">
5<div id="pageheader"> 5<div id="pageheader">
6 {include="page.header"} 6 {include="page.header"}
7{$timezone_js} 7 {$timezone_js}
8 <form method="POST" action="#" name="configform" id="configform"> 8 <form method="POST" action="#" name="configform" id="configform">
9 <input type="hidden" name="token" value="{$token}"> 9 <input type="hidden" name="token" value="{$token}">
10 <table id="configuration_table"> 10 <table id="configuration_table">
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>
13 <td><b>Page title:</b></td>
14 <td><input type="text" name="title" id="title" size="50" value="{$title}"></td>
15 </tr>
13 16
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> 17 <tr>
15 <tr><td><b>Timezone:</b></td><td>{$timezone_form}</td></tr> 18 <td><b>Title link:</b></td>
19 <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label
20 for="titleLink">(default value is: ?)</label></td>
21 </tr>
22 <tr>
23 <td><b>Timezone:</b></td>
24 <td>{$timezone_form}</td>
25 </tr>
16 26
17 <tr><td><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> 27 <tr>
28 <td><b>Redirector</b></td>
29 <td>
30 <input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>
31 (e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)
32 </td>
33 </tr>
18 34
19 <tr><td><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> 35 <tr>
36 <td><b>Security:</b></td>
37 <td>
38 <input type="checkbox" name="disablesessionprotection" id="disablesessionprotection"
39 {if="$session_protection_disabled"}checked{/if}>
40 <label
41 for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get
42 disconnected often or if your IP address changes often.)</label>
43 </td>
44 </tr>
20 45
21 <tr><td valign="top"><b>New link:</b></td><td> 46 <tr>
22 <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault">&nbsp;All new links are private by default</label></td> 47 <td valign="top"><b>New link:</b></td>
23 </tr> 48 <td>
24 <tr> 49 <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault"
25 <td valign="top"><b>RSS direct links</b></td> 50 {if="$private_links_default"}checked{/if}/>
26 <td> 51 <label for="privateLinkByDefault">
27 <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/> 52 &nbsp;All new links are private by default
28 <label for="enableRssPermalinks"> 53 </label>
29 &nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}.</b> 54 </td>
30 </label> 55 </tr>
31 </td> 56 <tr>
32 </tr> 57 <td valign="top"><b>RSS direct links</b></td>
33 <tr> 58 <td>
34 <td valign="top"><b>Hide public links</b></td> 59 <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks"
35 <td> 60 {if="$enable_rss_permalinks"}checked{/if}/>
36 <input type="checkbox" name="hidePublicLinks" id="hidePublicLinks" {if="!empty($GLOBALS['config']['HIDE_PUBLIC_LINKS'])"}checked{/if}/><label for="hidePublicLinks">&nbsp; 61 <label for="enableRssPermalinks">
37 Do not show any links if the user is not logged in.</label> 62 &nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>
38 </td> 63 {if="$enable_rss_permalinks"}enabled{else}disabled{/if}.</b>
39 </tr> 64 </label>
40 <tr><td valign="top"><b>Update:</b></td><td> 65 </td>
41 <input type="checkbox" name="updateCheck" id="updateCheck" {if="!empty($GLOBALS['config']['ENABLE_UPDATECHECK'])"}checked{/if}/> 66 </tr>
42 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td> 67 <tr>
43 </tr> 68 <td valign="top"><b>Hide public links</b></td>
69 <td>
70 <input type="checkbox" name="hidePublicLinks" id="hidePublicLinks"
71 {if="$hide_public_links"}checked{/if}/>
72 <label for="hidePublicLinks">&nbsp;Do not show any links if the user is not logged in.</label>
73 </td>
74 </tr>
75 <tr>
76 <td valign="top"><b>Update:</b></td>
77 <td>
78 <input type="checkbox" name="updateCheck" id="updateCheck"
79 {if="$enable_update_check"}checked{/if}/>
80 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label>
81 </td>
82 </tr>
44 83
45 <tr><td></td><td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr> 84 <tr>
46 </table> 85 <td></td>
47 </form> 86 <td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td>
87 </tr>
88 </table>
89 </form>
48</div> 90</div>
49{include="page.footer"} 91{include="page.footer"}
50</body> 92</body>
diff --git a/tpl/daily.html b/tpl/daily.html
index 063dc89a..eba0af3b 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -42,25 +42,25 @@
42 <div class="clear"></div> 42 <div class="clear"></div>
43 43
44 {if="$linksToDisplay"} 44 {if="$linksToDisplay"}
45 {loop="cols"} 45 {loop="$cols"}
46 {if="isset($value[0])"} 46 {if="isset($value[0])"}
47 <div id="daily_col{$counter+1}"> 47 <div id="daily_col{$counter+1}">
48 {loop="value"} 48 {loop="$value"}
49 {$link=$value} 49 {$link=$value}
50 <div class="dailyEntry"> 50 <div class="dailyEntry">
51 <div class="dailyEntryPermalink"> 51 <div class="dailyEntryPermalink">
52 <a href="?{$link.linkdate|smallHash}"> 52 <a href="?{$value.shorturl}">
53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink"> 53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
54 </a> 54 </a>
55 </div> 55 </div>
56 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} 56 {if="!$hide_timestamps || isLoggedIn()"}
57 <div class="dailyEntryLinkdate"> 57 <div class="dailyEntryLinkdate">
58 <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a> 58 <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
59 </div> 59 </div>
60 {/if} 60 {/if}
61 {if="$link.tags"} 61 {if="$link.tags"}
62 <div class="dailyEntryTags"> 62 <div class="dailyEntryTags">
63 {loop="link.taglist"} 63 {loop="$link.taglist"}
64 {$value} - 64 {$value} -
65 {/loop} 65 {/loop}
66 </div> 66 </div>
diff --git a/tpl/dailyrss.html b/tpl/dailyrss.html
index 4133ca3e..ddbd6c5e 100644
--- a/tpl/dailyrss.html
+++ b/tpl/dailyrss.html
@@ -4,9 +4,9 @@
4 <link>{$absurl}</link> 4 <link>{$absurl}</link>
5 <pubDate>{$rssdate}</pubDate> 5 <pubDate>{$rssdate}</pubDate>
6 <description><![CDATA[ 6 <description><![CDATA[
7 {loop="links"} 7 {loop="$links"}
8 <h3><a href="{$value.url}">{$value.title}</a></h3> 8 <h3><a href="{$value.url}">{$value.title}</a></h3>
9 <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> 9 <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
10 {$value.url}</small><br> 10 {$value.url}</small><br>
11 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br> 11 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
12 {if="$value.description"}{$value.formatedDescription}{/if} 12 {if="$value.description"}{$value.formatedDescription}{/if}
diff --git a/tpl/editlink.html b/tpl/editlink.html
index 14a2e6c8..870cc168 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -8,13 +8,18 @@
8{elseif="$link.description==''"}onload="document.linkform.lf_description.focus();" 8{elseif="$link.description==''"}onload="document.linkform.lf_description.focus();"
9{else}onload="document.linkform.lf_tags.focus();"{/if} > 9{else}onload="document.linkform.lf_tags.focus();"{/if} >
10<div id="pageheader"> 10<div id="pageheader">
11 {if="$source !== 'firefoxsocialapi'"} 11 {if="$source !== 'firefoxsocialapi'"}
12 {include="page.header"} 12 {include="page.header"}
13 {/if} 13 {else}
14 <div id="editlinkform"> 14 <div id="shaarli_title"><a href="{$titleLink}">{$shaarlititle}</a></div>
15 <form method="post" name="linkform"> 15 {/if}
16 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> 16 <div id="editlinkform">
17 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br> 17 <form method="post" name="linkform">
18 <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
19 {if="isset($link.id)"}
20 <input type="hidden" name="lf_id" value="{$link.id}">
21 {/if}
22 <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br>
18 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br> 23 <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br>
19 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br> 24 <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br>
20 <label for="lf_tags"><i>Tags</i></label><br> 25 <label for="lf_tags"><i>Tags</i></label><br>
@@ -25,30 +30,28 @@
25 {$value} 30 {$value}
26 {/loop} 31 {/loop}
27 32
28 {if="($link_is_new && $GLOBALS['privateLinkByDefault']==true) || $link.private == true"} 33 {if="($link_is_new && $default_private_links) || $link.private == true"}
29 <input type="checkbox" checked="checked" name="lf_private" id="lf_private"> 34 <input type="checkbox" checked="checked" name="lf_private" id="lf_private">
30 &nbsp;<label for="lf_private"><i>Private</i></label><br> 35 &nbsp;<label for="lf_private"><i>Private</i></label><br>
31 {else} 36 {else}
32 <input type="checkbox" name="lf_private" id="lf_private"> 37 <input type="checkbox" name="lf_private" id="lf_private">
33 &nbsp;<label for="lf_private"><i>Private</i></label><br> 38 &nbsp;<label for="lf_private"><i>Private</i></label><br>
34 {/if} 39 {/if}
35 <input type="submit" value="Save" name="save_edit" class="bigbutton"> 40 <input type="submit" value="Save" name="save_edit" class="bigbutton">
36 <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton"> 41 <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton">
37 {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if} 42 {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if}
38 <input type="hidden" name="token" value="{$token}"> 43 <input type="hidden" name="token" value="{$token}">
39 {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if} 44 {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if}
40 </form> 45 </form>
41 </div> 46 </div>
42</div> 47</div>
43{if="$source !== 'firefoxsocialapi'"} 48{if="$source !== 'firefoxsocialapi'"}
44{include="page.footer"} 49{include="page.footer"}
45{/if} 50{/if}
46{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"}
47<script src="inc/awesomplete.min.js#"></script> 51<script src="inc/awesomplete.min.js#"></script>
48<script src="inc/awesomplete-multiple-tags.js#"></script> 52<script src="inc/awesomplete-multiple-tags.js#"></script>
49<script> 53<script>
50 awesompleteUniqueTag('#lf_tags'); 54 awesompleteUniqueTag('#lf_tags');
51</script> 55</script>
52{/if}
53</body> 56</body>
54</html> 57</html>
diff --git a/tpl/export.bookmarks.html b/tpl/export.bookmarks.html
index da733257..127a5c20 100644
--- a/tpl/export.bookmarks.html
+++ b/tpl/export.bookmarks.html
@@ -5,6 +5,6 @@
5 Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore} 5 Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore}
6<TITLE>{$pagetitle}</TITLE> 6<TITLE>{$pagetitle}</TITLE>
7<H1>Shaarli export of {$selection} bookmarks on {$date}</H1> 7<H1>Shaarli export of {$selection} bookmarks on {$date}</H1>
8<DL><p>{loop="links"} 8<DL><p>{loop="$links"}
9<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop} 9<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop}
10</DL><p> 10</DL><p>
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html
index 2ebb162a..aead0459 100644
--- a/tpl/feed.atom.html
+++ b/tpl/feed.atom.html
@@ -17,7 +17,7 @@
17 </author> 17 </author>
18 <id>{$index_url}</id> 18 <id>{$index_url}</id>
19 <generator>Shaarli</generator> 19 <generator>Shaarli</generator>
20 {loop="links"} 20 {loop="$links"}
21 <entry> 21 <entry>
22 <title>{$value.title}</title> 22 <title>{$value.title}</title>
23 {if="$usepermalinks"} 23 {if="$usepermalinks"}
@@ -27,11 +27,10 @@
27 {/if} 27 {/if}
28 <id>{$value.guid}</id> 28 <id>{$value.guid}</id>
29 {if="$show_dates"} 29 {if="$show_dates"}
30 <updated>{$value.iso_date}</updated> 30 <published>{$value.pub_iso_date}</published>
31 <updated>{$value.up_iso_date}</updated>
31 {/if} 32 {/if}
32 <content type="html" xml:lang="{$language}"> 33 <content type="html" xml:lang="{$language}"><![CDATA[{$value.description}]]></content>
33 <![CDATA[{$value.description}]]>
34 </content>
35 {loop="$value.taglist"} 34 {loop="$value.taglist"}
36 <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" /> 35 <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
37 {/loop} 36 {/loop}
diff --git a/tpl/feed.rss.html b/tpl/feed.rss.html
index 26de7f19..e18dbf9b 100644
--- a/tpl/feed.rss.html
+++ b/tpl/feed.rss.html
@@ -12,7 +12,7 @@
12 <!-- PubSubHubbub Discovery --> 12 <!-- PubSubHubbub Discovery -->
13 <atom:link rel="hub" href="{$pubsubhub_url}" /> 13 <atom:link rel="hub" href="{$pubsubhub_url}" />
14 {/if} 14 {/if}
15 {loop="links"} 15 {loop="$links"}
16 <item> 16 <item>
17 <title>{$value.title}</title> 17 <title>{$value.title}</title>
18 <guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid> 18 <guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid>
@@ -22,7 +22,8 @@
22 <link>{$value.url}</link> 22 <link>{$value.url}</link>
23 {/if} 23 {/if}
24 {if="$show_dates"} 24 {if="$show_dates"}
25 <pubDate>{$value.iso_date}</pubDate> 25 <pubDate>{$value.pub_iso_date}</pubDate>
26 <atom:modified>{$value.up_iso_date}</atom:modified>
26 {/if} 27 {/if}
27 <description><![CDATA[{$value.description}]]></description> 28 <description><![CDATA[{$value.description}]]></description>
28 {loop="$value.taglist"} 29 {loop="$value.taglist"}
diff --git a/tpl/import.html b/tpl/import.html
index 6c4f9421..071e1160 100644
--- a/tpl/import.html
+++ b/tpl/import.html
@@ -3,19 +3,31 @@
3<head>{include="includes"}</head> 3<head>{include="includes"}</head>
4<body onload="document.uploadform.filetoupload.focus();"> 4<body onload="document.uploadform.filetoupload.focus();">
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} bytes). 8 Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize} bytes).
9 <form method="POST" action="?do=upload" enctype="multipart/form-data" name="uploadform" id="uploadform"> 9 <form method="POST" action="?do=import" enctype="multipart/form-data"
10 <input type="hidden" name="token" value="{$token}"> 10 name="uploadform" id="uploadform">
11 <input type="file" name="filetoupload"> 11 <input type="hidden" name="token" value="{$token}">
12 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}"> 12 <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
13 <input type="submit" name="import_file" value="Import" class="bigbutton"><br> 13 <input type="file" name="filetoupload">
14 <input type="checkbox" name="private" id="private"><label for="private">&nbsp;Import all links as private</label><br> 14 <input type="submit" name="import_file" value="Import" class="bigbutton"><br>
15 <input type="checkbox" name="overwrite" id="overwrite"><label for="overwrite">&nbsp;Overwrite existing links</label> 15
16 </form> 16 <label for="privacy">&nbsp;Visibility:</label><br>
17 </div> 17 <input type="radio" name="privacy" value="default" checked="true">
18 &nbsp;Use values from the imported file, default to public<br>
19 <input type="radio" name="privacy" value="private">
20 &nbsp;Import all bookmarks as private<br>
21 <input type="radio" name="privacy" value="public">
22 &nbsp;Import all bookmarks as public<br>
23
24 <input type="checkbox" name="overwrite" id="overwrite">
25 <label for="overwrite">&nbsp;Overwrite existing bookmarks</label><br>
26 <label for="default_tags">&nbsp;Add default tags</label>
27 <input type="text" name="default_tags" id="default_tags">
28 </form>
29 </div>
18</div> 30</div>
19{include="page.footer"} 31{include="page.footer"}
20</body> 32</body>
21</html> \ No newline at end of file 33</html>
diff --git a/tpl/includes.html b/tpl/includes.html
index f94ce1be..7b2997ce 100644
--- a/tpl/includes.html
+++ b/tpl/includes.html
@@ -2,6 +2,7 @@
2<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 2<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
3<meta name="format-detection" content="telephone=no" /> 3<meta name="format-detection" content="telephone=no" />
4<meta name="viewport" content="width=device-width,initial-scale=1.0" /> 4<meta name="viewport" content="width=device-width,initial-scale=1.0" />
5<meta name="referrer" content="same-origin">
5<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" /> 6<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
6<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" /> 7<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
7<link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" /> 8<link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" />
@@ -11,4 +12,4 @@
11{loop="$plugins_includes.css_files"} 12{loop="$plugins_includes.css_files"}
12<link type="text/css" rel="stylesheet" href="{$value}#"/> 13<link type="text/css" rel="stylesheet" href="{$value}#"/>
13{/loop} 14{/loop}
14<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/> \ No newline at end of file 15<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/>
diff --git a/tpl/linklist.html b/tpl/linklist.html
index c0d42006..0f1a5e8c 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -28,7 +28,17 @@
28 <input type="submit" value="Search" class="bigbutton"> 28 <input type="submit" value="Search" class="bigbutton">
29 </form> 29 </form>
30 {loop="$plugins_header.fields_toolbar"} 30 {loop="$plugins_header.fields_toolbar"}
31 {$value} 31 <form
32 {loop="$value.attr"}
33 {$key}="{$value}"
34 {/loop}>
35 {loop="$value.inputs"}
36 <input
37 {loop="$value"}
38 {$key}="{$value}"
39 {/loop}>
40 {/loop}
41 </form>
32 {/loop} 42 {/loop}
33 </div> 43 </div>
34</div> 44</div>
@@ -63,7 +73,7 @@
63 </div> 73 </div>
64 {/if} 74 {/if}
65 <ul> 75 <ul>
66 {loop="links"} 76 {loop="$links"}
67 <li{if="$value.class"} class="{$value.class}"{/if}> 77 <li{if="$value.class"} class="{$value.class}"{/if}>
68 <a id="{$value.shorturl}"></a> 78 <a id="{$value.shorturl}"></a>
69 <div class="thumbnail">{$value.url|thumbnail}</div> 79 <div class="thumbnail">{$value.url|thumbnail}</div>
@@ -71,11 +81,11 @@
71 {if="isLoggedIn()"} 81 {if="isLoggedIn()"}
72 <div class="linkeditbuttons"> 82 <div class="linkeditbuttons">
73 <form method="GET" class="buttoneditform"> 83 <form method="GET" class="buttoneditform">
74 <input type="hidden" name="edit_link" value="{$value.linkdate}"> 84 <input type="hidden" name="edit_link" value="{$value.id}">
75 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit"> 85 <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
76 </form><br> 86 </form><br>
77 <form method="POST" class="buttoneditform"> 87 <form method="POST" class="buttoneditform">
78 <input type="hidden" name="lf_linkdate" value="{$value.linkdate}"> 88 <input type="hidden" name="lf_linkdate" value="{$value.id}">
79 <input type="hidden" name="token" value="{$token}"> 89 <input type="hidden" name="token" value="{$token}">
80 <input type="hidden" name="delete_link"> 90 <input type="hidden" name="delete_link">
81 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete" 91 <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
@@ -88,8 +98,17 @@
88 </span> 98 </span>
89 <br> 99 <br>
90 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} 100 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
91 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} 101 {if="!$hide_timestamps || isLoggedIn()"}
92 <span class="linkdate" title="Permalink"><a href="?{$value.linkdate|smallHash}">{function="strftime('%c', $value.timestamp)"} - permalink</a> - </span> 102 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
103 <span class="linkdate" title="Permalink">
104 <a href="?{$value.shorturl}">
105 <span title="{$updated}">
106 {function="strftime('%c', $value.timestamp)"}
107 {if="$value.updated_timestamp"}*{/if}
108 </span>
109 - permalink
110 </a> -
111 </span>
93 {else} 112 {else}
94 <span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span> 113 <span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span>
95 {/if} 114 {/if}
@@ -101,7 +120,7 @@
101 <a href="{$value.real_url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br> 120 <a href="{$value.real_url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br>
102 {if="$value.tags"} 121 {if="$value.tags"}
103 <div class="linktaglist"> 122 <div class="linktaglist">
104 {loop="value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop} 123 {loop="$value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop}
105 </div> 124 </div>
106 {/if} 125 {/if}
107 126
diff --git a/tpl/linklist.paging.html b/tpl/linklist.paging.html
index e91c8f86..86019c01 100644
--- a/tpl/linklist.paging.html
+++ b/tpl/linklist.paging.html
@@ -13,7 +13,14 @@
13 </div> 13 </div>
14{/if} 14{/if}
15 {loop="$action_plugin"} 15 {loop="$action_plugin"}
16 {$value} 16 <div class="paging_privatelinks">
17 <a
18 {loop="$value.attr"}
19 {$key}="{$value}"
20 {/loop}>
21 {$value.html}
22 </a>
23 </div>
17 {/loop} 24 {/loop}
18 <div class="paging_linksperpage"> 25 <div class="paging_linksperpage">
19 Links per page: <a href="?linksperpage=20">20</a> <a href="?linksperpage=50">50</a> <a href="?linksperpage=100">100</a> 26 Links per page: <a href="?linksperpage=20">20</a> <a href="?linksperpage=50">50</a> <a href="?linksperpage=100">100</a>
diff --git a/tpl/loginform.html b/tpl/loginform.html
index a49b42d3..84176385 100644
--- a/tpl/loginform.html
+++ b/tpl/loginform.html
@@ -2,7 +2,7 @@
2<html> 2<html>
3<head>{include="includes"}</head> 3<head>{include="includes"}</head>
4<body 4<body
5{if="ban_canLogin()"} 5{if="ban_canLogin($conf)"}
6 {if="empty($username)"} 6 {if="empty($username)"}
7 onload="document.loginform.login.focus();" 7 onload="document.loginform.login.focus();"
8 {else} 8 {else}
@@ -13,7 +13,7 @@
13 {include="page.header"} 13 {include="page.header"}
14 14
15 <div id="headerform"> 15 <div id="headerform">
16 {if="!ban_canLogin()"} 16 {if="!ban_canLogin($conf)"}
17 You have been banned from login after too many failed attempts. Try later. 17 You have been banned from login after too many failed attempts. Try later.
18 {else} 18 {else}
19 <form method="post" name="loginform"> 19 <form method="post" name="loginform">
diff --git a/tpl/page.header.html b/tpl/page.header.html
index 3a09ecd9..cce61ec4 100644
--- a/tpl/page.header.html
+++ b/tpl/page.header.html
@@ -21,21 +21,26 @@
21 <li><a href="?do=logout">Logout</a></li> 21 <li><a href="?do=logout">Logout</a></li>
22 <li><a href="?do=tools">Tools</a></li> 22 <li><a href="?do=tools">Tools</a></li>
23 <li><a href="?do=addlink">Add link</a></li> 23 <li><a href="?do=addlink">Add link</a></li>
24 {elseif="$GLOBALS['config']['OPEN_SHAARLI']"} 24 {elseif="$openshaarli"}
25 <li><a href="?do=tools">Tools</a></li> 25 <li><a href="?do=tools">Tools</a></li>
26 <li><a href="?do=addlink">Add link</a></li> 26 <li><a href="?do=addlink">Add link</a></li>
27 {else} 27 {else}
28 <li><a href="?do=login">Login</a></li> 28 <li><a href="?do=login">Login</a></li>
29 {/if} 29 {/if}
30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li> 30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li>
31 {if="$GLOBALS['config']['SHOW_ATOM']"} 31 {if="$showatom"}
32 <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li> 32 <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li>
33 {/if} 33 {/if}
34 <li><a href="?do=tagcloud">Tag cloud</a></li> 34 <li><a href="?do=tagcloud">Tag cloud</a></li>
35 <li><a href="?do=picwall{$searchcrits}">Picture wall</a></li> 35 <li><a href="?do=picwall{$searchcrits}">Picture wall</a></li>
36 <li><a href="?do=daily">Daily</a></li> 36 <li><a href="?do=daily">Daily</a></li>
37 {loop="$plugins_header.buttons_toolbar"} 37 {loop="$plugins_header.buttons_toolbar"}
38 {$value} 38 <li><a
39 {loop="$value.attr"}
40 {$key}="{$value}"
41 {/loop}>
42 {$value.html}
43 </a></li>
39 {/loop} 44 {/loop}
40{/if} 45{/if}
41 </ul> 46 </ul>
@@ -43,7 +48,7 @@
43 48
44{if="!empty($plugin_errors) && isLoggedIn()"} 49{if="!empty($plugin_errors) && isLoggedIn()"}
45 <ul class="errors"> 50 <ul class="errors">
46 {loop="plugin_errors"} 51 {loop="$plugin_errors"}
47 <li>{$value}</li> 52 <li>{$value}</li>
48 {/loop} 53 {/loop}
49 </ul> 54 </ul>
diff --git a/tpl/picwall.html b/tpl/picwall.html
index 230c948b..4e227e37 100644
--- a/tpl/picwall.html
+++ b/tpl/picwall.html
@@ -14,7 +14,7 @@
14 14
15<div class="center"> 15<div class="center">
16 <div id="picwall_container"> 16 <div id="picwall_container">
17 {loop="linksToDisplay"} 17 {loop="$linksToDisplay"}
18 <div class="picwall_pictureframe"> 18 <div class="picwall_pictureframe">
19 {$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a> 19 {$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
20 {loop="$value.picwall_plugin"} 20 {loop="$value.picwall_plugin"}
diff --git a/tpl/pluginsadmin.html b/tpl/pluginsadmin.html
index 5ddcf061..ead1734e 100644
--- a/tpl/pluginsadmin.html
+++ b/tpl/pluginsadmin.html
@@ -38,11 +38,11 @@
38 <tr data-line="{$key}" data-order="{$counter}"> 38 <tr data-line="{$key}" data-order="{$counter}">
39 <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td> 39 <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td>
40 <td class="center"> 40 <td class="center">
41 <a href="#" 41 <a href="#" class="arrow"
42 onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));"> 42 onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));">
43 43
44 </a> 44 </a>
45 <a href="#" 45 <a href="#" class="arrow"
46 onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));"> 46 onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));">
47 47
48 </a> 48 </a>
@@ -104,11 +104,14 @@
104 <div class="plugin_parameter"> 104 <div class="plugin_parameter">
105 <div class="float_label"> 105 <div class="float_label">
106 <label for="{$key}"> 106 <label for="{$key}">
107 <code>{$key}</code> 107 <code>{$key}</code><br>
108 {if="isset($value.desc)"}
109 {$value.desc}
110 {/if}
108 </label> 111 </label>
109 </div> 112 </div>
110 <div class="float_input"> 113 <div class="float_input">
111 <input name="{$key}" value="{$value}" id="{$key}"/> 114 <input name="{$key}" value="{$value.value}" id="{$key}"/>
112 </div> 115 </div>
113 </div> 116 </div>
114 {/loop} 117 {/loop}
diff --git a/tpl/tagcloud.html b/tpl/tagcloud.html
index e449f293..05e45273 100644
--- a/tpl/tagcloud.html
+++ b/tpl/tagcloud.html
@@ -11,7 +11,7 @@
11 </div> 11 </div>
12 12
13 <div id="cloudtag"> 13 <div id="cloudtag">
14 {loop="tags"} 14 {loop="$tags"}
15 <span class="count">{$value.count}</span><a 15 <span class="count">{$value.count}</span><a
16 href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a> 16 href="?searchtags={$key|urlencode}" style="font-size:{$value.size}em;">{$key}</a>
17 {loop="$value.tag_plugin"} 17 {loop="$value.tag_plugin"}
diff --git a/tpl/tools.html b/tpl/tools.html
index 78b81663..e06d239d 100644
--- a/tpl/tools.html
+++ b/tpl/tools.html
@@ -9,7 +9,7 @@
9 <br><br> 9 <br><br>
10 <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> 10 <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
11 <br><br> 11 <br><br>
12 {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> 12 {if="!$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
13 <br><br>{/if} 13 <br><br>{/if}
14 <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> 14 <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
15 <br><br> 15 <br><br>
@@ -50,12 +50,15 @@
50 &nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a private Note (text post) to your Shaarli. 50 &nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a private Note (text post) to your Shaarli.
51 </span> 51 </span>
52 </a><br><br> 52 </a><br><br>
53
54 {if="$sslenabled"}
53 <a class="smallbutton" onclick="activateFirefoxSocial(this)"> 55 <a class="smallbutton" onclick="activateFirefoxSocial(this)">
54 <b>✚Add to Firefox social</b> 56 <b>✚Add to Firefox social</b>
55 </a> 57 </a>
56 <a href="#"> 58 <a href="#">
57 <span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span> 59 <span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span>
58 </a><br><br> 60 </a><br><br>
61 {/if}
59 62
60 {loop="$tools_plugin"} 63 {loop="$tools_plugin"}
61 {$value} 64 {$value}
@@ -64,6 +67,7 @@
64 <div class="clear"></div> 67 <div class="clear"></div>
65 68
66 <script> 69 <script>
70 {if="$sslenabled"}
67 function activateFirefoxSocial(node) { 71 function activateFirefoxSocial(node) {
68 var loc = location.href; 72 var loc = location.href;
69 var baseURL = loc.substring(0, loc.lastIndexOf("/")); 73 var baseURL = loc.substring(0, loc.lastIndexOf("/"));
@@ -79,7 +83,7 @@
79 icon32URL: baseURL + "/images/favicon.ico", 83 icon32URL: baseURL + "/images/favicon.ico",
80 icon64URL: baseURL + "/images/favicon.ico", 84 icon64URL: baseURL + "/images/favicon.ico",
81 85
82 shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{description}&source=firefoxsocialapi{/noparse}", 86 shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi{/noparse}",
83 homepageURL: baseURL 87 homepageURL: baseURL
84 }; 88 };
85 node.setAttribute("data-service", JSON.stringify(data)); 89 node.setAttribute("data-service", JSON.stringify(data));
@@ -87,7 +91,7 @@
87 var activate = new CustomEvent("ActivateSocialFeature"); 91 var activate = new CustomEvent("ActivateSocialFeature");
88 node.dispatchEvent(activate); 92 node.dispatchEvent(activate);
89 } 93 }
90 94 {/if}
91 function alertBookmarklet() { 95 function alertBookmarklet() {
92 alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...'); 96 alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');
93 return false; 97 return false;