Release v0.9.0

-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEGqoBQZDmIumKOObJrZlaKwD4KWQFAlkPUlEACgkQrZlaKwD4
 KWR5+gf/akFTTxy5uyTfB1U36cGpgdvhf6bjosBKlaXY+Nvpu1NF/LX8xpbrSFDY
 phI8gECt2XPD8Nk4eMhpXi9KLMMYWMccVdO5NKuZP+NxtjpYnTTueAthosm7sWqy
 JXtqSJQCDRZJj2GNUhw1WvM/6t2WlmTFUcVRV/2Vq87Hzf0eYnOrTNUXVTsxfc3K
 8TY98qu4XgaMenzTjp35O5wza6kihEW27NXwM4KumWjg+VTgRkfePla5EGdK9BcG
 16wT94WCy29t/gTIEW9Q9Tf+hTO7Oaq0iyN/8Ha0QFXOOutMuHfrhFMezMDGPzzb
 esH47/AT2DzaxfCAIqSorgPVHVYzMA==
 =GN48
 -----END PGP SIGNATURE-----

Merge tag 'v0.9.0' into latest

Release v0.9.0
This commit is contained in:
ArthurHoaro 2017-05-07 19:23:32 +02:00
commit fcf141926d
265 changed files with 18411 additions and 1145 deletions

9
.gitattributes vendored
View file

@ -10,15 +10,22 @@
*.php text diff=php *.php text diff=php
Dockerfile text Dockerfile text
# Do not alter images nor minified scripts # Do not alter images nor minified scripts nor fonts
*.ico binary *.ico binary
*.jpg binary *.jpg binary
*.png binary *.png binary
*.svg binary
*.otf binary
*.eot binary
*.woff binary
*.woff2 binary
*.ttf binary
*.min.css binary *.min.css binary
*.min.js binary *.min.js binary
# Exclude from Git archives # Exclude from Git archives
.gitattributes export-ignore .gitattributes export-ignore
.github export-ignore
.gitignore export-ignore .gitignore export-ignore
.travis.yml export-ignore .travis.yml export-ignore
doc/**/*.json export-ignore doc/**/*.json export-ignore

13
.github/mailmap vendored Normal file
View file

@ -0,0 +1,13 @@
ArthurHoaro <arthur@hoa.ro>
Florian Eula <eula.florian@gmail.com> feula
Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com>
Sébastien Sauvage <sebsauvage@sebsauvage.net>
Timo Van Neerden <fire@lehollandaisvolant.net>
Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com>
VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net>
VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org>

8
.gitignore vendored
View file

@ -13,11 +13,10 @@ pagecache
*.rtpl.php *.rtpl.php
# 3rd-party dependencies # 3rd-party dependencies
composer.lock
vendor/ vendor/
# Release archives # Release archives
*.tar *.tar.gz
*.zip *.zip
# Development and test resources # Development and test resources
@ -28,3 +27,8 @@ phpmd.html
# User plugin configuration # User plugin configuration
plugins/*/config.php plugins/*/config.php
# 3rd party themes
tpl/*
!tpl/default
!tpl/vintage

4
.htaccess Normal file
View file

@ -0,0 +1,4 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

View file

@ -1,5 +1,11 @@
sudo: false sudo: false
language: php language: php
addons:
apt:
packages:
- locales
- language-pack-de
- language-pack-fr
cache: cache:
directories: directories:
- $HOME/.composer/cache - $HOME/.composer/cache
@ -8,12 +14,10 @@ php:
- 7.0 - 7.0
- 5.6 - 5.6
- 5.5 - 5.5
- 5.4
- 5.3
install: install:
- composer self-update - composer self-update
- composer install --prefer-dist - composer install --prefer-dist
script: script:
- make clean - make clean
- make check_permissions - make check_permissions
- make test - make all_tests

41
AUTHORS Normal file
View file

@ -0,0 +1,41 @@
472 ArthurHoaro <arthur@hoa.ro>
201 VirtualTam <virtualtam@flibidi.net>
132 nodiscc <nodiscc@gmail.com>
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
15 Florian Eula <eula.florian@gmail.com>
13 Emilien Klein <emilien@klein.st>
12 Nicolas Danelon <hi@nicolasmd.com.ar>
8 Christophe HENRY <christophe.henry@sbgodin.fr>
4 Alexandre Alapetite <alexandre@alapetite.fr>
4 David Sferruzza <david.sferruzza@gmail.com>
3 Teromene <teromene@teromene.fr>
2 Chris Kuethe <chris.kuethe@gmail.com>
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
2 Mathieu Chabanon <git@matchab.fr>
2 Miloš Jovanović <mjovanovic@gmail.com>
2 Qwerty <champlywood@free.fr>
2 Timo Van Neerden <fire@lehollandaisvolant.net>
2 julienCXX <software@chmodplusx.eu>
2 kalvn <kalvnthereal@gmail.com>
1 Adrien Oliva <adrien.oliva@yapbreak.fr>
1 Alexis J <alexis@effingo.be>
1 BoboTiG <bobotig@gmail.com>
1 Bronco <bronco@warriordudimanche.net>
1 D Low <daniellowtw@gmail.com>
1 Dimtion <zizou.xena@gmail.com>
1 Fanch <fanch-github@qth.fr>
1 Felix Bartels <felix@host-consultants.de>
1 Felix Kästner <github.com-fpunktk@fpunktk.de>
1 Florian Voigt <flvoigt@me.com>
1 Gary Marigliano <gmarigliano93@gmail.com>
1 Guillaume Virlet <github@virlet.org>
1 Jonathan Druart <jonathan.druart@gmail.com>
1 Julien Pivotto <roidelapluie@inuits.eu>
1 Kevin Canévet <kevin@streamroot.io>
1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
1 Lionel Martin <renarddesmers@gmail.com>
1 Marsup <marsup@gmail.com>
1 Sbgodin <Sbgodin@users.noreply.github.com>
1 TsT <tst2005@gmail.com>
1 dimtion <zizou.xena@gmail.com>
1 philipp-r <philipp-r@users.noreply.github.com>

View file

@ -5,13 +5,81 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED ## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - 2017-05-07
This release introduces the REST API, and requires updating HTTP server
configuration to enable URL rewriting, see:
- https://shaarli.github.io/api-documentation/
- https://github.com/shaarli/Shaarli/wiki/Server-configuration
**WARNING**: Shaarli now requires PHP 5.5+.
### Added ### Added
- REST API v1
- [Slim](https://www.slimframework.com/) framework
- [JSON Web Token](https://jwt.io/introduction/) (JWT) authentication
- versioned API endpoints:
- `/api/v1/info`: get general information on the Shaarli instance
- `/api/v1/links`: get a list of shaared links
- `/api/v1/history`: get a list of latest actions
Theming:
- Introduce a new theme
- Allow selecting themes/templates from the configuration page
- New/Edit link form can be submitted using CTRL+Enter in the textarea
- Shaarli version is displayed in the footer when logged in
- Add plugin placeholders to Atom/RSS feed templates
- Add OpenSearch to feed templates
- Add `campaign_` to the URL cleanup pattern list
- Add an AUTHORS file and Makefile target to list authors from Git commit data
- Link imports are now logged in `data/` folder, and can be debug using `dev.debug=true` setting.
- `composer.lock` is now included in git file to allow proper `composer install`
- History mechanism which logs link addition/modification/deletion
### Changed ### Changed
- Docker: enable nginx URL rewriting for the REST API
- Theming:
- Move `user.css` to the `data` folder
- Move default template files to a subfolder (`default`)
- Rename the legacy theme to `vintage`
- Private only filter is now displayed as a search parameter
- Autocomplete: pre-select the first element
- Display daily date in the page title (browser title)
- Timezone lists are now passed as an array instead of raw HTML
- Move PubSubHub to a dedicated plugin
- Coding style:
- explicit method visibility
- safe boolean comparisons
- remove unused variables
- The updater now keeps custom theme preferences
- Simplify the COPYING information
- Improved client locale detection
- Improved date time display depending on the locale
- Partial namespace support for Shaarli classes
- Shaarli version is now only present in `shaarli_version.php`
- Human readable maximum file size upload
### Removed
- PHP < 5.5 compatibility
- ReadItYourself plugin
### Fixed ### Fixed
- Ignore generated release tarballs
- Hide default port when behind a reverse proxy
- Fix a typo in the Markdown plugin description
- Fix the presence of empty tags for private tags and in search results
- Fix a fatal error during the install
- Fix permalink image alignment in daily page
- Fix the delete button in `editlink`
- Fix redirection after link deletion
- Do not access LinkDB links by ID before the Updater applies migrations
- Remove extra spaces in the bookmarklet's name
- Piwik plugin: Piwik URL protocol can now be set (http or https)
- All inline JS has been moved to dedicated JS files
- Keep tags after login redirection
### Security
- Markdown plugin: escape HTML entities by default
## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04 ## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
### Security ### Security
@ -30,6 +98,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Editing a link created before the new ID system would change its permalink. - Editing a link created before the new ID system would change its permalink.
## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04
### Security
- Markdown plugin: escape HTML entities by default
## [v0.8.3](https://github.com/shaarli/Shaarli/releases/tag/v0.8.3) - 2017-01-20
### Fixed
- PHP 7.1 compatibility: add ConfigManager parameter to anti-bruteforce function call in login template.
## [v0.8.2](https://github.com/shaarli/Shaarli/releases/tag/v0.8.2) - 2016-12-15
### Fixed
- Editing a link created before the new ID system would change its permalink.
## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12 ## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12
> Note: this version will create an automatic backup of your database if anything goes wrong. > Note: this version will create an automatic backup of your database if anything goes wrong.
@ -115,6 +196,10 @@ Please use our release archives, or follow the
- XSRF token now generated each time a page is rendered - XSRF token now generated each time a page is rendered
## [v0.7.1](https://github.com/shaarli/Shaarli/releases/tag/v0.7.1) - 2017-03-08
### Security
- Markdown plugin: escape HTML entities by default
## [v0.7.0](https://github.com/shaarli/Shaarli/releases/tag/v0.7.0) - 2016-05-14 ## [v0.7.0](https://github.com/shaarli/Shaarli/releases/tag/v0.7.0) - 2016-05-14
### Added ### Added
- Adds an option to encode redirector URL parameter - Adds an option to encode redirector URL parameter

34
COPYING
View file

@ -1,33 +1,7 @@
Files: * Files: *
License: zlib/libpng License: zlib/libpng
Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net> Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
(c) 2011-2015 Alexandre Alapetite <alexandre@alapetite.fr> (c) 2011-2017 The Shaarli Community, see AUTHORS
(c) 2011-2015 David Sferruzza <david.sferruzza@gmail.com>
(c) 2011-2015 Christophe HENRY <christophe.henry@sbgodin.fr>
(c) 2011-2015 Mathieu Chabanon <git@matchab.fr>
(c) 2011-2015 BoboTiG <bobotig@gmail.com>
(c) 2011-2015 Bronco <bronco@warriordudimanche.net>
(c) 2011-2015 Emilien Klein <emilien@klein.st>
(c) 2011-2015 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
(c) 2011-2015 Lionel Martin <renarddesmers@gmail.com>
(c) 2011-2015 lehollandaisvolant <levoltigeurhollandais@gmail.com>
(c) 2011-2015 timo van neerden <fire@lehollandaisvolant.net>
(c) 2011-2015 nodiscc <nodiscc@gmail.com>
(c) 2011-2015 Florian Eula <mr.pikzen@gmail.com>
(c) 2011-2015 Arthur Hoaro <arthur@hoa.ro>
(c) 2011-2015 Aurélien "VirtualTam" Tamisier <virtualtam@flibidi.net>
(c) 2011-2015 qwertygc <champlywood@free.fr>
(c) 2011-2015 idleman <idleman@idleman.fr>
(c) 2015 Alexis Ju <alexis@effingo.be>
(c) 2015 dimtion <zizou.xena@gmail.com>
(c) 2015 Fanch <fanch-github@qth.fr>
(c) 2015 Guillaume Virlet <github@virlet.org>
(c) 2015 Felix Bartels <felix@host-consultants.de>
(c) 2015 Marsup <marsup@gmail.com>
(c) 2015 Miloš Jovanović <mjovanovic@gmail.com>
(c) 2015 Nicolás Danelón <hola@nicolasdanelon.com.ar>
(c) 2015 TsT <tst2005@gmail.com>
Files: inc/reset.css Files: inc/reset.css
License: BSD (http://opensource.org/licenses/BSD-3-Clause) License: BSD (http://opensource.org/licenses/BSD-3-Clause)
@ -43,7 +17,7 @@ License: CC-BY (http://creativecommons.org/licenses/by/3.0/)
Copyright: (c) 2014 Designmodo Copyright: (c) 2014 Designmodo
Source: http://designmodo.com/linecons-free/ Source: http://designmodo.com/linecons-free/
Files: images/floral_left.png, images/floral_right.png, images/squiggle.png, images/squiggle2.png, images/squiggle_closing.png Files: images/floral_left.png, images/floral_right.png, images/squiggle.png, images/squiggle_closing.png
Licence: Public Domain Licence: Public Domain
Source: https://openclipart.org/people/j4p4n/j4p4n_ornimental_bookend_-_left.svg Source: https://openclipart.org/people/j4p4n/j4p4n_ornimental_bookend_-_left.svg
@ -72,6 +46,10 @@ Files: plugins/wallabag/wallabag.png
License: MIT License (http://opensource.org/licenses/MIT) License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag
Files: tpl/default/sad_star.png
License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) 2015 kalvn - https://github.com/kalvn/Shaarli-Material
---------------------------------------------------- ----------------------------------------------------
ZLIB/LIBPNG LICENSE ZLIB/LIBPNG LICENSE

View file

@ -124,8 +124,20 @@ test:
@echo "-------" @echo "-------"
@echo "PHPUNIT" @echo "PHPUNIT"
@echo "-------" @echo "-------"
@mkdir -p sandbox @mkdir -p sandbox coverage
@$(BIN)/phpunit tests @$(BIN)/phpunit --coverage-php coverage/main.cov --testsuite unit-tests
locale_test_%:
@UT_LOCALE=$*.utf8 \
$(BIN)/phpunit \
--coverage-php coverage/$(firstword $(subst _, ,$*)).cov \
--bootstrap tests/languages/bootstrap.php \
--testsuite language-$(firstword $(subst _, ,$*))
all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR
@$(BIN)/phpcov merge --html coverage coverage
@# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6)
@#$(BIN)/phpcov merge --text coverage/txt coverage
## ##
# Custom release archive generation # Custom release archive generation
@ -169,6 +181,12 @@ clean:
@git clean -df @git clean -df
@rm -rf sandbox @rm -rf sandbox
### generate the AUTHORS file from Git commit information
authors:
@cp .github/mailmap .mailmap
@git shortlog -sne > AUTHORS
@rm .mailmap
### generate Doxygen documentation ### generate Doxygen documentation
doxygen: clean doxygen: clean
@rm -rf doxygen @rm -rf doxygen
@ -214,4 +232,4 @@ htmlpages:
-o doc/$$base.html $$file; \ -o doc/$$base.html $$file; \
done; done;
htmldoc: doc htmlsidebar htmlpages htmldoc: authors doc htmlsidebar htmlpages

View file

@ -6,13 +6,18 @@ _Do you want to share the links you discover?_
_Shaarli is a minimalist delicious clone that you can install on your own server._ _Shaarli is a minimalist delicious clone that you can install on your own server._
_It is designed to be personal (single-user), fast and handy._ _It is designed to be personal (single-user), fast and handy._
[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/badge/stable-v0.7.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.7.1)
[![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
[![](https://img.shields.io/github/release/shaarli/shaarli.svg)](https://github.com/shaarli/Shaarli/releases/latest/) &bull;
[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/) [![](https://img.shields.io/badge/latest-v0.8.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4)
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
&bull;
[![](https://img.shields.io/badge/master-v0.9.x-blue.svg)](https://github.com/shaarli/Shaarli)
[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/shaarli/Shaarli)
[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli) [![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
[![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues) [![Bountysource](https://www.bountysource.com/badge/team?team_id=19583&style=bounties_received)](https://www.bountysource.com/teams/shaarli/issues)
[![Docker repository](https://img.shields.io/docker/pulls/shaarli/shaarli.svg)](https://hub.docker.com/r/shaarli/shaarli/)
## Quickstart ## Quickstart
- [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki) - [Wiki/documentation](https://github.com/shaarli/Shaarli/wiki)
@ -20,7 +25,7 @@ _It is designed to be personal (single-user), fast and handy._
- [Bugs/Feature requests/Discussion](https://github.com/shaarli/Shaarli/issues/) - [Bugs/Feature requests/Discussion](https://github.com/shaarli/Shaarli/issues/)
### Demo ### Demo
You can use this [public demo instance of Shaarli](http://shaarlidemo.tuxfamily.org/Shaarli). You can use this [public demo instance of Shaarli](https://demo.shaarli.org).
It runs the latest development version of Shaarli and is updated/reset daily. It runs the latest development version of Shaarli and is updated/reset daily.
Login: `demo`; Password: `demo` Login: `demo`; Password: `demo`
@ -80,6 +85,12 @@ dailymotion, flickr, imageshack, imgur, vimeo, xkcd, youtube...
- URL cleanup: automatic removal of `?utm_source=...`, `fb=...` - URL cleanup: automatic removal of `?utm_source=...`, `fb=...`
- discreet pop-up notification when a new release is available - discreet pop-up notification when a new release is available
### REST API
Easily extensible by any client using the REST API exposed by Shaarli.
See the [API documentation](http://shaarli.github.io/api-documentation/).
### Other usages ### Other usages
Though Shaarli is primarily a bookmarking application, it can serve other purposes Though Shaarli is primarily a bookmarking application, it can serve other purposes
(see [usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)): (see [usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)):

View file

@ -4,9 +4,13 @@
*/ */
class ApplicationUtils class ApplicationUtils
{ {
/**
* @var string File containing the current version
*/
public static $VERSION_FILE = 'shaarli_version.php';
private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
private static $GIT_BRANCHES = array('master', 'stable'); private static $GIT_BRANCHES = array('latest', 'stable');
private static $VERSION_FILE = 'shaarli_version.php';
private static $VERSION_START_TAG = '<?php /* '; private static $VERSION_START_TAG = '<?php /* ';
private static $VERSION_END_TAG = ' */ ?>'; private static $VERSION_END_TAG = ' */ ?>';
@ -29,6 +33,30 @@ public static function getLatestGitVersionCode($url, $timeout=2)
return false; return false;
} }
return $data;
}
/**
* Retrieve the version from a remote URL or a file.
*
* @param string $remote URL or file to fetch.
* @param int $timeout For URLs fetching.
*
* @return bool|string The version or false if it couldn't be retrieved.
*/
public static function getVersion($remote, $timeout = 2)
{
if (startsWith($remote, 'http')) {
if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
return false;
}
} else {
if (! is_file($remote)) {
return false;
}
$data = file_get_contents($remote);
}
return str_replace( return str_replace(
array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL), array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
array('', '', ''), array('', '', ''),
@ -65,13 +93,10 @@ public static function checkUpdate($currentVersion,
$isLoggedIn, $isLoggedIn,
$branch='stable') $branch='stable')
{ {
if (! $isLoggedIn) { // Do not check versions for visitors
// Do not check versions for visitors // Do not check if the user doesn't want to
return false; // Do not check with dev version
} if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
if (empty($enableCheck)) {
// Do not check if the user doesn't want to
return false; return false;
} }
@ -93,7 +118,7 @@ public static function checkUpdate($currentVersion,
// Late Static Binding allows overriding within tests // Late Static Binding allows overriding within tests
// See http://php.net/manual/en/language.oop5.late-static-bindings.php // See http://php.net/manual/en/language.oop5.late-static-bindings.php
$latestVersion = static::getLatestGitVersionCode( $latestVersion = static::getVersion(
self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
); );
@ -150,6 +175,7 @@ public static function checkResourcePermissions($conf)
'inc', 'inc',
'plugins', 'plugins',
$conf->get('resource.raintpl_tpl'), $conf->get('resource.raintpl_tpl'),
$conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme'),
) as $path) { ) as $path) {
if (! is_readable(realpath($path))) { if (! is_readable(realpath($path))) {
$errors[] = '"'.$path.'" directory is not readable'; $errors[] = '"'.$path.'" directory is not readable';

34
application/Base64Url.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace Shaarli;
/**
* URL-safe Base64 operations
*
* @see https://en.wikipedia.org/wiki/Base64#URL_applications
*/
class Base64Url
{
/**
* Base64Url-encodes data
*
* @param string $data Data to encode
*
* @return string Base64Url-encoded data
*/
public static function encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* Decodes Base64Url-encoded data
*
* @param string $data Data to decode
*
* @return string Decoded data
*/
public static function decode($data) {
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
}

View file

@ -7,9 +7,6 @@ class CachedPage
// Directory containing page caches // Directory containing page caches
private $cacheDir; private $cacheDir;
// Full URL of the page to cache -typically the value returned by pageUrl()
private $url;
// Should this URL be cached (boolean)? // Should this URL be cached (boolean)?
private $shouldBeCached; private $shouldBeCached;
@ -27,7 +24,6 @@ public function __construct($cacheDir, $url, $shouldBeCached)
{ {
// TODO: check write access to the cache directory // TODO: check write access to the cache directory
$this->cacheDir = $cacheDir; $this->cacheDir = $cacheDir;
$this->url = $url;
$this->filename = $this->cacheDir.'/'.sha1($url).'.cache'; $this->filename = $this->cacheDir.'/'.sha1($url).'.cache';
$this->shouldBeCached = $shouldBeCached; $this->shouldBeCached = $shouldBeCached;
} }

View file

@ -62,11 +62,6 @@ class FeedBuilder
*/ */
protected $hideDates; protected $hideDates;
/**
* @var string PubSub hub URL.
*/
protected $pubsubhubUrl;
/** /**
* @var string server locale. * @var string server locale.
*/ */
@ -120,7 +115,6 @@ public function buildData()
} }
$data['language'] = $this->getTypeLanguage(); $data['language'] = $this->getTypeLanguage();
$data['pubsubhub_url'] = $this->pubsubhubUrl;
$data['last_update'] = $this->getLatestDateFormatted(); $data['last_update'] = $this->getLatestDateFormatted();
$data['show_dates'] = !$this->hideDates || $this->isLoggedIn; $data['show_dates'] = !$this->hideDates || $this->isLoggedIn;
// Remove leading slash from REQUEST_URI. // Remove leading slash from REQUEST_URI.
@ -182,16 +176,6 @@ protected function buildItem($link, $pageaddr)
return $link; return $link;
} }
/**
* Assign PubSub hub URL.
*
* @param string $pubsubhubUrl PubSub hub url.
*/
public function setPubsubhubUrl($pubsubhubUrl)
{
$this->pubsubhubUrl = $pubsubhubUrl;
}
/** /**
* Set this to true to use permalinks instead of direct links. * Set this to true to use permalinks instead of direct links.
* *

View file

@ -1,21 +1,76 @@
<?php <?php
require_once 'exceptions/IOException.php';
/** /**
* Exception class thrown when a filesystem access failure happens * Class FileUtils
*
* Utility class for file manipulation.
*/ */
class IOException extends Exception class FileUtils
{ {
private $path; /**
* @var string
*/
protected static $phpPrefix = '<?php /* ';
/** /**
* Construct a new IOException * @var string
*
* @param string $path path to the resource that cannot be accessed
* @param string $message Custom exception message.
*/ */
public function __construct($path, $message = '') protected static $phpSuffix = ' */ ?>';
/**
* Write data into a file (Shaarli database format).
* The data is stored in a PHP file, as a comment, in compressed base64 format.
*
* The file will be created if it doesn't exist.
*
* @param string $file File path.
* @param mixed $content Content to write.
*
* @return int|bool Number of bytes written or false if it fails.
*
* @throws IOException The destination file can't be written.
*/
public static function writeFlatDB($file, $content)
{ {
$this->path = $path; if (is_file($file) && !is_writeable($file)) {
$this->message = empty($message) ? 'Error accessing' : $message; // The datastore exists but is not writeable
$this->message .= PHP_EOL . $this->path; throw new IOException($file);
} else if (!is_file($file) && !is_writeable(dirname($file))) {
// The datastore does not exist and its parent directory is not writeable
throw new IOException(dirname($file));
}
return file_put_contents(
$file,
self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix
);
}
/**
* Read data from a file containing Shaarli database format content.
* If the file isn't readable or doesn't exists, default data will be returned.
*
* @param string $file File path.
* @param mixed $default The default value to return if the file isn't readable.
*
* @return mixed The content unserialized, or default if the file isn't readable, or false if it fails.
*/
public static function readFlatDB($file, $default = null)
{
// Note that gzinflate is faster than gzuncompress.
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
if (is_readable($file)) {
return unserialize(
gzinflate(
base64_decode(
substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
)
)
);
}
return $default;
} }
} }

200
application/History.php Normal file
View file

@ -0,0 +1,200 @@
<?php
/**
* Class History
*
* Handle the history file tracing events in Shaarli.
* The history is stored as JSON in a file set by 'resource.history' setting.
*
* Available data:
* - event: event key
* - datetime: event date, in ISO8601 format.
* - id: event item identifier (currently only link IDs).
*
* Available event keys:
* - CREATED: new link
* - UPDATED: link updated
* - DELETED: link deleted
* - SETTINGS: the settings have been updated through the UI.
*
* Note: new events are put at the beginning of the file and history array.
*/
class History
{
/**
* @var string Action key: a new link has been created.
*/
const CREATED = 'CREATED';
/**
* @var string Action key: a link has been updated.
*/
const UPDATED = 'UPDATED';
/**
* @var string Action key: a link has been deleted.
*/
const DELETED = 'DELETED';
/**
* @var string Action key: settings have been updated.
*/
const SETTINGS = 'SETTINGS';
/**
* @var string History file path.
*/
protected $historyFilePath;
/**
* @var array History data.
*/
protected $history;
/**
* @var int History retention time in seconds (1 month).
*/
protected $retentionTime = 2678400;
/**
* History constructor.
*
* @param string $historyFilePath History file path.
* @param int $retentionTime History content rentention time in seconds.
*
* @throws Exception if something goes wrong.
*/
public function __construct($historyFilePath, $retentionTime = null)
{
$this->historyFilePath = $historyFilePath;
if ($retentionTime !== null) {
$this->retentionTime = $retentionTime;
}
}
/**
* Initialize: read history file.
*
* Allow lazy loading (don't read the file if it isn't necessary).
*/
protected function initialize()
{
$this->check();
$this->read();
}
/**
* Add Event: new link.
*
* @param array $link Link data.
*/
public function addLink($link)
{
$this->addEvent(self::CREATED, $link['id']);
}
/**
* Add Event: update existing link.
*
* @param array $link Link data.
*/
public function updateLink($link)
{
$this->addEvent(self::UPDATED, $link['id']);
}
/**
* Add Event: delete existing link.
*
* @param array $link Link data.
*/
public function deleteLink($link)
{
$this->addEvent(self::DELETED, $link['id']);
}
/**
* Add Event: settings updated.
*/
public function updateSettings()
{
$this->addEvent(self::SETTINGS);
}
/**
* Save a new event and write it in the history file.
*
* @param string $status Event key, should be defined as constant.
* @param mixed $id Event item identifier (e.g. link ID).
*/
protected function addEvent($status, $id = null)
{
if ($this->history === null) {
$this->initialize();
}
$item = [
'event' => $status,
'datetime' => new DateTime(),
'id' => $id !== null ? $id : '',
];
$this->history = array_merge([$item], $this->history);
$this->write();
}
/**
* Check that the history file is writable.
* Create the file if it doesn't exist.
*
* @throws Exception if it isn't writable.
*/
protected function check()
{
if (! is_file($this->historyFilePath)) {
FileUtils::writeFlatDB($this->historyFilePath, []);
}
if (! is_writable($this->historyFilePath)) {
throw new Exception('History file isn\'t readable or writable');
}
}
/**
* Read JSON history file.
*/
protected function read()
{
$this->history = FileUtils::readFlatDB($this->historyFilePath, []);
if ($this->history === false) {
throw new Exception('Could not parse history file');
}
}
/**
* Write JSON history file and delete old entries.
*/
protected function write()
{
$comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
foreach ($this->history as $key => $value) {
if ($value['datetime'] < $comparaison) {
unset($this->history[$key]);
}
}
FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history));
}
/**
* Get the History.
*
* @return array
*/
public function getHistory()
{
if ($this->history === null) {
$this->initialize();
}
return $this->history;
}
}

View file

@ -122,7 +122,7 @@ function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
$content = substr($response, $headSize); $content = substr($response, $headSize);
$headers = array(); $headers = array();
foreach (preg_split('~[\r\n]+~', $rawHeadersLastRedir) as $line) { foreach (preg_split('~[\r\n]+~', $rawHeadersLastRedir) as $line) {
if (empty($line) or ctype_space($line)) { if (empty($line) || ctype_space($line)) {
continue; continue;
} }
$splitLine = explode(': ', $line, 2); $splitLine = explode(': ', $line, 2);
@ -297,9 +297,17 @@ function server_url($server)
// Keep forwarded port // Keep forwarded port
if (strpos($server['HTTP_X_FORWARDED_PORT'], ',') !== false) { if (strpos($server['HTTP_X_FORWARDED_PORT'], ',') !== false) {
$ports = explode(',', $server['HTTP_X_FORWARDED_PORT']); $ports = explode(',', $server['HTTP_X_FORWARDED_PORT']);
$port = ':' . trim($ports[0]); $port = trim($ports[0]);
} else { } else {
$port = ':' . $server['HTTP_X_FORWARDED_PORT']; $port = $server['HTTP_X_FORWARDED_PORT'];
}
if (($scheme == 'http' && $port != '80')
|| ($scheme == 'https' && $port != '443')
) {
$port = ':' . $port;
} else {
$port = '';
} }
} }

View file

@ -50,12 +50,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// Link date storage format // Link date storage format
const LINK_DATE_FORMAT = 'Ymd_His'; const LINK_DATE_FORMAT = 'Ymd_His';
// Datastore PHP prefix
protected static $phpPrefix = '<?php /* ';
// Datastore PHP suffix
protected static $phpSuffix = ' */ ?>';
// List of links (associative array) // List of links (associative array)
// - key: link date (e.g. "20110823_124546"), // - key: link date (e.g. "20110823_124546"),
// - value: associative array (keys: title, description...) // - value: associative array (keys: title, description...)
@ -144,10 +138,10 @@ public function offsetSet($offset, $value)
if (!isset($value['id']) || empty($value['url'])) { if (!isset($value['id']) || empty($value['url'])) {
die('Internal Error: A link should always have an id and URL.'); die('Internal Error: A link should always have an id and URL.');
} }
if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
die('You must specify an integer as a key.'); die('You must specify an integer as a key.');
} }
if (! empty($offset) && $offset !== $value['id']) { if ($offset !== null && $offset !== $value['id']) {
die('Array offset and link ID must be equal.'); die('Array offset and link ID must be equal.');
} }
@ -295,16 +289,7 @@ private function read()
return; return;
} }
// Read data $this->links = FileUtils::readFlatDB($this->datastore, []);
// Note that gzinflate is faster than gzuncompress.
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
$this->links = array();
if (file_exists($this->datastore)) {
$this->links = unserialize(gzinflate(base64_decode(
substr(file_get_contents($this->datastore),
strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
}
$toremove = array(); $toremove = array();
foreach ($this->links as $key => &$link) { foreach ($this->links as $key => &$link) {
@ -361,19 +346,7 @@ private function read()
*/ */
private function write() private function write()
{ {
if (is_file($this->datastore) && !is_writeable($this->datastore)) { FileUtils::writeFlatDB($this->datastore, $this->links);
// The datastore exists but is not writeable
throw new IOException($this->datastore);
} else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
// The datastore does not exist and its parent directory is not writeable
throw new IOException(dirname($this->datastore));
}
file_put_contents(
$this->datastore,
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
);
} }
/** /**
@ -443,11 +416,11 @@ public function filterDay($request) {
* - searchtags: list of tags * - searchtags: list of tags
* - searchterm: term search * - searchterm: term search
* @param bool $casesensitive Optional: Perform case sensitive filter * @param bool $casesensitive Optional: Perform case sensitive filter
* @param bool $privateonly Optional: Returns private links only if true. * @param string $visibility return only all/private/public links
* *
* @return array filtered links, all links if no suitable filter was provided. * @return array filtered links, all links if no suitable filter was provided.
*/ */
public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false) public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all')
{ {
// Filter link database according to parameters. // Filter link database according to parameters.
$searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
@ -475,7 +448,7 @@ public function filterSearch($filterRequest = array(), $casesensitive = false, $
} }
$linkFilter = new LinkFilter($this); $linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $privateonly); return $linkFilter->filter($type, $request, $casesensitive, $visibility);
} }
/** /**

View file

@ -51,12 +51,16 @@ public function __construct($links)
* @param string $type Type of filter (eg. tags, permalink, etc.). * @param string $type Type of filter (eg. tags, permalink, etc.).
* @param mixed $request Filter content. * @param mixed $request Filter content.
* @param bool $casesensitive Optional: Perform case sensitive filter if true. * @param bool $casesensitive Optional: Perform case sensitive filter if true.
* @param bool $privateonly Optional: Only returns private links if true. * @param string $visibility Optional: return only all/private/public links
* *
* @return array filtered link list. * @return array filtered link list.
*/ */
public function filter($type, $request, $casesensitive = false, $privateonly = false) public function filter($type, $request, $casesensitive = false, $visibility = 'all')
{ {
if (! in_array($visibility, ['all', 'public', 'private'])) {
$visibility = 'all';
}
switch($type) { switch($type) {
case self::$FILTER_HASH: case self::$FILTER_HASH:
return $this->filterSmallHash($request); return $this->filterSmallHash($request);
@ -64,42 +68,44 @@ public function filter($type, $request, $casesensitive = false, $privateonly = f
if (!empty($request)) { if (!empty($request)) {
$filtered = $this->links; $filtered = $this->links;
if (isset($request[0])) { if (isset($request[0])) {
$filtered = $this->filterTags($request[0], $casesensitive, $privateonly); $filtered = $this->filterTags($request[0], $casesensitive, $visibility);
} }
if (isset($request[1])) { if (isset($request[1])) {
$lf = new LinkFilter($filtered); $lf = new LinkFilter($filtered);
$filtered = $lf->filterFulltext($request[1], $privateonly); $filtered = $lf->filterFulltext($request[1], $visibility);
} }
return $filtered; return $filtered;
} }
return $this->noFilter($privateonly); return $this->noFilter($visibility);
case self::$FILTER_TEXT: case self::$FILTER_TEXT:
return $this->filterFulltext($request, $privateonly); return $this->filterFulltext($request, $visibility);
case self::$FILTER_TAG: case self::$FILTER_TAG:
return $this->filterTags($request, $casesensitive, $privateonly); return $this->filterTags($request, $casesensitive, $visibility);
case self::$FILTER_DAY: case self::$FILTER_DAY:
return $this->filterDay($request); return $this->filterDay($request);
default: default:
return $this->noFilter($privateonly); return $this->noFilter($visibility);
} }
} }
/** /**
* Unknown filter, but handle private only. * Unknown filter, but handle private only.
* *
* @param bool $privateonly returns private link only if true. * @param string $visibility Optional: return only all/private/public links
* *
* @return array filtered links. * @return array filtered links.
*/ */
private function noFilter($privateonly = false) private function noFilter($visibility = 'all')
{ {
if (! $privateonly) { if ($visibility === 'all') {
return $this->links; return $this->links;
} }
$out = array(); $out = array();
foreach ($this->links as $key => $value) { foreach ($this->links as $key => $value) {
if ($value['private']) { if ($value['private'] && $visibility === 'private') {
$out[$key] = $value;
} else if (! $value['private'] && $visibility === 'public') {
$out[$key] = $value; $out[$key] = $value;
} }
} }
@ -151,14 +157,14 @@ private function filterSmallHash($smallHash)
* - see https://github.com/shaarli/Shaarli/issues/75 for examples * - see https://github.com/shaarli/Shaarli/issues/75 for examples
* *
* @param string $searchterms search query. * @param string $searchterms search query.
* @param bool $privateonly return only private links if true. * @param string $visibility Optional: return only all/private/public links.
* *
* @return array search results. * @return array search results.
*/ */
private function filterFulltext($searchterms, $privateonly = false) private function filterFulltext($searchterms, $visibility = 'all')
{ {
if (empty($searchterms)) { if (empty($searchterms)) {
return $this->links; return $this->noFilter($visibility);
} }
$filtered = array(); $filtered = array();
@ -189,8 +195,12 @@ private function filterFulltext($searchterms, $privateonly = false)
foreach ($this->links as $id => $link) { foreach ($this->links as $id => $link) {
// ignore non private links when 'privatonly' is on. // ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) { if ($visibility !== 'all') {
continue; if (! $link['private'] && $visibility === 'private') {
continue;
} else if ($link['private'] && $visibility === 'public') {
continue;
}
} }
// Concatenate link fields to search across fields. // Concatenate link fields to search across fields.
@ -235,16 +245,16 @@ private function filterFulltext($searchterms, $privateonly = false)
* *
* @param string $tags list of tags separated by commas or blank spaces. * @param string $tags list of tags separated by commas or blank spaces.
* @param bool $casesensitive ignore case if false. * @param bool $casesensitive ignore case if false.
* @param bool $privateonly returns private links only. * @param string $visibility Optional: return only all/private/public links.
* *
* @return array filtered links. * @return array filtered links.
*/ */
public function filterTags($tags, $casesensitive = false, $privateonly = false) public function filterTags($tags, $casesensitive = false, $visibility = 'all')
{ {
// Implode if array for clean up. // Implode if array for clean up.
$tags = is_array($tags) ? trim(implode(' ', $tags)) : $tags; $tags = is_array($tags) ? trim(implode(' ', $tags)) : $tags;
if (empty($tags)) { if (empty($tags)) {
return $this->links; return $this->noFilter($visibility);
} }
$searchtags = self::tagsStrToArray($tags, $casesensitive); $searchtags = self::tagsStrToArray($tags, $casesensitive);
@ -255,8 +265,12 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
foreach ($this->links as $key => $link) { foreach ($this->links as $key => $link) {
// ignore non private links when 'privatonly' is on. // ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) { if ($visibility !== 'all') {
continue; if (! $link['private'] && $visibility === 'private') {
continue;
} else if ($link['private'] && $visibility === 'public') {
continue;
}
} }
$linktags = self::tagsStrToArray($link['tags'], $casesensitive); $linktags = self::tagsStrToArray($link['tags'], $casesensitive);
@ -341,14 +355,14 @@ protected function searchTagAndHashTag($tag, $taglist, $description)
* @param bool $casesensitive will convert everything to lowercase if false. * @param bool $casesensitive will convert everything to lowercase if false.
* *
* @return array filtered tags string. * @return array filtered tags string.
*/ */
public static function tagsStrToArray($tags, $casesensitive) public static function tagsStrToArray($tags, $casesensitive)
{ {
// We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek) // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
$tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'); $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
$tagsOut = str_replace(',', ' ', $tagsOut); $tagsOut = str_replace(',', ' ', $tagsOut);
return array_values(array_filter(explode(' ', trim($tagsOut)), 'strlen')); return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
} }
} }

View file

@ -89,7 +89,9 @@ function count_private($links)
{ {
$cpt = 0; $cpt = 0;
foreach ($links as $link) { foreach ($links as $link) {
$cpt = $link['private'] == true ? $cpt + 1 : $cpt; if ($link['private']) {
$cpt += 1;
}
} }
return $cpt; return $cpt;

View file

@ -1,7 +1,13 @@
<?php <?php
use Psr\Log\LogLevel;
use Shaarli\Config\ConfigManager;
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
use Katzgrau\KLogger\Logger;
/** /**
* Utilities to import and export bookmarks using the Netscape format * Utilities to import and export bookmarks using the Netscape format
* TODO: Not static, use a container.
*/ */
class NetscapeBookmarkUtils class NetscapeBookmarkUtils
{ {
@ -85,14 +91,15 @@ private static function importStatus(
/** /**
* Imports Web bookmarks from an uploaded Netscape bookmark dump * Imports Web bookmarks from an uploaded Netscape bookmark dump
* *
* @param array $post Server $_POST parameters * @param array $post Server $_POST parameters
* @param array $files Server $_FILES parameters * @param array $files Server $_FILES parameters
* @param LinkDB $linkDb Loaded LinkDB instance * @param LinkDB $linkDb Loaded LinkDB instance
* @param string $pagecache Page cache * @param ConfigManager $conf instance
* @param History $history History instance
* *
* @return string Summary of the bookmark import status * @return string Summary of the bookmark import status
*/ */
public static function import($post, $files, $linkDb, $pagecache) public static function import($post, $files, $linkDb, $conf, $history)
{ {
$filename = $files['filetoupload']['name']; $filename = $files['filetoupload']['name'];
$filesize = $files['filetoupload']['size']; $filesize = $files['filetoupload']['size'];
@ -119,10 +126,20 @@ public static function import($post, $files, $linkDb, $pagecache)
$defaultPrivacy = 0; $defaultPrivacy = 0;
$parser = new NetscapeBookmarkParser( $parser = new NetscapeBookmarkParser(
true, // nested tag support true, // nested tag support
$defaultTags, // additional user-specified tags $defaultTags, // additional user-specified tags
strval(1 - $defaultPrivacy) // defaultPub = 1 - defaultPrivacy strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy
$conf->get('resource.data_dir') // log path, will be overridden
); );
$logger = new Logger(
$conf->get('resource.data_dir'),
! $conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
[
'prefix' => 'import.',
'extension' => 'log',
]
);
$parser->setLogger($logger);
$bookmarks = $parser->parseString($data); $bookmarks = $parser->parseString($data);
$importCount = 0; $importCount = 0;
@ -163,9 +180,11 @@ public static function import($post, $files, $linkDb, $pagecache)
$newLink['id'] = $existingLink['id']; $newLink['id'] = $existingLink['id'];
$newLink['created'] = $existingLink['created']; $newLink['created'] = $existingLink['created'];
$newLink['updated'] = new DateTime(); $newLink['updated'] = new DateTime();
$newLink['shorturl'] = $existingLink['shorturl'];
$linkDb[$existingLink['id']] = $newLink; $linkDb[$existingLink['id']] = $newLink;
$importCount++; $importCount++;
$overwriteCount++; $overwriteCount++;
$history->updateLink($newLink);
continue; continue;
} }
@ -177,9 +196,10 @@ public static function import($post, $files, $linkDb, $pagecache)
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
$linkDb[$newLink['id']] = $newLink; $linkDb[$newLink['id']] = $newLink;
$importCount++; $importCount++;
$history->addLink($newLink);
} }
$linkDb->save($pagecache); $linkDb->save($conf->get('resource.page_cache'));
return self::importStatus( return self::importStatus(
$filename, $filename,
$filesize, $filesize,

View file

@ -1,5 +1,7 @@
<?php <?php
use Shaarli\Config\ConfigManager;
/** /**
* This class is in charge of building the final page. * This class is in charge of building the final page.
* (This is basically a wrapper around RainTPL which pre-fills some fields.) * (This is basically a wrapper around RainTPL which pre-fills some fields.)
@ -19,16 +21,23 @@ class PageBuilder
*/ */
protected $conf; protected $conf;
/**
* @var LinkDB $linkDB instance.
*/
protected $linkDB;
/** /**
* PageBuilder constructor. * PageBuilder constructor.
* $tpl is initialized at false for lazy loading. * $tpl is initialized at false for lazy loading.
* *
* @param ConfigManager $conf Configuration Manager instance (reference). * @param ConfigManager $conf Configuration Manager instance (reference).
* @param LinkDB $linkDB instance.
*/ */
function __construct(&$conf) public function __construct(&$conf, $linkDB = null)
{ {
$this->tpl = false; $this->tpl = false;
$this->conf = $conf; $this->conf = $conf;
$this->linkDB = $linkDB;
} }
/** /**
@ -75,9 +84,13 @@ private function initialize()
} }
$this->tpl->assign('shaarlititle', $this->conf->get('general.title', 'Shaarli')); $this->tpl->assign('shaarlititle', $this->conf->get('general.title', 'Shaarli'));
$this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false)); $this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false));
$this->tpl->assign('showatom', $this->conf->get('feed.show_atom', false)); $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', true));
$this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
$this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false)); $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
$this->tpl->assign('token', getToken($this->conf)); $this->tpl->assign('token', getToken($this->conf));
if ($this->linkDB !== null) {
$this->tpl->assign('tags', $this->linkDB->allTags());
}
// To be removed with a proper theme configuration. // To be removed with a proper theme configuration.
$this->tpl->assign('conf', $this->conf); $this->tpl->assign('conf', $this->conf);
} }

View file

@ -31,6 +31,8 @@ class Router
public static $PAGE_EDITLINK = 'edit_link'; public static $PAGE_EDITLINK = 'edit_link';
public static $PAGE_DELETELINK = 'delete_link';
public static $PAGE_EXPORT = 'export'; public static $PAGE_EXPORT = 'export';
public static $PAGE_IMPORT = 'import'; public static $PAGE_IMPORT = 'import';
@ -120,6 +122,10 @@ public static function findPage($query, $get, $loggedIn)
return self::$PAGE_EDITLINK; return self::$PAGE_EDITLINK;
} }
if (isset($get['delete_link'])) {
return self::$PAGE_DELETELINK;
}
if (startsWith($query, 'do='. self::$PAGE_EXPORT)) { if (startsWith($query, 'do='. self::$PAGE_EXPORT)) {
return self::$PAGE_EXPORT; return self::$PAGE_EXPORT;
} }

View file

@ -0,0 +1,33 @@
<?php
namespace Shaarli;
/**
* Class ThemeUtils
*
* Utility functions related to theme management.
*
* @package Shaarli
*/
class ThemeUtils
{
/**
* Get a list of available themes.
*
* It will return the name of any directory present in the template folder.
*
* @param string $tplDir Templates main directory.
*
* @return array List of theme names.
*/
public static function getThemes($tplDir)
{
$allTheme = glob($tplDir.'/*', GLOB_ONLYDIR);
$themes = [];
foreach ($allTheme as $value) {
$themes[] = str_replace($tplDir.'/', '', $value);
}
return $themes;
}
}

View file

@ -1,23 +1,42 @@
<?php <?php
/** /**
* Generates the timezone selection form and JavaScript. * Generates a list of available timezone continents and cities.
* *
* Note: 'UTC/UTC' is mapped to 'UTC' to form a valid option * Two distinct array based on available timezones
* and the one selected in the settings:
* - (0) continents:
* + list of available continents
* + special key 'selected' containing the value of the selected timezone's continent
* - (1) cities:
* + list of available cities associated with their continent
* + special key 'selected' containing the value of the selected timezone's city (without the continent)
* *
* Example: preselect Europe/Paris * Example:
* list($htmlform, $js) = generateTimeZoneForm('Europe/Paris'); * [
* [
* 'America',
* 'Europe',
* 'selected' => 'Europe',
* ],
* [
* ['continent' => 'America', 'city' => 'Toronto'],
* ['continent' => 'Europe', 'city' => 'Paris'],
* 'selected' => 'Paris',
* ],
* ];
* *
* Notes:
* - 'UTC/UTC' is mapped to 'UTC' to form a valid option
* - a few timezone cities includes the country/state, such as Argentina/Buenos_Aires
* - these arrays are designed to build timezone selects in template files with any HTML structure
*
* @param array $installedTimeZones List of installed timezones as string
* @param string $preselectedTimezone preselected timezone (optional) * @param string $preselectedTimezone preselected timezone (optional)
* *
* @return array containing the generated HTML form and Javascript code * @return array[] continents and cities
**/ **/
function generateTimeZoneForm($preselectedTimezone='') function generateTimeZoneData($installedTimeZones, $preselectedTimezone = '')
{ {
// Select the server timezone
if ($preselectedTimezone == '') {
$preselectedTimezone = date_default_timezone_get();
}
if ($preselectedTimezone == 'UTC') { if ($preselectedTimezone == 'UTC') {
$pcity = $pcontinent = 'UTC'; $pcity = $pcontinent = 'UTC';
} else { } else {
@ -27,62 +46,30 @@ function generateTimeZoneForm($preselectedTimezone='')
$pcity = substr($preselectedTimezone, $spos+1); $pcity = substr($preselectedTimezone, $spos+1);
} }
// The list is in the form 'Europe/Paris', 'America/Argentina/Buenos_Aires' $continents = [];
// We split the list in continents/cities. $cities = [];
$continents = array(); foreach ($installedTimeZones as $tz) {
$cities = array();
// TODO: use a template to generate the HTML/Javascript form
foreach (timezone_identifiers_list() as $tz) {
if ($tz == 'UTC') { if ($tz == 'UTC') {
$tz = 'UTC/UTC'; $tz = 'UTC/UTC';
} }
$spos = strpos($tz, '/'); $spos = strpos($tz, '/');
if ($spos !== false) { // Ignore invalid timezones
$continent = substr($tz, 0, $spos); if ($spos === false) {
$city = substr($tz, $spos+1); continue;
$continents[$continent] = 1;
if (!isset($cities[$continent])) {
$cities[$continent] = '';
}
$cities[$continent] .= '<option value="'.$city.'"';
if ($pcity == $city) {
$cities[$continent] .= ' selected="selected"';
}
$cities[$continent] .= '>'.$city.'</option>';
} }
$continent = substr($tz, 0, $spos);
$city = substr($tz, $spos+1);
$cities[] = ['continent' => $continent, 'city' => $city];
$continents[$continent] = true;
} }
$continentsHtml = '';
$continents = array_keys($continents); $continents = array_keys($continents);
$continents['selected'] = $pcontinent;
$cities['selected'] = $pcity;
foreach ($continents as $continent) { return [$continents, $cities];
$continentsHtml .= '<option value="'.$continent.'"';
if ($pcontinent == $continent) {
$continentsHtml .= ' selected="selected"';
}
$continentsHtml .= '>'.$continent.'</option>';
}
// Timezone selection form
$timezoneForm = 'Continent:';
$timezoneForm .= '<select name="continent" id="continent" onChange="onChangecontinent();">';
$timezoneForm .= $continentsHtml.'</select>';
$timezoneForm .= '&nbsp;&nbsp;&nbsp;&nbsp;City:';
$timezoneForm .= '<select name="city" id="city">'.$cities[$pcontinent].'</select><br />';
// Javascript handler - updates the city list when the user selects a continent
$timezoneJs = '<script>';
$timezoneJs .= 'function onChangecontinent() {';
$timezoneJs .= 'document.getElementById("city").innerHTML =';
$timezoneJs .= ' citiescontinent[document.getElementById("continent").value]; }';
$timezoneJs .= 'var citiescontinent = '.json_encode($cities).';';
$timezoneJs .= '</script>';
return array($timezoneForm, $timezoneJs);
} }
/** /**

View file

@ -1,4 +1,7 @@
<?php <?php
use Shaarli\Config\ConfigJson;
use Shaarli\Config\ConfigPhp;
use Shaarli\Config\ConfigManager;
/** /**
* Class Updater. * Class Updater.
@ -69,7 +72,7 @@ public function update()
return $updatesRan; return $updatesRan;
} }
if ($this->methods == null) { if ($this->methods === null) {
throw new UpdaterException('Couldn\'t retrieve Updater class methods.'); throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
} }
@ -132,21 +135,6 @@ public function updateMethodMergeDeprecatedConfigFile()
return true; return true;
} }
/**
* Rename tags starting with a '-' to work with tag exclusion search.
*/
public function updateMethodRenameDashTags()
{
$linklist = $this->linkDB->filterSearch();
foreach ($linklist as $key => $link) {
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
$this->linkDB[$key] = $link;
}
$this->linkDB->save($this->conf->get('resource.page_cache'));
return true;
}
/** /**
* Move old configuration in PHP to the new config system in JSON format. * Move old configuration in PHP to the new config system in JSON format.
* *
@ -257,6 +245,104 @@ public function updateMethodDatastoreIds()
return true; return true;
} }
/**
* Rename tags starting with a '-' to work with tag exclusion search.
*/
public function updateMethodRenameDashTags()
{
$linklist = $this->linkDB->filterSearch();
foreach ($linklist as $key => $link) {
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
$this->linkDB[$key] = $link;
}
$this->linkDB->save($this->conf->get('resource.page_cache'));
return true;
}
/**
* Initialize API settings:
* - api.enabled: true
* - api.secret: generated secret
*/
public function updateMethodApiSettings()
{
if ($this->conf->exists('api.secret')) {
return true;
}
$this->conf->set('api.enabled', true);
$this->conf->set(
'api.secret',
generate_api_secret(
$this->conf->get('credentials.login'),
$this->conf->get('credentials.salt')
)
);
$this->conf->write($this->isLoggedIn);
return true;
}
/**
* New setting: theme name. If the default theme is used, nothing to do.
*
* If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory,
* and the current theme is set as default in the theme setting.
*
* @return bool true if the update is successful, false otherwise.
*/
public function updateMethodDefaultTheme()
{
// raintpl_tpl isn't the root template directory anymore.
// We run the update only if this folder still contains the template files.
$tplDir = $this->conf->get('resource.raintpl_tpl');
$tplFile = $tplDir . '/linklist.html';
if (! file_exists($tplFile)) {
return true;
}
$parent = dirname($tplDir);
$this->conf->set('resource.raintpl_tpl', $parent);
$this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/'));
$this->conf->write($this->isLoggedIn);
// Dependency injection gore
RainTPL::$tpl_dir = $tplDir;
return true;
}
/**
* Move the file to inc/user.css to data/user.css.
*
* Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine.
*
* @return bool true if the update is successful, false otherwise.
*/
public function updateMethodMoveUserCss()
{
if (! is_file('inc/user.css')) {
return true;
}
return rename('inc/user.css', 'data/user.css');
}
/**
* While the new default theme is in an unstable state
* continue to use the vintage theme
*/
public function updateMethodDefaultThemeVintage()
{
if ($this->conf->get('resource.theme') !== 'default') {
return true;
}
$this->conf->set('resource.theme', 'vintage');
$this->conf->write($this->isLoggedIn);
return true;
}
/** /**
* * `markdown_escape` is a new setting, set to true as default. * * `markdown_escape` is a new setting, set to true as default.
* *
@ -278,6 +364,93 @@ public function updateMethodEscapeMarkdown()
return true; return true;
} }
/**
* Add 'http://' to Piwik URL the setting is set.
*
* @return bool true if the update is successful, false otherwise.
*/
public function updateMethodPiwikUrl()
{
if (! $this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
return true;
}
$this->conf->set('plugins.PIWIK_URL', 'http://'. $this->conf->get('plugins.PIWIK_URL'));
$this->conf->write($this->isLoggedIn);
return true;
}
/**
* Use ATOM feed as default.
*/
public function updateMethodAtomDefault()
{
if (!$this->conf->exists('feed.show_atom') || $this->conf->get('feed.show_atom') === true) {
return true;
}
$this->conf->set('feed.show_atom', true);
$this->conf->write($this->isLoggedIn);
return true;
}
/**
* Update updates.check_updates_branch setting.
*
* If the current major version digit matches the latest branch
* major version digit, we set the branch to `latest`,
* otherwise we'll check updates on the `stable` branch.
*
* No update required for the dev version.
*
* Note: due to hardcoded URL and lack of dependency injection, this is not unit testable.
*
* FIXME! This needs to be removed when we switch to first digit major version
* instead of the second one since the versionning process will change.
*/
public function updateMethodCheckUpdateRemoteBranch()
{
if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
return true;
}
// Get latest branch major version digit
$latestVersion = ApplicationUtils::getLatestGitVersionCode(
'https://raw.githubusercontent.com/shaarli/Shaarli/latest/shaarli_version.php',
5
);
if (preg_match('/(\d+)\.\d+$/', $latestVersion, $matches) === false) {
return false;
}
$latestMajor = $matches[1];
// Get current major version digit
preg_match('/(\d+)\.\d+$/', shaarli_version, $matches);
$currentMajor = $matches[1];
if ($currentMajor === $latestMajor) {
$branch = 'latest';
} else {
$branch = 'stable';
}
$this->conf->set('updates.check_updates_branch', $branch);
$this->conf->write($this->isLoggedIn);
return true;
}
/**
* Reset history store file due to date format change.
*/
public function updateMethodResetHistoryFile()
{
if (is_file($this->conf->get('resource.history'))) {
unlink($this->conf->get('resource.history'));
}
return true;
}
} }
/** /**

View file

@ -94,7 +94,10 @@ class Url
'utm_', 'utm_',
// ATInternet // ATInternet
'xtor=' 'xtor=',
// Other
'campaign_'
); );
private static $annoyingFragments = array( private static $annoyingFragments = array(

View file

@ -216,18 +216,222 @@ function is_session_id_valid($sessionId)
function autoLocale($headerLocale) function autoLocale($headerLocale)
{ {
// Default if browser does not send HTTP_ACCEPT_LANGUAGE // Default if browser does not send HTTP_ACCEPT_LANGUAGE
$attempts = array('en_US'); $locales = array('en_US', 'en_US.utf8', 'en_US.UTF-8');
if (isset($headerLocale)) { if (! empty($headerLocale)) {
// (It's a bit crude, but it works very well. Preferred language is always presented first.) if (preg_match_all('/([a-z]{2,3})[-_]?([a-z]{2})?,?/i', $headerLocale, $matches, PREG_SET_ORDER)) {
if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) { $attempts = [];
$loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : ''); foreach ($matches as $match) {
$attempts = array( $first = [strtolower($match[1]), strtoupper($match[1])];
$loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc), $separators = ['_', '-'];
$loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc), $encodings = ['utf8', 'UTF-8'];
$loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8', if (!empty($match[2])) {
$loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc $second = [strtoupper($match[2]), strtolower($match[2])];
); $items = [$first, $separators, $second, ['.'], $encodings];
} else {
$items = [$first, $separators, $first, ['.'], $encodings];
}
$attempts = array_merge($attempts, iterator_to_array(cartesian_product_generator($items)));
}
if (! empty($attempts)) {
$locales = array_merge(array_map('implode', $attempts), $locales);
}
} }
} }
setlocale(LC_ALL, $attempts);
setlocale(LC_ALL, $locales);
}
/**
* Build a Generator object representing the cartesian product from given $items.
*
* Example:
* [['a'], ['b', 'c']]
* will generate:
* [
* ['a', 'b'],
* ['a', 'c'],
* ]
*
* @param array $items array of array of string
*
* @return Generator representing the cartesian product of given array.
*
* @see https://en.wikipedia.org/wiki/Cartesian_product
*/
function cartesian_product_generator($items)
{
if (empty($items)) {
yield [];
}
$subArray = array_pop($items);
if (empty($subArray)) {
return;
}
foreach (cartesian_product_generator($items) as $item) {
foreach ($subArray as $value) {
yield $item + [count($item) => $value];
}
}
}
/**
* Generates a default API secret.
*
* Note that the random-ish methods used in this function are predictable,
* which makes them NOT suitable for crypto.
* BUT the random string is salted with the salt and hashed with the username.
* It makes the generated API secret secured enough for Shaarli.
*
* PHP 7 provides random_int(), designed for cryptography.
* More info: http://stackoverflow.com/questions/4356289/php-random-string-generator
* @param string $username Shaarli login username
* @param string $salt Shaarli password hash salt
*
* @return string|bool Generated API secret, 12 char length.
* Or false if invalid parameters are provided (which will make the API unusable).
*/
function generate_api_secret($username, $salt)
{
if (empty($username) || empty($salt)) {
return false;
}
return str_shuffle(substr(hash_hmac('sha512', uniqid($salt), $username), 10, 12));
}
/**
* Trim string, replace sequences of whitespaces by a single space.
* PHP equivalent to `normalize-space` XSLT function.
*
* @param string $string Input string.
*
* @return mixed Normalized string.
*/
function normalize_spaces($string)
{
return preg_replace('/\s{2,}/', ' ', trim($string));
}
/**
* Format the date according to the locale.
*
* Requires php-intl to display international datetimes,
* otherwise default format '%c' will be returned.
*
* @param DateTime $date to format.
* @param bool $time Displays time if true.
* @param bool $intl Use international format if true.
*
* @return bool|string Formatted date, or false if the input is invalid.
*/
function format_date($date, $time = true, $intl = true)
{
if (! $date instanceof DateTime) {
return false;
}
if (! $intl || ! class_exists('IntlDateFormatter')) {
$format = $time ? '%c' : '%x';
return strftime($format, $date->getTimestamp());
}
$formatter = new IntlDateFormatter(
setlocale(LC_TIME, 0),
IntlDateFormatter::LONG,
$time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
);
return $formatter->format($date);
}
/**
* Check if the input is an integer, no matter its real type.
*
* PHP is a bit messy regarding this:
* - is_int returns false if the input is a string
* - ctype_digit returns false if the input is an integer or negative
*
* @param mixed $input value
*
* @return bool true if the input is an integer, false otherwise
*/
function is_integer_mixed($input)
{
if (is_array($input) || is_bool($input) || is_object($input)) {
return false;
}
$input = strval($input);
return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1)));
}
/**
* Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
*
* @param string $val Size expressed in string.
*
* @return int Size expressed in bytes.
*/
function return_bytes($val)
{
if (is_integer_mixed($val) || $val === '0' || empty($val)) {
return $val;
}
$val = trim($val);
$last = strtolower($val[strlen($val)-1]);
$val = intval(substr($val, 0, -1));
switch($last) {
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
}
return $val;
}
/**
* Return a human readable size from bytes.
*
* @param int $bytes value
*
* @return string Human readable size
*/
function human_bytes($bytes)
{
if ($bytes === '') {
return t('Setting not set');
}
if (! is_integer_mixed($bytes)) {
return $bytes;
}
$bytes = intval($bytes);
if ($bytes === 0) {
return t('Unlimited');
}
$units = [t('B'), t('kiB'), t('MiB'), t('GiB')];
for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) {
$bytes /= 1024;
}
return round($bytes) . $units[$i];
}
/**
* Try to determine max file size for uploads (POST).
* Returns an integer (in bytes) or formatted depending on $format.
*
* @param mixed $limitPost post_max_size PHP setting
* @param mixed $limitUpload upload_max_filesize PHP setting
* @param bool $format Format max upload size to human readable size
*
* @return int|string max upload file size
*/
function get_max_upload_size($limitPost, $limitUpload, $format = true)
{
$size1 = return_bytes($limitPost);
$size2 = return_bytes($limitUpload);
// Return the smaller of two:
$maxsize = min($size1, $size2);
return $format ? human_bytes($maxsize) : $maxsize;
} }

View file

@ -0,0 +1,138 @@
<?php
namespace Shaarli\Api;
use Shaarli\Api\Exceptions\ApiException;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
use Shaarli\Config\ConfigManager;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class ApiMiddleware
*
* This will be called before accessing any API Controller.
* Its role is to make sure that the API is enabled, configured, and to validate the JWT token.
*
* If the request is validated, the controller is called, otherwise a JSON error response is returned.
*
* @package Api
*/
class ApiMiddleware
{
/**
* @var int JWT token validity in seconds (9 min).
*/
public static $TOKEN_DURATION = 540;
/**
* @var Container: contains conf, plugins, etc.
*/
protected $container;
/**
* @var ConfigManager instance.
*/
protected $conf;
/**
* ApiMiddleware constructor.
*
* @param Container $container instance.
*/
public function __construct($container)
{
$this->container = $container;
$this->conf = $this->container->get('conf');
$this->setLinkDb($this->conf);
}
/**
* Middleware execution:
* - check the API request
* - execute the controller
* - return the response
*
* @param Request $request Slim request
* @param Response $response Slim response
* @param callable $next Next action
*
* @return Response response.
*/
public function __invoke($request, $response, $next)
{
try {
$this->checkRequest($request);
$response = $next($request, $response);
} catch(ApiException $e) {
$e->setResponse($response);
$e->setDebug($this->conf->get('dev.debug', false));
$response = $e->getApiResponse();
}
return $response;
}
/**
* Check the request validity (HTTP method, request value, etc.),
* that the API is enabled, and the JWT token validity.
*
* @param Request $request Slim request
*
* @throws ApiAuthorizationException The API is disabled or the token is invalid.
*/
protected function checkRequest($request)
{
if (! $this->conf->get('api.enabled', true)) {
throw new ApiAuthorizationException('API is disabled');
}
$this->checkToken($request);
}
/**
* Check that the JWT token is set and valid.
* The API secret setting must be set.
*
* @param Request $request Slim request
*
* @throws ApiAuthorizationException The token couldn't be validated.
*/
protected function checkToken($request) {
if (! $request->hasHeader('Authorization')) {
throw new ApiAuthorizationException('JWT token not provided');
}
if (empty($this->conf->get('api.secret'))) {
throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration');
}
$authorization = $request->getHeaderLine('Authorization');
if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) {
throw new ApiAuthorizationException('Invalid JWT header');
}
ApiUtils::validateJwtToken($matches[1], $this->conf->get('api.secret'));
}
/**
* Instantiate a new LinkDB including private links,
* and load in the Slim container.
*
* FIXME! LinkDB could use a refactoring to avoid this trick.
*
* @param ConfigManager $conf instance.
*/
protected function setLinkDb($conf)
{
$linkDb = new \LinkDB(
$conf->get('resource.datastore'),
true,
$conf->get('privacy.hide_public_links'),
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
);
$this->container['db'] = $linkDb;
}
}

View file

@ -0,0 +1,137 @@
<?php
namespace Shaarli\Api;
use Shaarli\Base64Url;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
/**
* REST API utilities
*/
class ApiUtils
{
/**
* Validates a JWT token authenticity.
*
* @param string $token JWT token extracted from the headers.
* @param string $secret API secret set in the settings.
*
* @throws ApiAuthorizationException the token is not valid.
*/
public static function validateJwtToken($token, $secret)
{
$parts = explode('.', $token);
if (count($parts) != 3 || strlen($parts[0]) == 0 || strlen($parts[1]) == 0) {
throw new ApiAuthorizationException('Malformed JWT token');
}
$genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
if ($parts[2] != $genSign) {
throw new ApiAuthorizationException('Invalid JWT signature');
}
$header = json_decode(Base64Url::decode($parts[0]));
if ($header === null) {
throw new ApiAuthorizationException('Invalid JWT header');
}
$payload = json_decode(Base64Url::decode($parts[1]));
if ($payload === null) {
throw new ApiAuthorizationException('Invalid JWT payload');
}
if (empty($payload->iat)
|| $payload->iat > time()
|| time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION
) {
throw new ApiAuthorizationException('Invalid JWT issued time');
}
}
/**
* Format a Link for the REST API.
*
* @param array $link Link data read from the datastore.
* @param string $indexUrl Shaarli's index URL (used for relative URL).
*
* @return array Link data formatted for the REST API.
*/
public static function formatLink($link, $indexUrl)
{
$out['id'] = $link['id'];
// Not an internal link
if ($link['url'][0] != '?') {
$out['url'] = $link['url'];
} else {
$out['url'] = $indexUrl . $link['url'];
}
$out['shorturl'] = $link['shorturl'];
$out['title'] = $link['title'];
$out['description'] = $link['description'];
$out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
$out['private'] = $link['private'] == true;
$out['created'] = $link['created']->format(\DateTime::ATOM);
if (! empty($link['updated'])) {
$out['updated'] = $link['updated']->format(\DateTime::ATOM);
} else {
$out['updated'] = '';
}
return $out;
}
/**
* Convert a link given through a request, to a valid link for LinkDB.
*
* If no URL is provided, it will generate a local note URL.
* If no title is provided, it will use the URL as title.
*
* @param array $input Request Link.
* @param bool $defaultPrivate Request Link.
*
* @return array Formatted link.
*/
public static function buildLinkFromRequest($input, $defaultPrivate)
{
$input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
if (isset($input['private'])) {
$private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
} else {
$private = $defaultPrivate;
}
$link = [
'title' => ! empty($input['title']) ? $input['title'] : $input['url'],
'url' => $input['url'],
'description' => ! empty($input['description']) ? $input['description'] : '',
'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
'private' => $private,
'created' => new \DateTime(),
];
return $link;
}
/**
* Update link fields using an updated link object.
*
* @param array $oldLink data
* @param array $newLink data
*
* @return array $oldLink updated with $newLink values
*/
public static function updateLink($oldLink, $newLink)
{
foreach (['title', 'url', 'description', 'tags', 'private'] as $field) {
$oldLink[$field] = $newLink[$field];
}
$oldLink['updated'] = new \DateTime();
if (empty($oldLink['url'])) {
$oldLink['url'] = '?' . $oldLink['shorturl'];
}
if (empty($oldLink['title'])) {
$oldLink['title'] = $oldLink['url'];
}
return $oldLink;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Shaarli\Api\Controllers;
use Shaarli\Config\ConfigManager;
use \Slim\Container;
/**
* Abstract Class ApiController
*
* Defines REST API Controller dependencies injected from the container.
*
* @package Api\Controllers
*/
abstract class ApiController
{
/**
* @var Container
*/
protected $ci;
/**
* @var ConfigManager
*/
protected $conf;
/**
* @var \LinkDB
*/
protected $linkDb;
/**
* @var \History
*/
protected $history;
/**
* @var int|null JSON style option.
*/
protected $jsonStyle;
/**
* ApiController constructor.
*
* Note: enabling debug mode displays JSON with readable formatting.
*
* @param Container $ci Slim container.
*/
public function __construct(Container $ci)
{
$this->ci = $ci;
$this->conf = $ci->get('conf');
$this->linkDb = $ci->get('db');
$this->history = $ci->get('history');
if ($this->conf->get('dev.debug', false)) {
$this->jsonStyle = JSON_PRETTY_PRINT;
} else {
$this->jsonStyle = null;
}
}
/**
* Get the container.
*
* @return Container
*/
public function getCi()
{
return $this->ci;
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Shaarli\Api\Controllers;
use Shaarli\Api\Exceptions\ApiBadParametersException;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class History
*
* REST API Controller: /history
*
* @package Shaarli\Api\Controllers
*/
class History extends ApiController
{
/**
* Service providing operation regarding Shaarli datastore and settings.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
*
* @return Response response.
*
* @throws ApiBadParametersException Invalid parameters.
*/
public function getHistory($request, $response)
{
$history = $this->history->getHistory();
// Return history operations from the {offset}th, starting from {since}.
$since = \DateTime::createFromFormat(\DateTime::ATOM, $request->getParam('since'));
$offset = $request->getParam('offset');
if (empty($offset)) {
$offset = 0;
}
else if (ctype_digit($offset)) {
$offset = (int) $offset;
} else {
throw new ApiBadParametersException('Invalid offset');
}
// limit parameter is either a number of links or 'all' for everything.
$limit = $request->getParam('limit');
if (empty($limit)) {
$limit = count($history);
} else if (ctype_digit($limit)) {
$limit = (int) $limit;
} else {
throw new ApiBadParametersException('Invalid limit');
}
$out = [];
$i = 0;
foreach ($history as $entry) {
if ((! empty($since) && $entry['datetime'] <= $since) || count($out) >= $limit) {
break;
}
if (++$i > $offset) {
$out[$i] = $entry;
$out[$i]['datetime'] = $out[$i]['datetime']->format(\DateTime::ATOM);
}
}
$out = array_values($out);
return $response->withJson($out, 200, $this->jsonStyle);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Shaarli\Api\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class Info
*
* REST API Controller: /info
*
* @package Api\Controllers
* @see http://shaarli.github.io/api-documentation/#links-instance-information-get
*/
class Info extends ApiController
{
/**
* Service providing various information about Shaarli instance.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
*
* @return Response response.
*/
public function getInfo($request, $response)
{
$info = [
'global_counter' => count($this->linkDb),
'private_counter' => count_private($this->linkDb),
'settings' => array(
'title' => $this->conf->get('general.title', 'Shaarli'),
'header_link' => $this->conf->get('general.header_link', '?'),
'timezone' => $this->conf->get('general.timezone', 'UTC'),
'enabled_plugins' => $this->conf->get('general.enabled_plugins', []),
'default_private_links' => $this->conf->get('privacy.default_private_links', false),
),
];
return $response->withJson($info, 200, $this->jsonStyle);
}
}

View file

@ -0,0 +1,217 @@
<?php
namespace Shaarli\Api\Controllers;
use Shaarli\Api\ApiUtils;
use Shaarli\Api\Exceptions\ApiBadParametersException;
use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class Links
*
* REST API Controller: all services related to links collection.
*
* @package Api\Controllers
* @see http://shaarli.github.io/api-documentation/#links-links-collection
*/
class Links extends ApiController
{
/**
* @var int Number of links returned if no limit is provided.
*/
public static $DEFAULT_LIMIT = 20;
/**
* Retrieve a list of links, allowing different filters.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
*
* @return Response response.
*
* @throws ApiBadParametersException Invalid parameters.
*/
public function getLinks($request, $response)
{
$private = $request->getParam('visibility');
$links = $this->linkDb->filterSearch(
[
'searchtags' => $request->getParam('searchtags', ''),
'searchterm' => $request->getParam('searchterm', ''),
],
false,
$private
);
// Return links from the {offset}th link, starting from 0.
$offset = $request->getParam('offset');
if (! empty($offset) && ! ctype_digit($offset)) {
throw new ApiBadParametersException('Invalid offset');
}
$offset = ! empty($offset) ? intval($offset) : 0;
if ($offset > count($links)) {
return $response->withJson([], 200, $this->jsonStyle);
}
// limit parameter is either a number of links or 'all' for everything.
$limit = $request->getParam('limit');
if (empty($limit)) {
$limit = self::$DEFAULT_LIMIT;
} else if (ctype_digit($limit)) {
$limit = intval($limit);
} else if ($limit === 'all') {
$limit = count($links);
} else {
throw new ApiBadParametersException('Invalid limit');
}
// 'environment' is set by Slim and encapsulate $_SERVER.
$index = index_url($this->ci['environment']);
$out = [];
$cpt = 0;
foreach ($links as $link) {
if (count($out) >= $limit) {
break;
}
if ($cpt++ >= $offset) {
$out[] = ApiUtils::formatLink($link, $index);
}
}
return $response->withJson($out, 200, $this->jsonStyle);
}
/**
* Return a single formatted link by its ID.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
* @param array $args Path parameters. including the ID.
*
* @return Response containing the link array.
*
* @throws ApiLinkNotFoundException generating a 404 error.
*/
public function getLink($request, $response, $args)
{
if (!isset($this->linkDb[$args['id']])) {
throw new ApiLinkNotFoundException();
}
$index = index_url($this->ci['environment']);
$out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
return $response->withJson($out, 200, $this->jsonStyle);
}
/**
* Creates a new link from posted request body.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
*
* @return Response response.
*/
public function postLink($request, $response)
{
$data = $request->getParsedBody();
$link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
// duplicate by URL, return 409 Conflict
if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
return $response->withJson(
ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
409,
$this->jsonStyle
);
}
$link['id'] = $this->linkDb->getNextId();
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
// note: general relative URL
if (empty($link['url'])) {
$link['url'] = '?' . $link['shorturl'];
}
if (empty($link['title'])) {
$link['title'] = $link['url'];
}
$this->linkDb[$link['id']] = $link;
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->addLink($link);
$out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
$redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
return $response->withAddedHeader('Location', $redirect)
->withJson($out, 201, $this->jsonStyle);
}
/**
* Updates an existing link from posted request body.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
* @param array $args Path parameters. including the ID.
*
* @return Response response.
*
* @throws ApiLinkNotFoundException generating a 404 error.
*/
public function putLink($request, $response, $args)
{
if (! isset($this->linkDb[$args['id']])) {
throw new ApiLinkNotFoundException();
}
$index = index_url($this->ci['environment']);
$data = $request->getParsedBody();
$requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
// duplicate URL on a different link, return 409 Conflict
if (! empty($requestLink['url'])
&& ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url']))
&& $dup['id'] != $args['id']
) {
return $response->withJson(
ApiUtils::formatLink($dup, $index),
409,
$this->jsonStyle
);
}
$responseLink = $this->linkDb[$args['id']];
$responseLink = ApiUtils::updateLink($responseLink, $requestLink);
$this->linkDb[$responseLink['id']] = $responseLink;
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->updateLink($responseLink);
$out = ApiUtils::formatLink($responseLink, $index);
return $response->withJson($out, 200, $this->jsonStyle);
}
/**
* Delete an existing link by its ID.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
* @param array $args Path parameters. including the ID.
*
* @return Response response.
*
* @throws ApiLinkNotFoundException generating a 404 error.
*/
public function deleteLink($request, $response, $args)
{
if (! isset($this->linkDb[$args['id']])) {
throw new ApiLinkNotFoundException();
}
$link = $this->linkDb[$args['id']];
unset($this->linkDb[(int) $args['id']]);
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->deleteLink($link);
return $response->withStatus(204);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Shaarli\Api\Exceptions;
/**
* Class ApiAuthorizationException
*
* Request not authorized, return a 401 HTTP code.
*/
class ApiAuthorizationException extends ApiException
{
/**
* {@inheritdoc}
*/
public function getApiResponse()
{
$this->setMessage('Not authorized');
return $this->buildApiResponse(401);
}
/**
* Set the exception message.
*
* We only return a generic error message in production mode to avoid giving
* to much security information.
*
* @param $message string the exception message.
*/
public function setMessage($message)
{
$original = $this->debug === true ? ': '. $this->getMessage() : '';
$this->message = $message . $original;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Shaarli\Api\Exceptions;
/**
* Class ApiBadParametersException
*
* Invalid request exception, return a 400 HTTP code.
*/
class ApiBadParametersException extends ApiException
{
/**
* {@inheritdoc}
*/
public function getApiResponse()
{
return $this->buildApiResponse(400);
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Shaarli\Api\Exceptions;
use Slim\Http\Response;
/**
* Abstract class ApiException
*
* Parent Exception related to the API, able to generate a valid Response (ResponseInterface).
* Also can include various information in debug mode.
*/
abstract class ApiException extends \Exception {
/**
* @var Response instance from Slim.
*/
protected $response;
/**
* @var bool Debug mode enabled/disabled.
*/
protected $debug;
/**
* Build the final response.
*
* @return Response Final response to give.
*/
public abstract function getApiResponse();
/**
* Creates ApiResponse body.
* In production mode, it will only return the exception message,
* but in dev mode, it includes additional information in an array.
*
* @return array|string response body
*/
protected function getApiResponseBody() {
if ($this->debug !== true) {
return $this->getMessage();
}
return [
'message' => $this->getMessage(),
'stacktrace' => get_class($this) .': '. $this->getTraceAsString()
];
}
/**
* Build the Response object to return.
*
* @param int $code HTTP status.
*
* @return Response with status + body.
*/
protected function buildApiResponse($code)
{
$style = $this->debug ? JSON_PRETTY_PRINT : null;
return $this->response->withJson($this->getApiResponseBody(), $code, $style);
}
/**
* @param Response $response
*/
public function setResponse($response)
{
$this->response = $response;
}
/**
* @param bool $debug
*/
public function setDebug($debug)
{
$this->debug = $debug;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Shaarli\Api\Exceptions;
/**
* Class ApiInternalException
*
* Generic exception, return a 500 HTTP code.
*/
class ApiInternalException extends ApiException
{
/**
* @inheritdoc
*/
public function getApiResponse()
{
return $this->buildApiResponse(500);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Shaarli\Api\Exceptions;
use Slim\Http\Response;
/**
* Class ApiLinkNotFoundException
*
* Link selected by ID couldn't be found, results in a 404 error.
*
* @package Shaarli\Api\Exceptions
*/
class ApiLinkNotFoundException extends ApiException
{
/**
* ApiLinkNotFoundException constructor.
*/
public function __construct()
{
$this->message = 'Link not found';
}
/**
* {@inheritdoc}
*/
public function getApiResponse()
{
return $this->buildApiResponse(404);
}
}

View file

@ -1,4 +1,5 @@
<?php <?php
namespace Shaarli\Config;
/** /**
* Interface ConfigIO * Interface ConfigIO
@ -14,7 +15,7 @@ interface ConfigIO
* *
* @return array All configuration in an array. * @return array All configuration in an array.
*/ */
function read($filepath); public function read($filepath);
/** /**
* Write configuration. * Write configuration.
@ -22,12 +23,12 @@ function read($filepath);
* @param string $filepath Config file absolute path. * @param string $filepath Config file absolute path.
* @param array $conf All configuration in an array. * @param array $conf All configuration in an array.
*/ */
function write($filepath, $conf); public function write($filepath, $conf);
/** /**
* Get config file extension according to config type. * Get config file extension according to config type.
* *
* @return string Config file extension. * @return string Config file extension.
*/ */
function getExtension(); public function getExtension();
} }

View file

@ -1,4 +1,5 @@
<?php <?php
namespace Shaarli\Config;
/** /**
* Class ConfigJson (ConfigIO implementation) * Class ConfigJson (ConfigIO implementation)
@ -10,7 +11,7 @@ class ConfigJson implements ConfigIO
/** /**
* @inheritdoc * @inheritdoc
*/ */
function read($filepath) public function read($filepath)
{ {
if (! is_readable($filepath)) { if (! is_readable($filepath)) {
return array(); return array();
@ -20,8 +21,14 @@ function read($filepath)
$data = str_replace(self::getPhpSuffix(), '', $data); $data = str_replace(self::getPhpSuffix(), '', $data);
$data = json_decode($data, true); $data = json_decode($data, true);
if ($data === null) { if ($data === null) {
$error = json_last_error(); $errorCode = json_last_error();
throw new Exception('An error occurred while parsing JSON file: error code #'. $error); $error = 'An error occurred while parsing JSON configuration file ('. $filepath .'): error code #';
$error .= $errorCode. '<br>➜ <code>' . json_last_error_msg() .'</code>';
if ($errorCode === JSON_ERROR_SYNTAX) {
$error .= '<br>Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as ';
$error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.';
}
throw new \Exception($error);
} }
return $data; return $data;
} }
@ -29,13 +36,13 @@ function read($filepath)
/** /**
* @inheritdoc * @inheritdoc
*/ */
function write($filepath, $conf) public function write($filepath, $conf)
{ {
// JSON_PRETTY_PRINT is available from PHP 5.4. // JSON_PRETTY_PRINT is available from PHP 5.4.
$print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
$data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix(); $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix();
if (!file_put_contents($filepath, $data)) { if (!file_put_contents($filepath, $data)) {
throw new IOException( throw new \IOException(
$filepath, $filepath,
'Shaarli could not create the config file. 'Shaarli could not create the config file.
Please make sure Shaarli has the right to write in the folder is it installed in.' Please make sure Shaarli has the right to write in the folder is it installed in.'
@ -46,7 +53,7 @@ function write($filepath, $conf)
/** /**
* @inheritdoc * @inheritdoc
*/ */
function getExtension() public function getExtension()
{ {
return '.json.php'; return '.json.php';
} }

View file

@ -1,9 +1,8 @@
<?php <?php
namespace Shaarli\Config;
// FIXME! Namespaces... use Shaarli\Config\Exception\MissingFieldConfigException;
require_once 'ConfigIO.php'; use Shaarli\Config\Exception\UnauthorizedConfigException;
require_once 'ConfigJson.php';
require_once 'ConfigPhp.php';
/** /**
* Class ConfigManager * Class ConfigManager
@ -20,6 +19,8 @@ class ConfigManager
*/ */
protected static $NOT_FOUND = 'NOT_FOUND'; protected static $NOT_FOUND = 'NOT_FOUND';
public static $DEFAULT_PLUGINS = array('qrcode');
/** /**
* @var string Config folder. * @var string Config folder.
*/ */
@ -80,7 +81,11 @@ protected function initialize()
*/ */
protected function load() protected function load()
{ {
$this->loadedConfig = $this->configIO->read($this->getConfigFileExt()); try {
$this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
} catch (\Exception $e) {
die($e->getMessage());
}
$this->setDefaultValues(); $this->setDefaultValues();
} }
@ -122,12 +127,12 @@ public function get($setting, $default = '')
* @param bool $write Write the new setting in the config file, default false. * @param bool $write Write the new setting in the config file, default false.
* @param bool $isLoggedIn User login state, default false. * @param bool $isLoggedIn User login state, default false.
* *
* @throws Exception Invalid * @throws \Exception Invalid
*/ */
public function set($setting, $value, $write = false, $isLoggedIn = false) public function set($setting, $value, $write = false, $isLoggedIn = false)
{ {
if (empty($setting) || ! is_string($setting)) { if (empty($setting) || ! is_string($setting)) {
throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting)); throw new \Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
} }
// During the ConfigIO transition, map legacy settings to the new ones. // During the ConfigIO transition, map legacy settings to the new ones.
@ -175,7 +180,7 @@ public function exists($setting)
* *
* @throws MissingFieldConfigException: a mandatory field has not been provided in $conf. * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
* @throws UnauthorizedConfigException: user is not authorize to change configuration. * @throws UnauthorizedConfigException: user is not authorize to change configuration.
* @throws IOException: an error occurred while writing the new config file. * @throws \IOException: an error occurred while writing the new config file.
*/ */
public function write($isLoggedIn) public function write($isLoggedIn)
{ {
@ -296,7 +301,9 @@ protected function setDefaultValues()
$this->setEmpty('resource.updates', 'data/updates.txt'); $this->setEmpty('resource.updates', 'data/updates.txt');
$this->setEmpty('resource.log', 'data/log.txt'); $this->setEmpty('resource.log', 'data/log.txt');
$this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
$this->setEmpty('resource.history', 'data/history.php');
$this->setEmpty('resource.raintpl_tpl', 'tpl/'); $this->setEmpty('resource.raintpl_tpl', 'tpl/');
$this->setEmpty('resource.theme', 'default');
$this->setEmpty('resource.raintpl_tmp', 'tmp/'); $this->setEmpty('resource.raintpl_tmp', 'tmp/');
$this->setEmpty('resource.thumbnails_cache', 'cache'); $this->setEmpty('resource.thumbnails_cache', 'cache');
$this->setEmpty('resource.page_cache', 'pagecache'); $this->setEmpty('resource.page_cache', 'pagecache');
@ -308,14 +315,14 @@ protected function setDefaultValues()
$this->setEmpty('general.header_link', '?'); $this->setEmpty('general.header_link', '?');
$this->setEmpty('general.links_per_page', 20); $this->setEmpty('general.links_per_page', 20);
$this->setEmpty('general.enabled_plugins', array('qrcode')); $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
$this->setEmpty('updates.check_updates', false); $this->setEmpty('updates.check_updates', false);
$this->setEmpty('updates.check_updates_branch', 'stable'); $this->setEmpty('updates.check_updates_branch', 'stable');
$this->setEmpty('updates.check_updates_interval', 86400); $this->setEmpty('updates.check_updates_interval', 86400);
$this->setEmpty('feed.rss_permalinks', true); $this->setEmpty('feed.rss_permalinks', true);
$this->setEmpty('feed.show_atom', false); $this->setEmpty('feed.show_atom', true);
$this->setEmpty('privacy.default_private_links', false); $this->setEmpty('privacy.default_private_links', false);
$this->setEmpty('privacy.hide_public_links', false); $this->setEmpty('privacy.hide_public_links', false);
@ -359,36 +366,3 @@ public function setConfigIO($configIO)
$this->configIO = $configIO; $this->configIO = $configIO;
} }
} }
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}

View file

@ -1,4 +1,5 @@
<?php <?php
namespace Shaarli\Config;
/** /**
* Class ConfigPhp (ConfigIO implementation) * Class ConfigPhp (ConfigIO implementation)
@ -41,6 +42,7 @@ class ConfigPhp implements ConfigIO
'resource.log' => 'config.LOG_FILE', 'resource.log' => 'config.LOG_FILE',
'resource.update_check' => 'config.UPDATECHECK_FILENAME', 'resource.update_check' => 'config.UPDATECHECK_FILENAME',
'resource.raintpl_tpl' => 'config.RAINTPL_TPL', 'resource.raintpl_tpl' => 'config.RAINTPL_TPL',
'resource.theme' => 'config.theme',
'resource.raintpl_tmp' => 'config.RAINTPL_TMP', 'resource.raintpl_tmp' => 'config.RAINTPL_TMP',
'resource.thumbnails_cache' => 'config.CACHEDIR', 'resource.thumbnails_cache' => 'config.CACHEDIR',
'resource.page_cache' => 'config.PAGECACHE', 'resource.page_cache' => 'config.PAGECACHE',
@ -71,7 +73,7 @@ class ConfigPhp implements ConfigIO
/** /**
* @inheritdoc * @inheritdoc
*/ */
function read($filepath) public function read($filepath)
{ {
if (! file_exists($filepath) || ! is_readable($filepath)) { if (! file_exists($filepath) || ! is_readable($filepath)) {
return array(); return array();
@ -91,7 +93,7 @@ function read($filepath)
/** /**
* @inheritdoc * @inheritdoc
*/ */
function write($filepath, $conf) public function write($filepath, $conf)
{ {
$configStr = '<?php '. PHP_EOL; $configStr = '<?php '. PHP_EOL;
foreach (self::$ROOT_KEYS as $key) { foreach (self::$ROOT_KEYS as $key) {
@ -114,7 +116,7 @@ function write($filepath, $conf)
if (!file_put_contents($filepath, $configStr) if (!file_put_contents($filepath, $configStr)
|| strcmp(file_get_contents($filepath), $configStr) != 0 || strcmp(file_get_contents($filepath), $configStr) != 0
) { ) {
throw new IOException( throw new \IOException(
$filepath, $filepath,
'Shaarli could not create the config file. 'Shaarli could not create the config file.
Please make sure Shaarli has the right to write in the folder is it installed in.' Please make sure Shaarli has the right to write in the folder is it installed in.'
@ -125,7 +127,7 @@ function write($filepath, $conf)
/** /**
* @inheritdoc * @inheritdoc
*/ */
function getExtension() public function getExtension()
{ {
return '.php'; return '.php';
} }

View file

@ -1,4 +1,7 @@
<?php <?php
use Shaarli\Config\Exception\PluginConfigOrderException;
/** /**
* Plugin configuration helper functions. * Plugin configuration helper functions.
* *
@ -108,17 +111,3 @@ function load_plugin_parameter_values($plugins, $conf)
return $out; return $out;
} }
/**
* Exception used if an error occur while saving plugin configuration.
*/
class PluginConfigOrderException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'An error occurred while trying to save plugins loading order.';
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Shaarli\Config\Exception;
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends \Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Shaarli\Config\Exception;
/**
* Exception used if an error occur while saving plugin configuration.
*/
class PluginConfigOrderException extends \Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'An error occurred while trying to save plugins loading order.';
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Shaarli\Config\Exception;
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends \Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* Exception class thrown when a filesystem access failure happens
*/
class IOException extends Exception
{
private $path;
/**
* Construct a new IOException
*
* @param string $path path to the resource that cannot be accessed
* @param string $message Custom exception message.
*/
public function __construct($path, $message = '')
{
$this->path = $path;
$this->message = empty($message) ? 'Error accessing' : $message;
$this->message .= ' "' . $this->path .'"';
}
}

View file

@ -10,14 +10,27 @@
}, },
"keywords": ["bookmark", "link", "share", "web"], "keywords": ["bookmark", "link", "share", "web"],
"require": { "require": {
"php": ">=5.3.4", "php": ">=5.5",
"shaarli/netscape-bookmark-parser": "1.*", "shaarli/netscape-bookmark-parser": "^2.0",
"erusev/parsedown": "1.6" "erusev/parsedown": "1.6",
"slim/slim": "^3.0",
"pubsubhubbub/publisher": "dev-master"
}, },
"require-dev": { "require-dev": {
"phpmd/phpmd" : "@stable", "phpmd/phpmd" : "@stable",
"phpunit/phpunit": "4.8.*", "phpunit/phpunit": "4.8.*",
"sebastian/phpcpd": "*", "sebastian/phpcpd": "*",
"squizlabs/php_codesniffer": "2.*" "squizlabs/php_codesniffer": "2.*",
"phpunit/phpcov": "*"
},
"autoload": {
"psr-4": {
"Shaarli\\": "application",
"Shaarli\\Api\\": "application/api/",
"Shaarli\\Api\\Controllers\\": "application/api/controllers",
"Shaarli\\Api\\Exceptions\\": "application/api/exceptions",
"Shaarli\\Config\\": "application/config/",
"Shaarli\\Config\\Exception\\": "application/config/exception"
}
} }
} }

2424
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -64,7 +66,6 @@
</div> </div>
<h1 id="browsing-and-searching">Browsing and searching</h1> <h1 id="browsing-and-searching">Browsing and searching</h1>
<h1 id="browsing-and-searching-1">Browsing and Searching</h1> <h1 id="browsing-and-searching-1">Browsing and Searching</h1>
<p>Status: DRAFT</p>
<p><embed src="(http://pix.toile-libre.org/upload/original/1455571378.png).html" /></p> <p><embed src="(http://pix.toile-libre.org/upload/original/1455571378.png).html" /></p>
<h2 id="plain-text-search">Plain text search</h2> <h2 id="plain-text-search">Plain text search</h2>
<p>Use the <code>Search text</code> field to search in <em>any</em> of the fields of all links (Title, URL, Description...)</p> <p>Use the <code>Search text</code> field to search in <em>any</em> of the fields of all links (Title, URL, Description...)</p>
@ -75,6 +76,7 @@ <h2 id="tags-search">Tags search</h2>
<p>Use the <code>Filter by tags</code> field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags).</p> <p>Use the <code>Filter by tags</code> field to restrict displayed links to entries tagged with one or multiple tags (use space to separate tags).</p>
<p><strong>Hidden tags:</strong> Tags starting with a dot <code>.</code> (example <code>.secret</code>) are private. They can only be seen and searched when logged in.</p> <p><strong>Hidden tags:</strong> Tags starting with a dot <code>.</code> (example <code>.secret</code>) are private. They can only be seen and searched when logged in.</p>
<p>Alternatively you can use the <code>Tag cloud</code> to discover all tags and click on any of them to display related links.</p> <p>Alternatively you can use the <code>Tag cloud</code> to discover all tags and click on any of them to display related links.</p>
<p>To search for links that are not tagged, enter <code>&quot;&quot;</code> in the tag search field.</p>
<h2 id="filtering-rss-feedspicture-wall">Filtering RSS feeds/Picture wall</h2> <h2 id="filtering-rss-feedspicture-wall">Filtering RSS feeds/Picture wall</h2>
<p>RSS feeds can also be restricted to only return items matching a text/tag search: see <a href="RSS-feeds.html">RSS feeds</a>.</p> <p>RSS feeds can also be restricted to only return items matching a text/tag search: see <a href="RSS-feeds.html">RSS feeds</a>.</p>
</body> </body>

View file

@ -1,8 +1,6 @@
#Browsing and searching #Browsing and searching
# Browsing and Searching # Browsing and Searching
Status: DRAFT
![(http://pix.toile-libre.org/upload/original/1455571378.png)]((http://pix.toile-libre.org/upload/original/1455571378.png).html) ![(http://pix.toile-libre.org/upload/original/1455571378.png)]((http://pix.toile-libre.org/upload/original/1455571378.png).html)
## Plain text search ## Plain text search
@ -23,6 +21,8 @@ Use the `Filter by tags` field to restrict displayed links to entries tagged wit
Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links. Alternatively you can use the `Tag cloud` to discover all tags and click on any of them to display related links.
To search for links that are not tagged, enter `""` in the tag search field.
## Filtering RSS feeds/Picture wall ## Filtering RSS feeds/Picture wall
RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds.html). RSS feeds can also be restricted to only return items matching a text/tag search: see [RSS feeds](RSS-feeds.html).

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -77,12 +79,20 @@ <h2 id="community">Community</h2>
<li><a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history">Original revisions history</a><a href=".html"></a></li> <li><a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history">Original revisions history</a><a href=".html"></a></li>
<li><a href="https://www.shaarli.fr/my.php">Shaarli.fr/my</a> - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of <a href="https://github.com/DMeloni">DMeloni</a><a href=".html"></a></li> <li><a href="https://www.shaarli.fr/my.php">Shaarli.fr/my</a> - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of <a href="https://github.com/DMeloni">DMeloni</a><a href=".html"></a></li>
</ul> </ul>
<h3 id="articles-and-social-media-discussions">Articles and social media discussions</h3>
<ul>
<li>2016-09-22 - Hacker News - <a href="https://news.ycombinator.com/item?id=12552176" class="uri">https://news.ycombinator.com/item?id=12552176</a></li>
<li>2015-08-15 - Reddit - <a href="https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/">Question about migrating from WordPress to Shaarli.</a><a href=".html"></a></li>
<li>2015-06-22 - Hacker News - <a href="https://news.ycombinator.com/item?id=9755366" class="uri">https://news.ycombinator.com/item?id=9755366</a></li>
<li>2015-05-12 - Reddit - <a href="https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/">shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)</a><a href=".html"></a></li>
</ul>
<h3 id="third-party-plugins">Third party plugins</h3> <h3 id="third-party-plugins">Third party plugins</h3>
<ul> <ul>
<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> <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>
<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> <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>
<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> <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>
<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> <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>
<li><a href="https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin">google analytics</a> by <a href="http://github.com/ericjuden">@ericjuden</a>: Adds Google Analytics tracking support<a href=".html"></a></li>
<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> <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>
<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> <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>
<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> <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>

View file

@ -14,6 +14,11 @@ _TODO: contact repos owners to see if they'd like to standardize their work with
- [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)[](.html) - [Original revisions history](http://sebsauvage.net/wiki/doku.php?id=php:shaarli:history)[](.html)
- [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)[](.html) - [Shaarli.fr/my](https://www.shaarli.fr/my.php) - Unofficial, unsupported (old fork) hosted Shaarlis provider, courtesy of [DMeloni](https://github.com/DMeloni)[](.html)
### Articles and social media discussions
- 2016-09-22 - Hacker News - https://news.ycombinator.com/item?id=12552176
- 2015-08-15 - Reddit - [Question about migrating from WordPress to Shaarli.](https://www.reddit.com/r/selfhosted/comments/3h3zwh/question_about_migrating_from_wordpress_to_shaarli/)[](.html)
- 2015-06-22 - Hacker News - https://news.ycombinator.com/item?id=9755366
- 2015-05-12 - Reddit - [shaarli - Self hosted Bookmarking / Delicious (PHP, MySQL)](https://www.reddit.com/r/selfhosted/comments/35pkkc/shaarli_self_hosted_bookmarking_delicious_php/)[](.html)
### Third party plugins ### Third party plugins
@ -22,6 +27,7 @@ _TODO: contact repos owners to see if they'd like to standardize their work with
* [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html)
* [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html) * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html)
* [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html) * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html)
* [google analytics](https://github.com/ericjuden/Shaarli-Google-Analytics-Plugin) by [@ericjuden](http://github.com/ericjuden): Adds Google Analytics tracking support[](.html)
* [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html)
* [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
* [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html) * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html)

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -101,33 +103,33 @@
</div> </div>
<h1 id="directory-structure">Directory structure</h1> <h1 id="directory-structure">Directory structure</h1>
<p>Here is the directory structure of Shaarli and the purpose of the different files:</p> <p>Here is the directory structure of Shaarli and the purpose of the different files:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="ex">index.php</span> <span class="co"># Main program</span> <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="ex">index.php</span> # Main program
<span class="ex">application/</span> <span class="co"># Shaarli classes</span> <span class="ex">application/</span> # Shaarli classes
├── <span class="ex">LinkDB.php</span> ├── <span class="ex">LinkDB.php</span>
└── <span class="ex">Utils.php</span> └── <span class="ex">Utils.php</span>
<span class="ex">tests/</span> <span class="co"># Shaarli unitary &amp; functional tests</span> <span class="ex">tests/</span> # Shaarli unitary <span class="kw">&amp;</span> <span class="ex">functional</span> tests
├── <span class="ex">LinkDBTest.php</span> ├── <span class="ex">LinkDBTest.php</span>
├── <span class="ex">utils</span> <span class="co"># utilities to ease testing</span> ├── <span class="ex">utils</span> # utilities to ease testing
│ └── <span class="ex">ReferenceLinkDB.php</span> │ └── <span class="ex">ReferenceLinkDB.php</span>
└── <span class="ex">UtilsTest.php</span> └── <span class="ex">UtilsTest.php</span>
<span class="ex">COPYING</span> <span class="co"># Shaarli license</span> <span class="ex">COPYING</span> # Shaarli license
<span class="ex">inc/</span> <span class="co"># static assets and 3rd party libraries</span> <span class="ex">inc/</span> # static assets and 3rd party libraries
├── <span class="ex">awesomplete.*</span> <span class="co"># tags autocompletion library</span> ├── <span class="ex">awesomplete.*</span> # tags autocompletion library
├── <span class="ex">blazy.*</span> <span class="co"># picture wall lazy image loading library</span> ├── <span class="ex">blazy.*</span> # picture wall lazy image loading library
├── <span class="ex">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span> ├── <span class="ex">shaarli.css</span>, reset.css <span class="co"># Shaarli stylesheet.</span>
├── <span class="ex">qr.*</span> <span class="co"># qr code generation library</span> ├── <span class="ex">qr.*</span> # qr code generation library
└──<span class="ex">rain.tpl.class.php</span> <span class="co"># RainTPL templating library</span> └──<span class="ex">rain.tpl.class.php</span> # RainTPL templating library
<span class="ex">tpl/</span> <span class="co"># RainTPL templates for Shaarli. They are used to build the pages.</span> <span class="ex">tpl/</span> # RainTPL templates for Shaarli. They are used to build the pages.
<span class="ex">images/</span> <span class="co"># Images and icons used in Shaarli</span> <span class="ex">images/</span> # Images and icons used in Shaarli
<span class="ex">data/</span> <span class="co"># data storage: bookmark database, configuration, logs, banlist…</span> <span class="ex">data/</span> # data storage: bookmark database, configuration, logs, banlist…
├── <span class="ex">config.php</span> <span class="co"># Shaarli configuration (login, password, timezone, title…)</span> ├── <span class="ex">config.php</span> # Shaarli configuration (login, password, timezone, title…)
├── <span class="ex">datastore.php</span> <span class="co"># Your link database (compressed).</span> ├── <span class="ex">datastore.php</span> # Your link database (compressed)<span class="ex">.</span>
├── <span class="ex">ipban.php</span> <span class="co"># IP address ban system data</span> ├── <span class="ex">ipban.php</span> # IP address ban system data
├── <span class="ex">lastupdatecheck.txt</span> <span class="co"># Update check timestamp file</span> ├── <span class="ex">lastupdatecheck.txt</span> # Update check timestamp file
└──<span class="ex">log.txt</span> <span class="co"># login/IPban log.</span> └──<span class="ex">log.txt</span> # login/IPban log.
<span class="ex">cache/</span> <span class="co"># thumbnails cache</span> <span class="ex">cache/</span> # thumbnails cache
<span class="co"># This directory is automatically created. You can erase it anytime you want.</span> <span class="co"># This directory is automatically created. You can erase it anytime you want.</span>
<span class="ex">tmp/</span> <span class="co"># Temporary directory for compiled RainTPL templates.</span> <span class="ex">tmp/</span> # Temporary directory for compiled RainTPL templates.
<span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div> <span class="co"># This directory is automatically created. You can erase it anytime you want.</span></code></pre></div>
</body> </body>
</html> </html>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -200,7 +202,7 @@ <h3 id="create-and-start-a-new-container-from-the-image">Create and start a new
<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES <span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES
<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> <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>
<h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3> <h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3>
<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> <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">docker</span> stop backstabbing_galileo # those docker guys are really rude to physicists!
<span class="ex">backstabbing_galileo</span> <span class="ex">backstabbing_galileo</span>
<span class="co"># check the container is stopped</span> <span class="co"># check the container is stopped</span>
@ -213,14 +215,15 @@ <h3 id="stop-and-destroy-a-container">Stop and destroy a container</h3>
<span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="ex">48</span> seconds ago backstabbing_galileo <span class="ex">d40b7af693d6</span> shaarli/shaarli /usr/bin/supervisor 5 minutes ago Exited (0) <span class="ex">48</span> seconds ago backstabbing_galileo
<span class="co"># destroy the container</span> <span class="co"># destroy the container</span>
$ <span class="ex">docker</span> rm backstabbing_galileo <span class="co"># let&#39;s put an end to these barbarian practices</span> $ <span class="ex">docker</span> rm backstabbing_galileo # let<span class="st">&#39;s put an end to these barbarian practices</span>
<span class="ex">backstabbing_galileo</span> <span class="st">backstabbing_galileo</span>
$ <span class="ex">docker</span> ps -a <span class="st">$ docker ps -a</span>
<span class="ex">CONTAINER</span> ID IMAGE COMMAND CREATED STATUS PORTS NAMES</code></pre></div> <span class="st">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span></code></pre></div>
<h2 id="resources">Resources</h2> <h2 id="resources">Resources</h2>
<h3 id="docker-1">Docker</h3> <h3 id="docker-1">Docker</h3>
<ul> <ul>
<li><a href="https://www.katacoda.com/courses/docker/">Interactive Docker training portal</a> on <a href="https://www.katacoda.com/">Katakoda</a><a href=".html"></a></li>
<li><a href="http://blog.thoward37.me/articles/where-are-docker-images-stored/">Where are Docker images stored?</a><a href=".html"></a></li> <li><a href="http://blog.thoward37.me/articles/where-are-docker-images-stored/">Where are Docker images stored?</a><a href=".html"></a></li>
<li><a href="https://docs.docker.com/reference/builder/">Dockerfile reference</a><a href=".html"></a></li> <li><a href="https://docs.docker.com/reference/builder/">Dockerfile reference</a><a href=".html"></a></li>
<li><a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfile best practices</a><a href=".html"></a></li> <li><a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfile best practices</a><a href=".html"></a></li>

View file

@ -141,6 +141,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS
## Resources ## Resources
### Docker ### Docker
- [Interactive Docker training portal](https://www.katacoda.com/courses/docker/) on [Katakoda](https://www.katacoda.com/)[](.html)
- [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/)[](.html) - [Where are Docker images stored?](http://blog.thoward37.me/articles/where-are-docker-images-stored/)[](.html)
- [Dockerfile reference](https://docs.docker.com/reference/builder/)[](.html) - [Dockerfile reference](https://docs.docker.com/reference/builder/)[](.html)
- [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/)[](.html) - [Dockerfile best practices](https://docs.docker.com/articles/dockerfile_best-practices/)[](.html)

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -155,7 +157,7 @@ <h3 id="download-css-styles-for-shaarlis-listed-in-an-opml-file">Download CSS st
<span class="kw">function</span> copyUserStyleFrom<span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$name</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">)</span> { <span class="kw">function</span> copyUserStyleFrom<span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$name</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">)</span> {
<span class="kw">$userStyle</span> = <span class="kw">$url</span>.<span class="st">&quot;inc/user.css&quot;</span><span class="ot">;</span> <span class="kw">$userStyle</span> = <span class="kw">$url</span>.<span class="st">&quot;inc/user.css&quot;</span><span class="ot">;</span>
<span class="kw">if</span><span class="ot">(</span><span class="fu">in_array</span><span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">))</span> { <span class="kw">if</span><span class="ot">(</span><span class="fu">in_array</span><span class="ot">(</span><span class="kw">$url</span><span class="ot">,</span> <span class="kw">$knownStyles</span><span class="ot">))</span> {
<span class="co">// TODO add log message</span> <span class="co">// </span><span class="al">TODO</span><span class="co"> add log message</span>
} <span class="kw">else</span> { } <span class="kw">else</span> {
<span class="kw">$statusCode</span> = get_http_response_code<span class="ot">(</span><span class="kw">$userStyle</span><span class="ot">);</span> <span class="kw">$statusCode</span> = get_http_response_code<span class="ot">(</span><span class="kw">$userStyle</span><span class="ot">);</span>
<span class="kw">if</span><span class="ot">(</span><span class="fu">intval</span><span class="ot">(</span><span class="kw">$statusCode</span><span class="ot">)</span>&lt;<span class="dv">300</span><span class="ot">)</span> { <span class="kw">if</span><span class="ot">(</span><span class="fu">intval</span><span class="ot">(</span><span class="kw">$statusCode</span><span class="ot">)</span>&lt;<span class="dv">300</span><span class="ot">)</span> {

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -108,10 +110,10 @@ <h2 id="latest-release-recommended">Latest release (recommended)</h2>
<h3 id="download-as-an-archive">Download as an archive</h3> <h3 id="download-as-an-archive">Download as an archive</h3>
<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p> <p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
<p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p> <p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p>
<p>The current latest released version is <code>v0.8.0</code></p> <p>The current latest released version is <code>v0.8.4</code></p>
<p>Or in command lines:</p> <p>Or in command lines:</p>
<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 <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.4/shaarli-v0.8.4-full.zip
$ <span class="fu">unzip</span> shaarli-v0.8.0-full.zip $ <span class="fu">unzip</span> shaarli-v0.8.4-full.zip
$ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div> $ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div>
<table style="width:46%;"> <table style="width:46%;">
<colgroup> <colgroup>
@ -129,8 +131,8 @@ <h3 id="download-as-an-archive">Download as an archive</h3>
</table> </table>
<h3 id="using-git">Using git</h3> <h3 id="using-git">Using git</h3>
<pre><code>mkdir -p /path/to/shaarli &amp;&amp; cd /path/to/shaarli/ <pre><code>mkdir -p /path/to/shaarli &amp;&amp; cd /path/to/shaarli/
git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git . git clone -b v0.8 https://github.com/shaarli/Shaarli.git .
composer update --no-dev</code></pre> composer install --no-dev</code></pre>
<hr /> <hr />
<h2 id="stable-version">Stable version</h2> <h2 id="stable-version">Stable version</h2>
<p>The stable version has been experienced by Shaarli users, and will receive security updates.</p> <p>The stable version has been experienced by Shaarli users, and will receive security updates.</p>
@ -148,16 +150,16 @@ <h3 id="clone-with-git">Clone with Git</h3>
<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/ <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/
<span class="co"># install/update third-party dependencies</span> <span class="co"># install/update third-party dependencies</span>
$ <span class="bu">cd</span> /path/to/shaarli/ $ <span class="bu">cd</span> /path/to/shaarli/
$ <span class="ex">composer</span> update --no-dev</code></pre></div> $ <span class="ex">composer</span> install --no-dev</code></pre></div>
<hr /> <hr />
<h2 id="development-version-mainline">Development version (mainline)</h2> <h2 id="development-version-mainline">Development version (mainline)</h2>
<p><em>Use at your own risk!</em></p> <p><em>Use at your own risk!</em></p>
<p>To get the latest changes from the <code>master</code> branch:</p> <p>To get the latest changes from the <code>master</code> branch:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># clone the repository </span> <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># clone the repository </span>
$ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/ $ <span class="fu">git</span> clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/
<span class="co"># install/update third-party dependencies</span> <span class="co"># install/update third-party dependencies</span>
$ <span class="bu">cd</span> /path/to/shaarli $ <span class="bu">cd</span> /path/to/shaarli
$ <span class="ex">composer</span> update --no-dev</code></pre></div> $ <span class="ex">composer</span> install --no-dev</code></pre></div>
<hr /> <hr />
<h2 id="finish-installation">Finish Installation</h2> <h2 id="finish-installation">Finish Installation</h2>
<p>Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.</p> <p>Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.</p>

View file

@ -13,13 +13,13 @@ Get the latest released version from the [releases](https://github.com/shaarli/S
**Download our *shaarli-full* archive** to include dependencies. **Download our *shaarli-full* archive** to include dependencies.
The current latest released version is `v0.8.0` The current latest released version is `v0.8.4`
Or in command lines: Or in command lines:
```bash ```bash
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip $ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.4/shaarli-v0.8.4-full.zip
$ unzip shaarli-v0.8.0-full.zip $ unzip shaarli-v0.8.4-full.zip
$ mv Shaarli /path/to/shaarli/ $ mv Shaarli /path/to/shaarli/
``` ```
@ -30,8 +30,8 @@ $ mv Shaarli /path/to/shaarli/
``` ```
mkdir -p /path/to/shaarli && cd /path/to/shaarli/ mkdir -p /path/to/shaarli && cd /path/to/shaarli/
git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git . git clone -b v0.8 https://github.com/shaarli/Shaarli.git .
composer update --no-dev composer install --no-dev
``` ```
-------------------------------------------------------- --------------------------------------------------------
@ -66,7 +66,7 @@ $ mv Shaarli-stable /path/to/shaarli/
$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
# install/update third-party dependencies # install/update third-party dependencies
$ cd /path/to/shaarli/ $ cd /path/to/shaarli/
$ composer update --no-dev $ composer install --no-dev
``` ```
-------------------------------------------------------- --------------------------------------------------------
@ -79,10 +79,10 @@ To get the latest changes from the `master` branch:
```bash ```bash
# clone the repository # clone the repository
$ git clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/ $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/
# install/update third-party dependencies # install/update third-party dependencies
$ cd /path/to/shaarli $ cd /path/to/shaarli
$ composer update --no-dev $ composer install --no-dev
``` ```
-------------------------------------------------------- --------------------------------------------------------

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -100,9 +102,6 @@
</ul> </ul>
</div> </div>
<h1 id="plugin-system">Plugin System</h1> <h1 id="plugin-system">Plugin System</h1>
<blockquote>
<p>Note: Plugin current status - in development (not merged into master).</p>
</blockquote>
<p><a href="#developer-api"><strong>I am a developer.</strong> Developer API.</a><a href=".html"></a></p> <p><a href="#developer-api"><strong>I am a developer.</strong> Developer API.</a><a href=".html"></a></p>
<p><a href="#guide-for-template-designer"><strong>I am a template designer.</strong> Guide for template designer.</a><a href=".html"></a></p> <p><a href="#guide-for-template-designer"><strong>I am a template designer.</strong> Guide for template designer.</a><a href=".html"></a></p>
<h2 id="developer-api">Developer API</h2> <h2 id="developer-api">Developer API</h2>
@ -121,12 +120,21 @@ <h3 id="how-can-i-create-a-plugin-for-shaarli">How can I create a plugin for Sha
| plugins/ | plugins/
|---| demo_plugin/ |---| demo_plugin/
| |---| demo_plugin.php</code></pre> | |---| demo_plugin.php</code></pre>
<h3 id="plugin-initialization">Plugin initialization</h3>
<p>At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an <code>init()</code> function to execute and run it if it exists. This function must be named this way, and takes the <code>ConfigManager</code> as parameter.</p>
<pre><code>&lt;plugin_name&gt;_init($conf)</code></pre>
<p>This function can be used to create initial data, load default settings, etc. But also to set <em>plugin errors</em>. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users.</p>
<h3 id="understanding-hooks">Understanding hooks</h3> <h3 id="understanding-hooks">Understanding hooks</h3>
<p>A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.</p> <p>A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.</p>
<p>These functions need to be named with this pattern:</p> <p>These functions need to be named with this pattern:</p>
<pre><code>hook_&lt;plugin_name&gt;_&lt;hook_name&gt;</code></pre> <pre><code>hook_&lt;plugin_name&gt;_&lt;hook_name&gt;($data, $conf)</code></pre>
<p>Parameters:</p>
<ul>
<li>data: see <a href="https://github.com/shaarli/Shaarli/wiki/Plugin-System#plugins-data">$data section</a><a href=".html"></a></li>
<li>conf: the <code>ConfigManager</code> instance.</li>
</ul>
<p>For exemple, if my plugin want to add data to the header, this function is needed:</p> <p>For exemple, if my plugin want to add data to the header, this function is needed:</p>
<pre><code>hook_demo_plugin_render_header()</code></pre> <pre><code>hook_demo_plugin_render_header</code></pre>
<p>If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.</p> <p>If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.</p>
<h3 id="plugins-data">Plugin's data</h3> <h3 id="plugins-data">Plugin's data</h3>
<h4 id="parameters">Parameters</h4> <h4 id="parameters">Parameters</h4>
@ -159,6 +167,7 @@ <h3 id="metadata">Metadata</h3>
<ul> <ul>
<li><code>description</code>: plugin description</li> <li><code>description</code>: plugin description</li>
<li><code>parameters</code>: user parameter names, separated by a <code>;</code>.</li> <li><code>parameters</code>: user parameter names, separated by a <code>;</code>.</li>
<li><code>parameter.&lt;PARAMETER_NAME&gt;</code>: add a text description the specified parameter.</li>
</ul> </ul>
<blockquote> <blockquote>
<p>Note: In PHP, <code>parse_ini_file()</code> seems to want strings to be between by quotes <code>&quot;</code> in the ini file.</p> <p>Note: In PHP, <code>parse_ini_file()</code> seems to want strings to be between by quotes <code>&quot;</code> in the ini file.</p>
@ -209,16 +218,28 @@ <h3 id="hooks">Hooks</h3>
</tr> </tr>
<tr class="even"> <tr class="even">
<td><a href="#render_tagcloud">render_tagcloud</a></td> <td><a href="#render_tagcloud">render_tagcloud</a></td>
<td style="text-align: center;">Allow to add content at the top and bottom of the page.</td> <td style="text-align: center;">Allow to add content at the top and bottom of the page, and after all tags.</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td><a href="#render_taglist">render_taglist</a></td>
<td style="text-align: center;">Allow to add content at the top and bottom of the page, and after all tags.</td>
</tr>
<tr class="even">
<td><a href="#render_daily">render_daily</a></td> <td><a href="#render_daily">render_daily</a></td>
<td style="text-align: center;">Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</td> <td style="text-align: center;">Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</td>
</tr> </tr>
<tr class="odd">
<td><a href="#render_feed">render_feed</a></td>
<td style="text-align: center;">Allow to do add tags in RSS and ATOM feeds.</td>
</tr>
<tr class="even"> <tr class="even">
<td><a href="#savelink">savelink</a></td> <td><a href="#save_link">save_link</a></td>
<td style="text-align: center;">Allow to alter the link being saved in the datastore.</td> <td style="text-align: center;">Allow to alter the link being saved in the datastore.</td>
</tr> </tr>
<tr class="odd">
<td><a href="#delete_link">delete_link</a></td>
<td style="text-align: center;">Allow to do an action before a link is deleted from the datastore.</td>
</tr>
</tbody> </tbody>
</table> </table>
<h4 id="render_header">render_header</h4> <h4 id="render_header">render_header</h4>
@ -376,17 +397,41 @@ <h5 id="template-placeholders-7">Template placeholders</h5>
<li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li>
<li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li>
</ul> </ul>
<p>For each tag, the following placeholder can be used:</p>
<ul>
<li><code>tag_plugin</code>: after each tag</li>
</ul>
<p><img src="http://i.imgur.com/vHmyT3a.png" alt="plugin_start_end_zone_example" /><a href=".html"></a></p> <p><img src="http://i.imgur.com/vHmyT3a.png" alt="plugin_start_end_zone_example" /><a href=".html"></a></p>
<h4 id="render_taglist">render_taglist</h4>
<p>Triggered when taglist is displayed.</p>
<p>Allow to add content at the top and bottom of the page.</p>
<h5 id="data-8">Data</h5>
<p><code>$data</code> is an array containing:</p>
<ul>
<li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li>
<li>All templates data.</li>
</ul>
<h5 id="template-placeholders-8">Template placeholders</h5>
<p>Items can be displayed in templates by adding an entry in <code>$data['&lt;placeholder&gt;']</code> array.<a href=".html"></a></p>
<p>List of placeholders:</p>
<ul>
<li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li>
<li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li>
</ul>
<p>For each tag, the following placeholder can be used:</p>
<ul>
<li><code>tag_plugin</code>: after each tag</li>
</ul>
<h4 id="render_daily">render_daily</h4> <h4 id="render_daily">render_daily</h4>
<p>Triggered when tagcloud is displayed.</p> <p>Triggered when tagcloud is displayed.</p>
<p>Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</p> <p>Allow to add content at the top and bottom of the page, the bottom of each link and to alter data.</p>
<h5 id="data-8">Data</h5> <h5 id="data-9">Data</h5>
<p><code>$data</code> is an array containing:</p> <p><code>$data</code> is an array containing:</p>
<ul> <ul>
<li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li> <li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li>
<li>All templates data, including links.</li> <li>All templates data, including links.</li>
</ul> </ul>
<h5 id="template-placeholders-8">Template placeholders</h5> <h5 id="template-placeholders-9">Template placeholders</h5>
<p>Items can be displayed in templates by adding an entry in <code>$data['&lt;placeholder&gt;']</code> array.<a href=".html"></a></p> <p>Items can be displayed in templates by adding an entry in <code>$data['&lt;placeholder&gt;']</code> array.<a href=".html"></a></p>
<p>List of placeholders:</p> <p>List of placeholders:</p>
<ul> <ul>
@ -397,18 +442,57 @@ <h5 id="template-placeholders-8">Template placeholders</h5>
<li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li> <li><p><code>plugin_start_zone</code>: before displaying the template content.</p></li>
<li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li> <li><p><code>plugin_end_zone</code>: after displaying the template content.</p></li>
</ul> </ul>
<h4 id="savelink">savelink</h4> <h4 id="render_feed">render_feed</h4>
<p>Triggered when the ATOM or RSS feed is displayed.</p>
<p>Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered.</p>
<h5 id="data-10">Data</h5>
<p><code>$data</code> is an array containing:</p>
<ul>
<li><code>_LOGGEDIN_</code>: true if user is logged in, false otherwise.</li>
<li><code>_PAGE_</code>: containing either <code>rss</code> or <code>atom</code>.</li>
<li>All templates data, including links.</li>
</ul>
<h5 id="template-placeholders-10">Template placeholders</h5>
<p>Tags can be added in feeds by adding an entry in <code>$data['&lt;placeholder&gt;']</code> array.<a href=".html"></a></p>
<p>List of placeholders:</p>
<ul>
<li><code>feed_plugins_header</code>: used as a header tag in the feed.</li>
</ul>
<p>For each links:</p>
<ul>
<li><code>feed_plugins</code>: additional tag for every link entry.</li>
</ul>
<h4 id="save_link">save_link</h4>
<p>Triggered when a link is save (new link or edit).</p> <p>Triggered when a link is save (new link or edit).</p>
<p>Allow to alter the link being saved in the datastore.</p> <p>Allow to alter the link being saved in the datastore.</p>
<h5 id="data-9">Data</h5> <h5 id="data-11">Data</h5>
<p><code>$data</code> is an array containing the link being saved:</p> <p><code>$data</code> is an array containing the link being saved:</p>
<ul> <ul>
<li>id</li>
<li>title</li> <li>title</li>
<li>url</li> <li>url</li>
<li>shorturl</li>
<li>description</li> <li>description</li>
<li>linkdate</li>
<li>private</li> <li>private</li>
<li>tags</li> <li>tags</li>
<li>created</li>
<li>updated</li>
</ul>
<h4 id="delete_link">delete_link</h4>
<p>Triggered when a link is deleted.</p>
<p>Allow to execute any action before the link is actually removed from the datastore</p>
<h5 id="data-12">Data</h5>
<p><code>$data</code> is an array containing the link being saved:</p>
<ul>
<li>id</li>
<li>title</li>
<li>url</li>
<li>shorturl</li>
<li>description</li>
<li>private</li>
<li>tags</li>
<li>created</li>
<li>updated</li>
</ul> </ul>
<h2 id="guide-for-template-designer">Guide for template designer</h2> <h2 id="guide-for-template-designer">Guide for template designer</h2>
<h3 id="plugin-administration">Plugin administration</h3> <h3 id="plugin-administration">Plugin administration</h3>
@ -537,5 +621,14 @@ <h3 id="list-of-placeholders">List of placeholders</h3>
{$value} {$value}
{/loop} {/loop}
<span class="kw">&lt;/div&gt;</span></code></pre></div> <span class="kw">&lt;/div&gt;</span></code></pre></div>
<p><strong>feed.atom.xml</strong> and <strong>feed.rss.xml</strong>:</p>
<p>In headers tags section:</p>
<div class="sourceCode"><pre class="sourceCode xml"><code class="sourceCode xml">{loop=&quot;$feed_plugins_header&quot;}
{$value}
{/loop}</code></pre></div>
<p>After each entry:</p>
<div class="sourceCode"><pre class="sourceCode xml"><code class="sourceCode xml">{loop=&quot;$value.feed_plugins&quot;}
{$value}
{/loop}</code></pre></div>
</body> </body>
</html> </html>

View file

@ -1,6 +1,4 @@
#Plugin System #Plugin System
> Note: Plugin current status - in development (not merged into master).
[**I am a developer.** Developer API.](#developer-api)[](.html) [**I am a developer.** Developer API.](#developer-api)[](.html)
[**I am a template designer.** Guide for template designer.](#guide-for-template-designer)[](.html) [**I am a template designer.** Guide for template designer.](#guide-for-template-designer)[](.html)
@ -30,6 +28,14 @@ You should have the following tree view:
| |---| demo_plugin.php | |---| demo_plugin.php
``` ```
### Plugin initialization
At the beginning of Shaarli execution, all enabled plugins are loaded. At this point, the plugin system looks for an `init()` function to execute and run it if it exists. This function must be named this way, and takes the `ConfigManager` as parameter.
<plugin_name>_init($conf)
This function can be used to create initial data, load default settings, etc. But also to set *plugin errors*. If the initialization function returns an array of strings, they will be understand as errors, and displayed in the header to logged in users.
### Understanding hooks ### Understanding hooks
A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution. A plugin is a set of functions. Each function will be triggered by the plugin system at certain point in Shaarli execution.
@ -37,12 +43,17 @@ A plugin is a set of functions. Each function will be triggered by the plugin sy
These functions need to be named with this pattern: These functions need to be named with this pattern:
``` ```
hook_<plugin_name>_<hook_name> hook_<plugin_name>_<hook_name>($data, $conf)
``` ```
Parameters:
- data: see [$data section](https://github.com/shaarli/Shaarli/wiki/Plugin-System#plugins-data)[](.html)
- conf: the `ConfigManager` instance.
For exemple, if my plugin want to add data to the header, this function is needed: For exemple, if my plugin want to add data to the header, this function is needed:
hook_demo_plugin_render_header() hook_demo_plugin_render_header
If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header. If this function is declared, and the plugin enabled, it will be called every time Shaarli is rendering the header.
@ -98,6 +109,7 @@ Each file contain two keys:
* `description`: plugin description * `description`: plugin description
* `parameters`: user parameter names, separated by a `;`. * `parameters`: user parameter names, separated by a `;`.
* `parameter.<PARAMETER_NAME>`: add a text description the specified parameter.
> Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file.
@ -118,9 +130,13 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha
| [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |[](.html) | [render_editlink](#render_editlink) | Allow to add fields in the form, or display elements. |[](.html)
| [render_tools](#render_tools) | Allow to add content at the end of the page. |[](.html) | [render_tools](#render_tools) | Allow to add content at the end of the page. |[](.html)
| [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |[](.html) | [render_picwall](#render_picwall) | Allow to add content at the top and bottom of the page. |[](.html)
| [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page. |[](.html) | [render_tagcloud](#render_tagcloud) | Allow to add content at the top and bottom of the page, and after all tags. |[](.html)
| [render_taglist](#render_taglist) | Allow to add content at the top and bottom of the page, and after all tags. |[](.html)
| [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |[](.html) | [render_daily](#render_daily) | Allow to add content at the top and bottom of the page, the bottom of each link and to alter data. |[](.html)
| [savelink](#savelink) | Allow to alter the link being saved in the datastore. |[](.html) | [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. |[](.html)
| [save_link](#save_link) | Allow to alter the link being saved in the datastore. |[](.html)
| [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |[](.html)
#### render_header #### render_header
@ -330,8 +346,40 @@ List of placeholders:
* `plugin_end_zone`: after displaying the template content. * `plugin_end_zone`: after displaying the template content.
For each tag, the following placeholder can be used:
* `tag_plugin`: after each tag
![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)[](.html) ![plugin_start_end_zone_example](http://i.imgur.com/vHmyT3a.png)[](.html)
#### render_taglist
Triggered when taglist is displayed.
Allow to add content at the top and bottom of the page.
##### Data
`$data` is an array containing:
* `_LOGGEDIN_`: true if user is logged in, false otherwise.
* All templates data.
##### Template placeholders
Items can be displayed in templates by adding an entry in `$data['<placeholder>']` array.[](.html)
List of placeholders:
* `plugin_start_zone`: before displaying the template content.
* `plugin_end_zone`: after displaying the template content.
For each tag, the following placeholder can be used:
* `tag_plugin`: after each tag
#### render_daily #### render_daily
Triggered when tagcloud is displayed. Triggered when tagcloud is displayed.
@ -359,7 +407,33 @@ List of placeholders:
* `plugin_end_zone`: after displaying the template content. * `plugin_end_zone`: after displaying the template content.
#### savelink #### render_feed
Triggered when the ATOM or RSS feed is displayed.
Allow to add tags in the feed, either in the header or for each items. Items (links) can also be altered before being rendered.
##### Data
`$data` is an array containing:
* `_LOGGEDIN_`: true if user is logged in, false otherwise.
* `_PAGE_`: containing either `rss` or `atom`.
* All templates data, including links.
##### Template placeholders
Tags can be added in feeds by adding an entry in `$data['<placeholder>']` array.[](.html)
List of placeholders:
* `feed_plugins_header`: used as a header tag in the feed.
For each links:
* `feed_plugins`: additional tag for every link entry.
#### save_link
Triggered when a link is save (new link or edit). Triggered when a link is save (new link or edit).
@ -369,12 +443,36 @@ Allow to alter the link being saved in the datastore.
`$data` is an array containing the link being saved: `$data` is an array containing the link being saved:
* id
* title * title
* url * url
* shorturl
* description * description
* linkdate
* private * private
* tags * tags
* created
* updated
#### delete_link
Triggered when a link is deleted.
Allow to execute any action before the link is actually removed from the datastore
##### Data
`$data` is an array containing the link being saved:
* id
* title
* url
* shorturl
* description
* private
* tags
* created
* updated
## Guide for template designer ## Guide for template designer
@ -595,3 +693,19 @@ Bottom:
{/loop} {/loop}
</div> </div>
``` ```
**feed.atom.xml** and **feed.rss.xml**:
In headers tags section:
```xml
{loop="$feed_plugins_header"}
{$value}
{/loop}
```
After each entry:
```xml
{loop="$value.feed_plugins"}
{$value}
{/loop}
```

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -145,7 +147,6 @@ <h4 id="official-plugins">Official plugins</h4>
<li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md"><code>markdown</code></a>: Render shaare description with Markdown syntax.<a href=".html"></a></li> <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md"><code>markdown</code></a>: Render shaare description with Markdown syntax.<a href=".html"></a></li>
<li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md"><code>playvideos</code></a>: Add a button in the toolbar allowing to watch all videos.<a href=".html"></a></li> <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md"><code>playvideos</code></a>: Add a button in the toolbar allowing to watch all videos.<a href=".html"></a></li>
<li><code>qrcode</code>: For each link, add a QRCode icon.</li> <li><code>qrcode</code>: For each link, add a QRCode icon.</li>
<li><code>readityourself</code>: For each link, add a ReadItYourself icon to save the shaared URL</li>
<li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md"><code>wallabag</code></a>: For each link, add a Wallabag icon to save it in your instance.<a href=".html"></a></li> <li><a href="https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md"><code>wallabag</code></a>: For each link, add a Wallabag icon to save it in your instance.<a href=".html"></a></li>
</ul> </ul>
<h4 id="third-party-plugins">Third party plugins</h4> <h4 id="third-party-plugins">Third party plugins</h4>

View file

@ -67,7 +67,6 @@ Usage of each plugin is documented in it's README file:
* [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.[](.html) * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.[](.html)
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.[](.html) * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.[](.html)
* `qrcode`: For each link, add a QRCode icon. * `qrcode`: For each link, add a QRCode icon.
* `readityourself`: For each link, add a ReadItYourself icon to save the shaared URL
* [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance.[](.html) * [`wallabag`](https://github.com/shaarli/Shaarli/blob/master/plugins/wallabag/README.md): For each link, add a Wallabag icon to save it in your instance.[](.html)

169
doc/REST-API.html Normal file
View file

@ -0,0 +1,169 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Shaarli REST API</title>
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="github-markdown.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<div id="local-sidebar">
<ul>
<li><a href="Home.html">Home</a></li>
<li>Setup
<ul>
<li><a href="Download-and-Installation.html">Download and Installation</a></li>
<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
<li><a href="Server-requirements.html">Server requirements</a></li>
<li><a href="Server-configuration.html">Server configuration</a></li>
<li><a href="Server-security.html">Server security</a></li>
<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
<li><a href="Plugins.html">Plugins</a></li>
</ul></li>
<li><a href="Docker.html">Docker</a></li>
<li><a href="Usage.html">Usage</a>
<ul>
<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li>
<li>How To
<ul>
<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
<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>
<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
</ul></li>
<li><a href="Troubleshooting.html">Troubleshooting</a></li>
<li><a href="Development.html">Development</a>
<ul>
<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
<li><a href="Directory-structure.html">Directory structure</a></li>
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li>
<li><a href="Unit-tests.html">Unit tests</a></li>
</ul></li>
<li>About
<ul>
<li><a href="FAQ.html">FAQ</a></li>
<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
</ul></li>
</ul>
</div>
<h1 id="rest-api">REST API</h1>
<h2 id="usage">Usage</h2>
<p>See the <a href="http://shaarli.github.io/api-documentation/">REST API documentation</a>.<a href=".html"></a></p>
<h2 id="authentication">Authentication</h2>
<p>All requests to Shaarli's API must include a JWT token to verify their authenticity.</p>
<p>This token has to be included as an HTTP header called <code>Authentication: Bearer &lt;jwt token&gt;</code>.</p>
<p>JWT resources :</p>
<ul>
<li><a href="https://jwt.io">jwt.io</a> (including a list of client per language).<a href=".html"></a></li>
<li>RFC : <a href="https://tools.ietf.org/html/rfc7519" class="uri">https://tools.ietf.org/html/rfc7519</a></li>
<li><a href="https://float-middle.com/json-web-tokens-jwt-vs-sessions/" class="uri">https://float-middle.com/json-web-tokens-jwt-vs-sessions/</a></li>
<li>HackerNews thread: <a href="https://news.ycombinator.com/item?id=11929267" class="uri">https://news.ycombinator.com/item?id=11929267</a></li>
</ul>
<h3 id="shaarli-jwt-token">Shaarli JWT Token</h3>
<p>JWT tokens are composed by three parts, separated by a dot <code>.</code> and encoded in base64:</p>
<pre><code>[header].[payload].[signature][](.html)</code></pre>
<h4 id="header">Header</h4>
<p>Shaarli only allow one hash algorithm, so the header will always be the same:</p>
<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="fu">{</span>
<span class="dt">&quot;typ&quot;</span><span class="fu">:</span> <span class="st">&quot;JWT&quot;</span><span class="fu">,</span>
<span class="dt">&quot;alg&quot;</span><span class="fu">:</span> <span class="st">&quot;HS512&quot;</span>
<span class="fu">}</span></code></pre></div>
<p>Encoded in base64, it gives:</p>
<pre><code>ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==</code></pre>
<h4 id="payload">Payload</h4>
<p><strong>Validity duration</strong></p>
<p>To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independant - UTC) under the key <code>iat</code> (issued at). This token will be accepted during 9 minutes.</p>
<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="fu">{</span>
<span class="dt">&quot;iat&quot;</span><span class="fu">:</span> <span class="dv">1468663519</span>
<span class="fu">}</span></code></pre></div>
<p>See <a href="https://tools.ietf.org/html/rfc7519#section-4.1.6">RFC reference</a>.<a href=".html"></a></p>
<h4 id="signature">Signature</h4>
<p>The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot <code>.</code>, hashed in SHA512 with the API secret available in Shaarli administration page.</p>
<p>Signature example with PHP:</p>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$content</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="kw">$header</span><span class="ot">)</span> . <span class="st">&#39;.&#39;</span> . <span class="fu">base64_encode</span><span class="ot">(</span><span class="kw">$payload</span><span class="ot">);</span>
<span class="kw">$signature</span> = <span class="fu">hash_hmac</span><span class="ot">(</span><span class="st">&#39;sha512&#39;</span><span class="ot">,</span> <span class="kw">$content</span><span class="ot">,</span> <span class="kw">$secret</span><span class="ot">);</span></code></pre></div>
<h3 id="complete-example">Complete example</h3>
<h4 id="php">PHP</h4>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">function</span> generateToken<span class="ot">(</span><span class="kw">$secret</span><span class="ot">)</span> {
<span class="kw">$header</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="st">&#39;{</span>
<span class="st"> &quot;typ&quot;: &quot;JWT&quot;,</span>
<span class="st"> &quot;alg&quot;: &quot;HS512&quot;</span>
<span class="st"> }&#39;</span><span class="ot">);</span>
<span class="kw">$payload</span> = <span class="fu">base64_encode</span><span class="ot">(</span><span class="st">&#39;{</span>
<span class="st"> &quot;iat&quot;: &#39;</span>. <span class="fu">time</span><span class="ot">()</span> .<span class="st">&#39;</span>
<span class="st"> }&#39;</span><span class="ot">);</span>
<span class="kw">$signature</span> = <span class="fu">hash_hmac</span><span class="ot">(</span><span class="st">&#39;sha512&#39;</span><span class="ot">,</span> <span class="kw">$header</span> .<span class="st">&#39;.&#39;</span>. <span class="kw">$payload</span> <span class="ot">,</span> <span class="kw">$secret</span><span class="ot">);</span>
<span class="kw">return</span> <span class="kw">$header</span> .<span class="st">&#39;.&#39;</span>. <span class="kw">$payload</span> .<span class="st">&#39;.&#39;</span>. <span class="kw">$signature</span><span class="ot">;</span>
}
<span class="kw">$secret</span> = <span class="st">&#39;mysecret&#39;</span><span class="ot">;</span>
<span class="kw">$token</span> = generateToken<span class="ot">(</span><span class="kw">$secret</span><span class="ot">);</span>
<span class="fu">echo</span> <span class="kw">$token</span><span class="ot">;</span></code></pre></div>
<blockquote>
<p><code>ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==.ewogICAgICAgICJpYXQiOiAxNDY4NjY3MDQ3CiAgICB9.1d2c54fa947daf594fdbf7591796195652c8bc63bffad7f6a6db2a41c313f495a542cbfb595acade79e83f3810d709b4251d7b940bbc10b531a6e6134af63a68</code></p>
</blockquote>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$options</span> = <span class="ot">[[](</span>.html<span class="ot">)</span>
<span class="st">&#39;http&#39;</span> =&gt; <span class="ot">[[](</span>.html<span class="ot">)</span>
<span class="st">&#39;method&#39;</span> =&gt; <span class="st">&#39;GET&#39;</span><span class="ot">,</span>
<span class="st">&#39;jwt&#39;</span> =&gt; <span class="kw">$token</span><span class="ot">,</span>
<span class="ot">],</span>
<span class="ot">];</span>
<span class="kw">$context</span> = <span class="fu">stream_context_create</span><span class="ot">(</span><span class="kw">$options</span><span class="ot">);</span>
<span class="fu">file_get_contents</span><span class="ot">(</span><span class="kw">$apiEndpoint</span><span class="ot">,</span> <span class="kw">false</span><span class="ot">,</span> <span class="kw">$context</span><span class="ot">);</span></code></pre></div>
</body>
</html>

105
doc/REST-API.md Normal file
View file

@ -0,0 +1,105 @@
#REST API
## Usage
See the [REST API documentation](http://shaarli.github.io/api-documentation/).[](.html)
## Authentication
All requests to Shaarli's API must include a JWT token to verify their authenticity.
This token has to be included as an HTTP header called `Authentication: Bearer <jwt token>`.
JWT resources :
* [jwt.io](https://jwt.io) (including a list of client per language).[](.html)
* RFC : https://tools.ietf.org/html/rfc7519
* https://float-middle.com/json-web-tokens-jwt-vs-sessions/
* HackerNews thread: https://news.ycombinator.com/item?id=11929267
### Shaarli JWT Token
JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64:
```
[header].[payload].[signature][](.html)
```
#### Header
Shaarli only allow one hash algorithm, so the header will always be the same:
```json
{
"typ": "JWT",
"alg": "HS512"
}
```
Encoded in base64, it gives:
```
ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==
```
#### Payload
**Validity duration**
To avoid infinite token validity, JWT tokens must include their creation date in UNIX timestamp format (timezone independant - UTC) under the key `iat` (issued at). This token will be accepted during 9 minutes.
```json
{
"iat": 1468663519
}
```
See [RFC reference](https://tools.ietf.org/html/rfc7519#section-4.1.6).[](.html)
#### Signature
The signature authenticate the token validity. It contains the base64 of the header and the body, separated by a dot `.`, hashed in SHA512 with the API secret available in Shaarli administration page.
Signature example with PHP:
```php
$content = base64_encode($header) . '.' . base64_encode($payload);
$signature = hash_hmac('sha512', $content, $secret);
```
### Complete example
#### PHP
```php
function generateToken($secret) {
$header = base64_encode('{
"typ": "JWT",
"alg": "HS512"
}');
$payload = base64_encode('{
"iat": '. time() .'
}');
$signature = hash_hmac('sha512', $header .'.'. $payload , $secret);
return $header .'.'. $payload .'.'. $signature;
}
$secret = 'mysecret';
$token = generateToken($secret);
echo $token;
```
> `ewogICAgICAgICJ0eXAiOiAiSldUIiwKICAgICAgICAiYWxnIjogIkhTNTEyIgogICAgfQ==.ewogICAgICAgICJpYXQiOiAxNDY4NjY3MDQ3CiAgICB9.1d2c54fa947daf594fdbf7591796195652c8bc63bffad7f6a6db2a41c313f495a542cbfb595acade79e83f3810d709b4251d7b940bbc10b531a6e6134af63a68`
```php
$options = [[](.html)
'http' => [[](.html)
'method' => 'GET',
'jwt' => $token,
],
];
$context = stream_context_create($options);
file_get_contents($apiEndpoint, false, $context);
```

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -192,6 +194,8 @@ <h3 id="verify-a-signed-tag">Verify a signed tag</h3>
<span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F <span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
<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> <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>
<h2 id="publish-the-github-release">Publish the GitHub release</h2> <h2 id="publish-the-github-release">Publish the GitHub release</h2>
<h3 id="update-release-badges">Update release badges</h3>
<p>Update <code>README.md</code> so version badges display and point to the newly released Shaarli version(s).</p>
<h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3> <h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3>
<p>From the previously drafted release:</p> <p>From the previously drafted release:</p>
<ul> <ul>

View file

@ -103,6 +103,9 @@ gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.htm
``` ```
## Publish the GitHub release ## Publish the GitHub release
### Update release badges
Update `README.md` so version badges display and point to the newly released Shaarli version(s).
### Create a GitHub release from a Git tag ### Create a GitHub release from a Git tag
From the previously drafted release: From the previously drafted release:
- edit the release notes (if needed) - edit the release notes (if needed)

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -196,6 +198,7 @@ <h3 id="paranoid---redirect-http-80-to-https-443">Paranoid - Redirect HTTP (:80)
<h3 id="htaccess">.htaccess</h3> <h3 id="htaccess">.htaccess</h3>
<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> <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>
<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> <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>
<p>Apache module <code>mod_rewrite</code> <strong>must</strong> be enabled to use the REST API. URL rewriting rules for the Slim microframework are stated in the root <code>.htaccess</code> file.</p>
<h2 id="lighthttpd">LightHttpd</h2> <h2 id="lighthttpd">LightHttpd</h2>
<h2 id="nginx">Nginx</h2> <h2 id="nginx">Nginx</h2>
<h3 id="foreword">Foreword</h3> <h3 id="foreword">Foreword</h3>
@ -296,11 +299,14 @@ <h3 id="minimal-1">Minimal</h3>
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
location /shaarli/ { location /shaarli/ {
try_files $uri /shaarli/index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }
location ~ (index)\.php$ { location ~ (index)\.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
include fastcgi.conf; include fastcgi.conf;
@ -335,6 +341,10 @@ <h3 id="modular">Modular</h3>
}</code></pre> }</code></pre>
<pre class="nginx"><code># /etc/nginx/php.conf <pre class="nginx"><code># /etc/nginx/php.conf
location ~ (index)\.php$ { location ~ (index)\.php$ {
# Slim - split URL path into (script_filename, path_info)
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# filter and proxy PHP requests to PHP-FPM # filter and proxy PHP requests to PHP-FPM
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
@ -367,6 +377,9 @@ <h3 id="modular">Modular</h3>
server_name my.first.domain.org; server_name my.first.domain.org;
location /shaarli/ { location /shaarli/ {
# Slim - rewrite URLs
try_files $uri /shaarli/index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }
@ -425,6 +438,9 @@ <h3 id="redirect-http-to-https">Redirect HTTP to HTTPS</h3>
ssl_certificate_key /home/john/ssl/localhost.key; ssl_certificate_key /home/john/ssl/localhost.key;
location /shaarli/ { location /shaarli/ {
# Slim - rewrite URLs
try_files $uri /index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }

View file

@ -108,6 +108,8 @@ Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be d
**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) **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)
Apache module `mod_rewrite` **must** be enabled to use the REST API. URL rewriting rules for the Slim microframework are stated in the root `.htaccess` file.
## LightHttpd ## LightHttpd
## Nginx ## Nginx
@ -218,11 +220,14 @@ http {
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
location /shaarli/ { location /shaarli/ {
try_files $uri /shaarli/index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }
location ~ (index)\.php$ { location ~ (index)\.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
include fastcgi.conf; include fastcgi.conf;
@ -261,6 +266,10 @@ location ~ ~$ {
```nginx ```nginx
# /etc/nginx/php.conf # /etc/nginx/php.conf
location ~ (index)\.php$ { location ~ (index)\.php$ {
# Slim - split URL path into (script_filename, path_info)
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# filter and proxy PHP requests to PHP-FPM # filter and proxy PHP requests to PHP-FPM
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
@ -299,6 +308,9 @@ http {
server_name my.first.domain.org; server_name my.first.domain.org;
location /shaarli/ { location /shaarli/ {
# Slim - rewrite URLs
try_files $uri /shaarli/index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }
@ -361,6 +373,9 @@ http {
ssl_certificate_key /home/john/ssl/localhost.key; ssl_certificate_key /home/john/ssl/localhost.key;
location /shaarli/ { location /shaarli/ {
# Slim - rewrite URLs
try_files $uri /index.php$is_args$args;
access_log /var/log/nginx/shaarli.access.log; access_log /var/log/nginx/shaarli.access.log;
error_log /var/log/nginx/shaarli.error.log; error_log /var/log/nginx/shaarli.error.log;
} }

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -83,26 +85,31 @@ <h3 id="supported-versions">Supported versions</h3>
</thead> </thead>
<tbody> <tbody>
<tr class="odd"> <tr class="odd">
<td style="text-align: center;">7.1</td>
<td style="text-align: center;">Supported (v0.9.x)</td>
<td style="text-align: center;"></td>
</tr>
<tr class="even">
<td style="text-align: center;">7.0</td> <td style="text-align: center;">7.0</td>
<td style="text-align: center;">Supported</td> <td style="text-align: center;">Supported</td>
<td style="text-align: center;"></td> <td style="text-align: center;"></td>
</tr> </tr>
<tr class="even"> <tr class="odd">
<td style="text-align: center;">5.6</td> <td style="text-align: center;">5.6</td>
<td style="text-align: center;">Supported</td> <td style="text-align: center;">Supported</td>
<td style="text-align: center;"></td> <td style="text-align: center;"></td>
</tr> </tr>
<tr class="odd"> <tr class="even">
<td style="text-align: center;">5.5</td> <td style="text-align: center;">5.5</td>
<td style="text-align: center;">EOL: 2016-07-10</td> <td style="text-align: center;">EOL: 2016-07-10</td>
<td style="text-align: center;"></td> <td style="text-align: center;"></td>
</tr> </tr>
<tr class="even"> <tr class="odd">
<td style="text-align: center;">5.4</td> <td style="text-align: center;">5.4</td>
<td style="text-align: center;">EOL: 2015-09-14</td> <td style="text-align: center;">EOL: 2015-09-14</td>
<td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td>
</tr> </tr>
<tr class="odd"> <tr class="even">
<td style="text-align: center;">5.3</td> <td style="text-align: center;">5.3</td>
<td style="text-align: center;">EOL: 2014-08-14</td> <td style="text-align: center;">EOL: 2014-08-14</td>
<td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td> <td style="text-align: center;">✅ (up to Shaarli 0.8.x)</td>
@ -130,6 +137,16 @@ <h3 id="dependency-management">Dependency management</h3>
<td style="text-align: center;">All</td> <td style="text-align: center;">All</td>
<td>Import bookmarks from Netscape files<a href=".html"></a></td> <td>Import bookmarks from Netscape files<a href=".html"></a></td>
</tr> </tr>
<tr class="even">
<td><a href="https://packagist.org/packages/erusev/parsedown"><code>erusev/parsedown</code></a></td>
<td style="text-align: center;">All</td>
<td>Parse MarkDown syntax for the MarkDown plugin<a href=".html"></a></td>
</tr>
<tr class="odd">
<td><a href="https://packagist.org/packages/slim/slim"><code>slim/slim</code></a></td>
<td style="text-align: center;">All</td>
<td>Handle routes and middleware for the REST API<a href=".html"></a></td>
</tr>
</tbody> </tbody>
</table> </table>
<h3 id="extensions">Extensions</h3> <h3 id="extensions">Extensions</h3>

View file

@ -10,6 +10,7 @@
### Supported versions ### Supported versions
Version | Status | Shaarli compatibility Version | Status | Shaarli compatibility
:---:|:---:|:---: :---:|:---:|:---:
7.1 | Supported (v0.9.x) | :white_check_mark:
7.0 | Supported | :white_check_mark: 7.0 | Supported | :white_check_mark:
5.6 | Supported | :white_check_mark: 5.6 | Supported | :white_check_mark:
5.5 | EOL: 2016-07-10 | :white_check_mark: 5.5 | EOL: 2016-07-10 | :white_check_mark:
@ -26,6 +27,8 @@ download and install third-party PHP dependencies.
Library | Required? | Usage Library | Required? | Usage
---|:---:|--- ---|:---:|---
[`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html) [`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html)
[`erusev/parsedown`](https://packagist.org/packages/erusev/parsedown) | All | Parse MarkDown syntax for the MarkDown plugin[](.html)
[`slim/slim`](https://packagist.org/packages/slim/slim) | All | Handle routes and middleware for the REST API[](.html)
### Extensions ### Extensions
Extension | Required? | Usage Extension | Required? | Usage

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -169,6 +171,7 @@ <h3 id="security">Security</h3>
<h3 id="resources">Resources</h3> <h3 id="resources">Resources</h3>
<p><strong>data_dir</strong>: Data directory.<br /> <p><strong>data_dir</strong>: Data directory.<br />
<strong>datastore</strong>: Shaarli's links database file path.<br /> <strong>datastore</strong>: Shaarli's links database file path.<br />
<strong>history</strong>: Shaarli's operation history file path.<br />
<strong>updates</strong>: File path for the ran updates file.<br /> <strong>updates</strong>: File path for the ran updates file.<br />
<strong>log</strong>: Log file path.<br /> <strong>log</strong>: Log file path.<br />
<strong>update_check</strong>: Last update check file path.<br /> <strong>update_check</strong>: Last update check file path.<br />

View file

@ -70,6 +70,7 @@ It might be useful if your IP adress often changes.
**data_dir**: Data directory. **data_dir**: Data directory.
**datastore**: Shaarli's links database file path. **datastore**: Shaarli's links database file path.
**history**: Shaarli's operation history file path.
**updates**: File path for the ran updates file. **updates**: File path for the ran updates file.
**log**: Log file path. **log**: Log file path.
**update_check**: Last update check file path. **update_check**: Last update check file path.

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -100,42 +102,56 @@
</ul> </ul>
</div> </div>
<h1 id="theming">Theming</h1> <h1 id="theming">Theming</h1>
<h2 id="user-css">User CSS</h2> <h2 id="foreword">Foreword</h2>
<p>There are two ways of customizing how Shaarli looks:</p>
<ol>
<li>by using a custom CSS to override Shaarli's CSS</li>
<li>by using a full theme that provides its own RainTPL templates, CSS and Javascript resources</li>
</ol>
<h2 id="custom-css">Custom CSS</h2>
<p>Shaarli's appearance can be modified by adding CSS rules to:</p>
<ul> <ul>
<li>Shaarli's apparence can be modified by editing CSS rules in <code>inc/user.css</code>. This file allows to override rules defined in the main <code>inc/shaarli.css</code> (only add changed rules), or define a whole new theme.</li> <li>Shaarli &lt; <code>v0.9.0</code>: <code>inc/user.css</code></li>
<li>Do not edit <code>inc/shaarli.css</code>! Your changes would be overriden when updating Shaarli.</li> <li>Shaarli &gt;= <code>v0.9.0</code>: <code>data/user.css</code></li>
<li>Some themes are available at <a href="https://github.com/shaarli/shaarli-themes" class="uri">https://github.com/shaarli/shaarli-themes</a>.</li>
</ul> </ul>
<p>See also:</p> <p>This file allows overriding rules defined in the template CSS files (only add changed rules), or define a whole new theme.</p>
<ul> <p><strong>Note</strong>: Do not edit <code>tpl/default/css/shaarli.css</code>! Your changes would be overridden when updating Shaarli.</p>
<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li> <p>See also <a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></p>
</ul> <h2 id="themes">Themes</h2>
<h2 id="raintpl-template">RainTPL template</h2>
<p><em>WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental.</em></p> <p><em>WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental.</em></p>
<p>Installation:</p>
<ul> <ul>
<li>Find the template you'd like to install (see the list of <a href="available-templates%7CTheming#community-themes--templates.html">available templates|Theming#community-themes--templates</a>)</li> <li>find a theme you'd like to install</li>
<li>Find it's git clone URL or download the zip archive for the template.</li> <li>copy or clone the theme folder under <code>tpl/&lt;a_sweet_theme&gt;</code></li>
<li>In your Shaarli <code>tpl/</code> directory, run <code>git clone https://url/of/my-template/</code> or unpack the zip archive. <li>enable the theme:
<ul> <ul>
<li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li> <li>Shaarli &lt; <code>v0.9.0</code>: edit <code>data/config.json.php</code> and set the value of <code>raintpl_tpl</code> to the new theme name:<br />
<code>&quot;raintpl_tpl&quot;: &quot;tpl\/my-template\/&quot;</code></li>
<li>Shaarli &gt;= <code>v0.9.0</code>: select the theme through the <em>Tools</em> page</li>
</ul></li> </ul></li>
<li><p>Edit <code>data/config.json.php</code> to have Shaarli use this template, in <code>&quot;resource&quot;</code> e.g.</p>
<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>
</ul> </ul>
<h2 id="community-themes-templates">Community themes &amp; templates</h2> <h2 id="community-css-themes">Community CSS &amp; themes</h2>
<h3 id="custom-css-1">Custom CSS</h3>
<ul>
<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>
<li><a href="https://github.com/shaarli/shaarli-themes">shaarli/shaarli-themes</a><a href=".html"></a></li>
</ul>
<h3 id="themes-1">Themes</h3>
<ul> <ul>
<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> <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>
<li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li> <li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li>
<li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme.<a href=".html"></a></li> <li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme<a href=".html"></a></li>
<li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li> <li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li>
<li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li> <li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li>
<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> <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>
<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> <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>
</ul>
<h3 id="shaarli-forks">Shaarli forks</h3>
<ul>
<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> <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>
<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>
<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> <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>
</ul> </ul>
<h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3> <h2 id="example-installation-albinomouse-theme">Example installation: AlbinoMouse theme</h2>
<p>With the following configuration:</p> <p>With the following configuration:</p>
<ul> <ul>
<li>Apache 2 / PHP 5.6</li> <li>Apache 2 / PHP 5.6</li>

View file

@ -1,39 +1,51 @@
#Theming #Theming
## User CSS ## Foreword
There are two ways of customizing how Shaarli looks:
- Shaarli's apparence can be modified by editing CSS rules in `inc/user.css`. This file allows to override rules defined in the main `inc/shaarli.css` (only add changed rules), or define a whole new theme. 1. by using a custom CSS to override Shaarli's CSS
- Do not edit `inc/shaarli.css`! Your changes would be overriden when updating Shaarli. 2. by using a full theme that provides its own RainTPL templates, CSS and Javascript resources
- Some themes are available at https://github.com/shaarli/shaarli-themes.
See also: ## Custom CSS
- [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html) Shaarli's appearance can be modified by adding CSS rules to:
- Shaarli < `v0.9.0`: `inc/user.css`
- Shaarli >= `v0.9.0`: `data/user.css`
## RainTPL template This file allows overriding rules defined in the template CSS files (only add changed rules), or define a whole new theme.
**Note**: Do not edit `tpl/default/css/shaarli.css`! Your changes would be overridden when updating Shaarli.
See also [Download CSS styles from an OPML list](Download-CSS-styles-from-an-OPML-list.html)
## Themes
_WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental._ _WARNING - This feature is currently being worked on and will be improved in the next releases. Experimental._
- Find the template you'd like to install (see the list of [available templates|Theming#community-themes--templates](available-templates|Theming#community-themes--templates.html)) Installation:
- Find it's git clone URL or download the zip archive for the template. - find a theme you'd like to install
- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive. - copy or clone the theme folder under `tpl/<a_sweet_theme>`
- There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files. - enable the theme:
- Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g. - Shaarli < `v0.9.0`: edit `data/config.json.php` and set the value of `raintpl_tpl` to the new theme name:
```json `"raintpl_tpl": "tpl\/my-template\/"`
"raintpl_tpl": "tpl\/my-template\/", - Shaarli >= `v0.9.0`: select the theme through the _Tools_ page
```
## Community themes & templates ## Community CSS & themes
### Custom CSS
- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for Shaarli[](.html)
- [shaarli/shaarli-themes](https://github.com/shaarli/shaarli-themes)[](.html)
### Themes
- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) - [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html)
- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) - [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html)
- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html) - [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme[](.html)
- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) - [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html)
- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html)
- [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) - [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)
- [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) - [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)
### Shaarli forks
- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) - [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html)
- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html)
- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) - [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html)
### Example installation: AlbinoMouse template ## Example installation: AlbinoMouse theme
With the following configuration: With the following configuration:
- Apache 2 / PHP 5.6 - Apache 2 / PHP 5.6
- user sites are enabled, e.g. `/home/user/public_html/somedir` is served as `http://localhost/~user/somedir` - user sites are enabled, e.g. `/home/user/public_html/somedir` is served as `http://localhost/~user/somedir`

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -172,7 +174,7 @@ <h3 id="old-php-versions">Old PHP versions</h3>
<li>If you have the error <code>Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx</code>, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:<a href=".html"></a></li> <li>If you have the error <code>Warning: file_get_contents() [function.file-get-contents]: URL file-access is disabled in the server configuration in /…/index.php on line xxx</code>, it means that your host has disabled the ability to fetch a file by HTTP in the php config (Typically in 1and1 hosting). Bad host. Change host. Or comment the following lines:<a href=".html"></a></li>
</ul> </ul>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">//list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.</span> <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">//list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.</span>
<span class="co">// FIXME: Decode charset according to charset specified in either 1) HTTP response headers or 2) &lt;head&gt; in html </span> <span class="co">// </span><span class="al">FIXME</span><span class="co">: Decode charset according to charset specified in either 1) HTTP response headers or 2) &lt;head&gt; in html </span>
<span class="co">//if (strpos($status,&#39;200 OK&#39;)) $title=html_extract_title($data);</span></code></pre></div> <span class="co">//if (strpos($status,&#39;200 OK&#39;)) $title=html_extract_title($data);</span></code></pre></div>
<ul> <ul>
<li>On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work.</li> <li>On hosts which forbid outgoing HTTP requests (such as free.fr), some thumbnails will not work.</li>

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -208,5 +210,17 @@ <h4 id="test-results-and-coverage">Test results and coverage</h4>
<li>a detailed HTML report with metrics for tested code</li> <li>a detailed HTML report with metrics for tested code</li>
<li>to open it in a web browser: <code>firefox coverage/index.html &amp;</code></li> <li>to open it in a web browser: <code>firefox coverage/index.html &amp;</code></li>
</ul> </ul>
<h3 id="executing-specific-tests">Executing specific tests</h3>
<p>Add a <a href="https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group"><code>@group</code></a> annotation in a test class or method comment:<a href=".html"></a></p>
<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="co">/**</span>
<span class="co"> * Netscape bookmark import</span>
<span class="co"> * </span><span class="an">@group</span><span class="co"> WIP</span>
<span class="co"> */</span>
<span class="kw">class</span> BookmarkImportTest <span class="kw">extends</span> PHPUnit_Framework_TestCase
{
<span class="ot">[</span><span class="st">...</span><span class="ot">][](</span>.html<span class="ot">)</span>
}</code></pre></div>
<p>To run all tests annotated with <code>@group WIP</code>:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">vendor/bin/phpunit</span> --group WIP tests/</code></pre></div>
</body> </body>
</html> </html>

View file

@ -126,3 +126,22 @@ If Xdebug has been installed and activated, two coverage reports will be generat
* a summary in the console * a summary in the console
* a detailed HTML report with metrics for tested code * a detailed HTML report with metrics for tested code
* to open it in a web browser: `firefox coverage/index.html &` * to open it in a web browser: `firefox coverage/index.html &`
### Executing specific tests
Add a [`@group`](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group) annotation in a test class or method comment:[](.html)
```php
/**
* Netscape bookmark import
* @group WIP
*/
class BookmarkImportTest extends PHPUnit_Framework_TestCase
{
[...][](.html)
}
```
To run all tests annotated with `@group WIP`:
```bash
$ vendor/bin/phpunit --group WIP tests/
```

View file

@ -69,6 +69,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -87,6 +88,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>
@ -101,12 +103,16 @@
</div> </div>
<h1 id="upgrade-and-migration">Upgrade and migration</h1> <h1 id="upgrade-and-migration">Upgrade and migration</h1>
<h2 id="preparation">Preparation</h2> <h2 id="preparation">Preparation</h2>
<h3 id="note-your-current-version">Note your current version</h3>
<p>If anything goes wrong, it's important for us to know which version you're upgrading from.<br />
The current version is present in the <code>version.php</code> file.</p>
<h3 id="backup-your-data">Backup your data</h3> <h3 id="backup-your-data">Backup your data</h3>
<p>Shaarli stores all user data under the <code>data</code> directory:</p> <p>Shaarli stores all user data under the <code>data</code> directory:</p>
<ul> <ul>
<li><code>data/config.php</code> - main configuration file</li> <li><code>data/config.php</code> - main configuration file</li>
<li><code>data/datastore.php</code> - bookmarked links</li> <li><code>data/datastore.php</code> - bookmarked links</li>
<li><code>data/ipbans.php</code> - banned IP addresses</li> <li><code>data/ipbans.php</code> - banned IP addresses</li>
<li><code>data/updates.txt</code> - contains all automatic update to the configuration and datastore files already run</li>
</ul> </ul>
<p>See <a href="Shaarli-configuration.html">Shaarli configuration</a> for more information about Shaarli resources.</p> <p>See <a href="Shaarli-configuration.html">Shaarli configuration</a> for more information about Shaarli resources.</p>
<p>It is recommended to backup this repository <em>before</em> starting updating/upgrading Shaarli:</p> <p>It is recommended to backup this repository <em>before</em> starting updating/upgrading Shaarli:</p>
@ -125,15 +131,11 @@ <h3 id="migrating-data-from-a-previous-installation">Migrating data from a previ
</ul></li> </ul></li>
<li>check or restore the <code>data</code> directory</li> <li>check or restore the <code>data</code> directory</li>
</ul> </ul>
<h2 id="upgrading-from-release-archives">Upgrading from release archives</h2> <h2 id="recommended-upgrading-from-release-archives">Recommended : Upgrading from release archives</h2>
<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> <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>
<p>We <em>recommend</em> using the releases from the <code>stable</code> branch, which are available as:</p> <p>We recommend that you use the latest release tarball with the <code>-full</code> suffix. It contains the dependencies, please read <a href="Download-and-installation.html">Download and installation</a> for <code>git</code> complete instructions.</p>
<ul> <p>Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the <code>data</code> directory!</p>
<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> <p>After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to <code>data/config.json.php</code> (see <a href="Shaarli-configuration.html">Shaarli configuration</a> for more details).</p>
<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>
</ul>
<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>
<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>
<h2 id="upgrading-with-git">Upgrading with Git</h2> <h2 id="upgrading-with-git">Upgrading with Git</h2>
<h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3> <h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3>
<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> <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>
@ -149,7 +151,7 @@ <h3 id="updating-a-community-shaarli">Updating a community Shaarli</h3>
<span class="ex">tests/Url/UrlTest.php</span> <span class="kw">|</span> <span class="ex">1</span> + <span class="ex">tests/Url/UrlTest.php</span> <span class="kw">|</span> <span class="ex">1</span> +
<span class="ex">3</span> files changed, 3 insertions(+), <span class="ex">1</span> deletion(-)</code></pre></div> <span class="ex">3</span> files changed, 3 insertions(+), <span class="ex">1</span> deletion(-)</code></pre></div>
<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> <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>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> install --no-dev
<span class="ex">Loading</span> composer repositories with package information <span class="ex">Loading</span> composer repositories with package information
<span class="ex">Updating</span> dependencies <span class="ex">Updating</span> dependencies
@ -214,7 +216,7 @@ <h4 id="step-2-use-the-stable-community-branch">Step 2: use the stable community
<span class="ex">master</span> 029f75f [sebsauvage/master] Update README.md[](.html) <span class="ex">master</span> 029f75f [sebsauvage/master] Update README.md[](.html)
<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> <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>
<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> <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>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> update --no-dev <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="ex">composer</span> install --no-dev
<span class="ex">Loading</span> composer repositories with package information <span class="ex">Loading</span> composer repositories with package information
<span class="ex">Updating</span> dependencies <span class="ex">Updating</span> dependencies
@ -238,5 +240,20 @@ <h4 id="step-2-use-the-stable-community-branch">Step 2: use the stable community
<span class="ex">Total</span> 3317 (delta 2050), <span class="ex">reused</span> 3301 (delta 2034)<span class="ex">to</span></code></pre></div> <span class="ex">Total</span> 3317 (delta 2050), <span class="ex">reused</span> 3301 (delta 2034)<span class="ex">to</span></code></pre></div>
<h4 id="step-3-configuration">Step 3: configuration</h4> <h4 id="step-3-configuration">Step 3: configuration</h4>
<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> <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>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>If the solutions provided here doesn't work, please open an issue specifying which version you're upgrading from and to.</p>
<h3 id="you-must-specify-an-integer-as-a-key">You must specify an integer as a key</h3>
<p>In <code>v0.8.1</code> we changed how link keys are handled (from timestamps to incremental integers).<br />
Take a look at <code>data/updates.txt</code> content.</p>
<h4 id="updates.txt-contains-updatemethoddatastoreids"><code>updates.txt</code> contains <code>updateMethodDatastoreIds</code></h4>
<p>Try to delete it and refresh your page while being logged in.</p>
<h4 id="updates.txt-doesnt-exists-or-doesnt-contain-updatemethoddatastoreids"><code>updates.txt</code> doesn't exists or doesn't contain <code>updateMethodDatastoreIds</code></h4>
<ol>
<li>Create <code>data/updates.txt</code> if it doesn't exist.</li>
<li>Paste this string in the update file <code>;updateMethodRenameDashTags;</code></li>
<li>Login to Shaarli.</li>
<li>Delete the update file.</li>
<li>Refresh.</li>
</ol>
</body> </body>
</html> </html>

View file

@ -1,11 +1,17 @@
#Upgrade and migration #Upgrade and migration
## Preparation ## Preparation
### Note your current version
If anything goes wrong, it's important for us to know which version you're upgrading from.
The current version is present in the `version.php` file.
### Backup your data ### Backup your data
Shaarli stores all user data under the `data` directory: Shaarli stores all user data under the `data` directory:
- `data/config.php` - main configuration file - `data/config.php` - main configuration file
- `data/datastore.php` - bookmarked links - `data/datastore.php` - bookmarked links
- `data/ipbans.php` - banned IP addresses - `data/ipbans.php` - banned IP addresses
- `data/updates.txt` - contains all automatic update to the configuration and datastore files already run
See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources. See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources.
@ -22,16 +28,14 @@ As all user data is kept under `data`, this is the only directory you need to wo
- update - see the following sections - update - see the following sections
- check or restore the `data` directory - check or restore the `data` directory
## Upgrading from release archives ## Recommended : Upgrading from release archives
All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html)
We _recommend_ using the releases from the `stable` branch, which are available as: We recommend that you use the latest release tarball with the `-full` suffix. It contains the dependencies, please read [Download and installation](Download-and-installation.html) for `git` complete instructions.
- gzipped tarball - https://github.com/shaarli/Shaarli/archive/stable.tar.gz
- ZIP archive - https://github.com/shaarli/Shaarli/archive/stable.zip
Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the `data` directory! Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the content of the `data` directory!
After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). After upgrading, access your fresh Shaarli installation from a web browser; the configuration and data store will then be automatically updated, and new settings added to `data/config.json.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details).
## Upgrading with Git ## Upgrading with Git
### Updating a community Shaarli ### Updating a community Shaarli
@ -54,7 +58,7 @@ Fast-forward
Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html)
```bash ```bash
$ composer update --no-dev $ composer install --no-dev
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies Updating dependencies
@ -129,7 +133,7 @@ $ git branch -vv
Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html)
```bash ```bash
$ composer update --no-dev $ composer install --no-dev
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies Updating dependencies
@ -159,3 +163,24 @@ Total 3317 (delta 2050), reused 3301 (delta 2034)to
#### Step 3: configuration #### Step 3: configuration
After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details).
## Troubleshooting
If the solutions provided here doesn't work, please open an issue specifying which version you're upgrading from and to.
### You must specify an integer as a key
In `v0.8.1` we changed how link keys are handled (from timestamps to incremental integers).
Take a look at `data/updates.txt` content.
#### `updates.txt` contains `updateMethodDatastoreIds`
Try to delete it and refresh your page while being logged in.
#### `updates.txt` doesn't exists or doesn't contain `updateMethodDatastoreIds`
1. Create `data/updates.txt` if it doesn't exist.
2. Paste this string in the update file `;updateMethodRenameDashTags;`
3. Login to Shaarli.
4. Delete the update file.
5. Refresh.

View file

@ -32,6 +32,7 @@
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li> <li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li> <li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li> <li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li> </ul></li>
<li>How To <li>How To
<ul> <ul>
@ -50,6 +51,7 @@
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li> <li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li> <li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li> <li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li> <li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li> <li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li> <li><a href="Theming.html">Theming</a></li>

View file

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Shaarli Versioning and Branches</title>
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="github-markdown.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<div id="local-sidebar">
<ul>
<li><a href="Home.html">Home</a></li>
<li>Setup
<ul>
<li><a href="Download-and-Installation.html">Download and Installation</a></li>
<li><a href="Upgrade-and-migration.html">Upgrade and migration</a></li>
<li><a href="Server-requirements.html">Server requirements</a></li>
<li><a href="Server-configuration.html">Server configuration</a></li>
<li><a href="Server-security.html">Server security</a></li>
<li><a href="Shaarli-configuration.html">Shaarli configuration</a></li>
<li><a href="Plugins.html">Plugins</a></li>
</ul></li>
<li><a href="Docker.html">Docker</a></li>
<li><a href="Usage.html">Usage</a>
<ul>
<li><a href="Sharing-button.html">Sharing button</a> (bookmarklet)</li>
<li><a href="Browsing-and-Searching.html">Browsing and Searching</a></li>
<li><a href="Firefox-share.html">Firefox share</a></li>
<li><a href="RSS-feeds.html">RSS feeds</a></li>
<li><a href="REST-API.html">REST API</a></li>
</ul></li>
<li>How To
<ul>
<li><a href="Backup,-restore,-import-and-export.html">Backup, restore, import and export</a></li>
<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>
<li><a href="Create-and-serve-multiple-Shaarlis-(farm).html">Create and serve multiple Shaarlis (farm)</a></li>
<li><a href="Download-CSS-styles-from-an-OPML-list.html">Download CSS styles from an OPML list</a></li>
<li><a href="Datastore-hacks.html">Datastore hacks</a></li>
</ul></li>
<li><a href="Troubleshooting.html">Troubleshooting</a></li>
<li><a href="Development.html">Development</a>
<ul>
<li><a href="GnuPG-signature.html">GnuPG signature</a></li>
<li><a href="Coding-guidelines.html">Coding guidelines</a></li>
<li><a href="Directory-structure.html">Directory structure</a></li>
<li><a href="3rd-party-libraries.html">3rd party libraries</a></li>
<li><a href="Plugin-System.html">Plugin System</a></li>
<li><a href="Release-Shaarli.html">Release Shaarli</a></li>
<li><a href="Versioning-and-Branches.html">Versioning and Branches</a></li>
<li><a href="Security.html">Security</a></li>
<li><a href="Static-analysis.html">Static analysis</a></li>
<li><a href="Theming.html">Theming</a></li>
<li><a href="Unit-tests.html">Unit tests</a></li>
</ul></li>
<li>About
<ul>
<li><a href="FAQ.html">FAQ</a></li>
<li><a href="Community-&amp;-Related-software.html">Community &amp; Related software</a></li>
</ul></li>
</ul>
</div>
<h1 id="versioning-and-branches">Versioning and Branches</h1>
<p>[<strong>WORK IN PROGRESS</strong>][](.html)</p>
<p>It's important to understand how Shaarli branches work, especially if you're maintaining a 3rd party tools for Shaarli (theme, plugin, etc.), to be sure stay compatible.</p>
<h2 id="master-branch"><code>master</code> branch</h2>
<p>The <code>master</code> branch is the development branch. Any new change MUST go through this branch using Pull Requests.</p>
<p>Remarks:</p>
<ul>
<li>This branch shouldn't be used for production as it isn't necessary stable.</li>
<li>3rd party aren't required to be compatible with the latest changes.</li>
<li>Official plugins, themes and libraries (contained within Shaarli organization repos) must be compatible with the master branch.</li>
<li>The version in this branch is always <code>dev</code>.</li>
</ul>
<h2 id="v0.x-branch"><code>v0.x</code> branch</h2>
<p>This <code>v0.x</code> branch, points to the latest <code>v0.x.y</code> release.</p>
<p>Explanation:</p>
<p>When a new version is released, it might contains a major bug which isn't detected right away. For example, a new PHP version is released, containing backward compatibility issue which doesn't work with Shaarli.</p>
<p>In this case, the issue is fixed in the <code>master</code> branch, and the fix is backported the to the <code>v0.x</code> branch. Then a new release is made from the <code>v0.x</code> branch.</p>
<p>This workflow allow us to fix any major bug detected, without having to release bleeding edge feature too soon.</p>
<h2 id="latest-branch"><code>latest</code> branch</h2>
<p>This branch point the latest release. It recommended to use it to get the latest tested changes.</p>
<h2 id="stable-branch"><code>stable</code> branch</h2>
<p>The <code>stable</code> branch doesn't contain any major bug, and is one major digit version behind the latest release.</p>
<p>For example, the current latest release is <code>v0.8.3</code>, the stable branch is an alias to the latest <code>v0.7.x</code> release. When the <code>v0.9.0</code> version will be released, the stable will move to the latest <code>v0.8.x</code> release.</p>
<p>Remarks:</p>
<ul>
<li>Shaarli release pace isn't fast, and the stable branch might be a few months behind the latest release.</li>
</ul>
<h2 id="releases">Releases</h2>
<p>Releases are always made from the latest <code>v0.x</code> branch.</p>
<p>Note that for every release, we manually generate a tarball which contains all Shaarli dependencies, making Shaarli's installation only one step.</p>
<h2 id="advices-on-3rd-party-git-repos-workflow">Advices on 3rd party git repos workflow</h2>
<h3 id="versioning">Versioning</h3>
<p>Any time a new Shaarli release is published, you should publish a new release of your repo if the changes affected you since the latest release (take a look at the <a href="https://github.com/shaarli/Shaarli/releases">changelog</a> (<em>Draft</em> means not released yet) and the commit log (like <a href="https://github.com/shaarli/Shaarli/commits/master/tpl/default"><code>tpl</code> folder</a> for themes)). You can either:<a href=".html"></a></p>
<ul>
<li>use the Shaarli version number, with your repo version. For example, if Shaarli <code>v0.8.3</code> is released, publish a <code>v0.8.3-1</code> release, where <code>v0.8.3</code> states Shaarli compatibility and <code>-1</code> is your own version digit for the current Shaarli version.</li>
<li>use your own versioning scheme, and state Shaarli compatibility in the release description.</li>
</ul>
<p>Using this, any user will be able to pick the release matching his own Shaarli version.</p>
<h3 id="major-bugfix-backport-releases">Major bugfix backport releases</h3>
<p>To be able to support backported fixes, it recommended to use our workflow:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="co"># In master, fix the major bug</span>
<span class="fu">git</span> commit -m <span class="st">&quot;Katastrophe&quot;</span>
<span class="fu">git</span> push origin master
<span class="co"># Get your commit hash</span>
<span class="fu">git</span> log --format=<span class="st">&quot;%H&quot;</span> -n 1
<span class="co"># Create a new branch from your latest release, let&#39;s say v0.8.2-1 (the tag name)</span>
<span class="fu">git</span> checkout -b katastrophe v0.8.2-1
<span class="co"># Backport the fix commit to your brand new branch</span>
<span class="fu">git</span> cherry-pick <span class="op">&lt;</span>fix commit hash<span class="op">&gt;</span>
<span class="fu">git</span> push origin katastrophe
<span class="co"># Then you just have to make a new release from the `katastrophe` branch tagged `v0.8.3-1`</span></code></pre></div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more