Release v0.11.0

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEWe5LuNiFNDXAgI8BOzJIyqqwgW4FAl08H7AACgkQOzJIyqqw
 gW4dEw/9F55N9HMK1xTByxsnrMihjzBaKKc1lBBNJizAXrX2QchgnhE15ATRnQNy
 /7GUU8hCRukBsffMp7Ve1tbPkVvQwWgyQn2Hpp+ayGNWgQYrU1jNSaCQcbyxybyP
 6e+8DFAdDsleHiYCSZBPUHMpiJyQWsVBDV1wQPRrqvm+JYE3+9IwHzm+9/y4sk55
 7bp5Mj7fYyts5AJfLj9gxg2juGRnnhKXGWj2WI4Yk1mpwQLFSf43wC8lFf0ASY1J
 PfhjwOOFCRv/7LOL66nIPp74+pKcyO/S8p2m/pFNgrHL2bJXaAmFMPmYQjyoFmaA
 83iM5Jv3fBXMSf/iHnPvQlD0nmIvXUeu5ftBUIE/C4Uwu8LZTlOsPelW1dH5ygGa
 TVaA3/vlRhDWATe9mRNrHPHQT3VoxHg8U3qIv3p3cakj1uRFaFvkKhI7dEoqFSJY
 zsmISLbPMbmvJkMMNT4sI2q3ioyGDiU0OSayKocJziiu/H9+c2Pdty3YOSvJp/SX
 sjgqSX/hwtNmpQnS63dweDLoBGWjj01MYgedI9r64kmfW3QoSYsdVfykEMHIfofw
 /g8hRMBmuzK0VuDrla6DIBl7s58w0Uepr+e/lFMI4pzwHzxzUCZ5lc6wG0yCxuq2
 R+wTbpLqeXghKIaprmxq9i1TnAiCIl+lmw9zKj3M3fXwBGQ8e4I=
 =c7Xq
 -----END PGP SIGNATURE-----

Merge tag 'v0.11.0' into myShaarli_commu

Release v0.11.0
This commit is contained in:
Knah Tsaeb 2019-08-12 14:16:22 +02:00
commit 984073a980
212 changed files with 5288 additions and 5465 deletions

View file

@ -2,9 +2,11 @@ options:
max-warnings: 0 max-warnings: 0
rules: rules:
property-sort-order: property-sort-order:
- 1 - 0
- # Sort order rule does not work with CSS variables: https://github.com/sasstools/sass-lint/issues/1161
order: 'concentric' # - 1
# -
# order: 'concentric'
no-important: no-important:
- 0 - 0
no-vendor-prefixes: no-vendor-prefixes:

9
.gitignore vendored
View file

@ -22,15 +22,17 @@ inc/languages/*/LC_MESSAGES/shaarli.mo
# Development and test resources # Development and test resources
coverage coverage
doxygen
sandbox sandbox
phpmd.html phpmd.html
phpdoc.xml
# User plugin configuration # User plugin configuration
plugins/*/config.php plugins/*/config.php
plugins/default_colors/default_colors.css
# HTML documentation # HTML documentation
doc/html/ doc/html/
doc/phpdoc/
# 3rd party themes # 3rd party themes
tpl/* tpl/*
@ -51,4 +53,7 @@ tpl/vintage/js
tpl/vintage/css tpl/vintage/css
tpl/vintage/img tpl/vintage/img
.composer.lock .composer.lock
# Documented scripts
generate_templates.php

15
.readthedocs.yml Normal file
View file

@ -0,0 +1,15 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation with MkDocs
mkdocs:
configuration: mkdocs.yml
# Optionally set the version of Python and requirements required to build your docs
# https://github.com/rtfd/readthedocs.org/issues/5250
python:
version: 3.5

View file

@ -3,6 +3,8 @@ dist: trusty
matrix: matrix:
include: include:
- language: php
php: 7.3
- language: php - language: php
php: 7.2 php: 7.2
- language: php - language: php

10
AUTHORS
View file

@ -1,9 +1,10 @@
716 ArthurHoaro <arthur@hoa.ro> 769 ArthurHoaro <arthur@hoa.ro>
372 VirtualTam <virtualtam@flibidi.net> 401 VirtualTam <virtualtam@flibidi.net>
208 nodiscc <nodiscc@gmail.com> 216 nodiscc <nodiscc@gmail.com>
56 Sébastien Sauvage <sebsauvage@sebsauvage.net> 56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
15 Florian Eula <eula.florian@gmail.com> 15 Florian Eula <eula.florian@gmail.com>
13 Emilien Klein <emilien@klein.st> 13 Emilien Klein <emilien@klein.st>
13 Luce Carević <lcarevic@access42.net>
12 Nicolas Danelon <hi@nicolasmd.com.ar> 12 Nicolas Danelon <hi@nicolasmd.com.ar>
9 Willi Eggeling <thewilli@gmail.com> 9 Willi Eggeling <thewilli@gmail.com>
8 Christophe HENRY <christophe.henry@sbgodin.fr> 8 Christophe HENRY <christophe.henry@sbgodin.fr>
@ -15,6 +16,7 @@
4 Alexandre Alapetite <alexandre@alapetite.fr> 4 Alexandre Alapetite <alexandre@alapetite.fr>
4 David Sferruzza <david.sferruzza@gmail.com> 4 David Sferruzza <david.sferruzza@gmail.com>
4 Immánuel Fodor <immanuelfactor+github@gmail.com> 4 Immánuel Fodor <immanuelfactor+github@gmail.com>
3 Agurato <mail.vmonot@gmail.com>
3 Teromene <teromene@teromene.fr> 3 Teromene <teromene@teromene.fr>
2 Alexandre G.-Raymond <alex@ndre.gr> 2 Alexandre G.-Raymond <alex@ndre.gr>
2 Chris Kuethe <chris.kuethe@gmail.com> 2 Chris Kuethe <chris.kuethe@gmail.com>
@ -29,6 +31,7 @@
2 julienCXX <software@chmodplusx.eu> 2 julienCXX <software@chmodplusx.eu>
2 philipp-r <philipp-r@users.noreply.github.com> 2 philipp-r <philipp-r@users.noreply.github.com>
2 pips <pips@e5150.fr> 2 pips <pips@e5150.fr>
2 trailjeep <trailjeep@gmail.com>
1 Adrien Oliva <adrien.oliva@yapbreak.fr> 1 Adrien Oliva <adrien.oliva@yapbreak.fr>
1 Adrien le Maire <adrien@alemaire.be> 1 Adrien le Maire <adrien@alemaire.be>
1 Alexis J <alexis@effingo.be> 1 Alexis J <alexis@effingo.be>
@ -56,6 +59,7 @@
1 Mark Gerarts <mark.gerarts@gmail.com> 1 Mark Gerarts <mark.gerarts@gmail.com>
1 Marsup <marsup@gmail.com> 1 Marsup <marsup@gmail.com>
1 Neros <contact@neros.fr> 1 Neros <contact@neros.fr>
1 Rajat Hans <rajathans9@gmail.com>
1 Sbgodin <Sbgodin@users.noreply.github.com> 1 Sbgodin <Sbgodin@users.noreply.github.com>
1 TsT <tst2005@gmail.com> 1 TsT <tst2005@gmail.com>
1 dimtion <zizou.xena@gmail.com> 1 dimtion <zizou.xena@gmail.com>

View file

@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) 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/).
<<<<<<< HEAD
## [v0.10.4](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) - 2019-04-16 ## [v0.10.4](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) - 2019-04-16
### Fixed ### Fixed
@ -39,6 +40,89 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed ### Removed
- Remove Firefox Share documentation - Remove Firefox Share documentation
||||||| merged common ancestors
=======
## [v0.11.0](https://github.com/shaarli/Shaarli/releases/tag/v0.11.0) - 2019-07-27
**Shaarli no longer officially support PHP 5.6 and PHP 7.0 as they've reached end of life.**
**Shaarli classes now use namespace, third party plugins need to update.**
### Added
- Add optional PHP extension to composer suggestions.
- composer: enforce PHP security advisories
- phpDocumentor configuration and make target
- Run unit tests against PHP 7.3
- Bunch of accessibility improvements to the default template, thanks to @llune
- Bulk actions: set visibility
- Display sticky label in linklist
- Add print CSS rules to the default template
- New setting to automatically retrieve description for new bookmarks
- Plugin to override default template colors
### Changed
- Shaarli now uses namespaces for its classes.
- Rewrite IP ban management
- Default template: slightly lighten visited link color
- Hide select all button on mobile view
- Switch from FontAwesome v4.x to ForkAwesome
- Daily - display the current day instead of the previous one
### Fixed
- Do not check the IP address with session protection disabled
- API: update test regexes to comply with PCRE2
- Optimize and cleanup imports
- ensure HTML tags are stripped from OpenGraph description
- Documentation invalid links
- Thumbnails disabling if PHP GD is not installed
- Warning if links sticky status isn't set
- Fix button overlapping on mobile in linklist
- Do not try to retrieve thumbnails for internal link
- Update node-sass to fix a vulnerability in node tar dependency
- armhf Dockerfile
- Default template: Responsive issue with delete button fix
- Persist sticky status on bookmark update
### Removed
- Doxygen configuration
- redirector setting
- QRCode link to an external service
## [v0.10.4](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4) - 2019-04-16
### Fixed
- Fix thumbnails disabling if PHP GD is not installed
- Fix a warning if links sticky status isn't set
## [v0.10.3](https://github.com/shaarli/Shaarli/releases/tag/v0.10.3) - 2019-02-23
### Added
- Add OpenGraph metadata tags on permalink page
- Add CORS headers to REST API reponses
- Add a button to toggle checkboxes of displayed links
- Add an icon to the link list when the Isso plugin is enabled
- Add noindex, nofollow to documentation pages
- Document usage of robots.txt
- Add a button to set links as sticky
### Changed
- Update French translation
- Refactor the documentation homepage
- Bump netscape-bookmark-parser
- Update session_start condition
- Improve accessibility
- Cleanup and refactor lint tooling
### Fixed
- Fix input size for dropdown search form
- Fix history for bulk link deletion
- Fix thumbnail requests
- Fix hashtag rendering when markdown escaping is enabled
- Fix AJAX tag deletion
- Fix lint errors and improve PSR-1 and PSR-2 compliance
### Removed
- Remove Firefox Share documentation
>>>>>>> v0.11.0
## [v0.10.2](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) - 2018-08-11 ## [v0.10.2](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) - 2018-08-11
### Fixed ### Fixed

View file

@ -1,4 +1,34 @@
FROM lsiobase/alpine.armhf:3.6 # Stage 1:
# - Copy Shaarli sources
# - Build documentation
FROM arm32v6/alpine:3.8 as docs
ADD . /usr/src/app/shaarli
RUN apk --update --no-cache add py2-pip \
&& cd /usr/src/app/shaarli \
&& pip install --no-cache-dir mkdocs \
&& mkdocs build --clean
# Stage 2:
# - Resolve PHP dependencies with Composer
FROM arm32v6/alpine:3.8 as composer
COPY --from=docs /usr/src/app/shaarli /app/shaarli
RUN apk --update --no-cache add php7-curl php7-mbstring composer \
&& cd /app/shaarli \
&& composer --prefer-dist --no-dev install
# Stage 3:
# - Frontend dependencies
FROM arm32v6/alpine:3.8 as node
COPY --from=composer /app/shaarli /shaarli
RUN apk --update --no-cache add yarn nodejs-current python2 build-base \
&& cd /shaarli \
&& yarn install \
&& yarn run build \
&& rm -rf node_modules
# Stage 4:
# - Shaarli image
FROM arm32v6/alpine:3.8
LABEL maintainer="Shaarli Community" LABEL maintainer="Shaarli Community"
MAINTAINER Shaarli Community MAINTAINER Shaarli Community

2377
Doxyfile

File diff suppressed because it is too large Load diff

View file

@ -146,10 +146,9 @@ authors:
@git shortlog -sne > AUTHORS @git shortlog -sne > AUTHORS
@rm .mailmap @rm .mailmap
### generate Doxygen documentation ### generate phpDocumentor documentation
doxygen: clean phpdoc: clean
@rm -rf doxygen @docker run --rm -v $(PWD):/data -u `id -u`:`id -g` phpdoc/phpdoc
@doxygen Doxyfile
### generate HTML documentation from Markdown pages with MkDocs ### generate HTML documentation from Markdown pages with MkDocs
htmldoc: htmldoc:

View file

@ -9,10 +9,10 @@ _It is designed to be personal (single-user), fast and handy._
[![](https://img.shields.io/badge/stable-v0.9.7-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.7) [![](https://img.shields.io/badge/stable-v0.9.7-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.9.7)
[![](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)
&bull; &bull;
[![](https://img.shields.io/badge/latest-v0.10.2-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) [![](https://img.shields.io/badge/latest-v0.10.4-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.4)
[![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli) [![](https://img.shields.io/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
&bull; &bull;
[![](https://img.shields.io/badge/master-v0.10.x-blue.svg)](https://github.com/shaarli/Shaarli) [![](https://img.shields.io/badge/master-v0.11.x-blue.svg)](https://github.com/shaarli/Shaarli)
[![](https://img.shields.io/travis/shaarli/Shaarli.svg?label=master)](https://travis-ci.org/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)

View file

@ -1,4 +1,9 @@
<?php <?php
namespace Shaarli;
use Exception;
use Shaarli\Config\ConfigManager;
/** /**
* Shaarli (application) utilities * Shaarli (application) utilities
*/ */
@ -51,7 +56,7 @@ public static function getVersion($remote, $timeout = 2)
return false; return false;
} }
} else { } else {
if (! is_file($remote)) { if (!is_file($remote)) {
return false; return false;
} }
$data = file_get_contents($remote); $data = file_get_contents($remote);
@ -97,7 +102,7 @@ public static function checkUpdate(
// Do not check versions for visitors // Do not check versions for visitors
// Do not check if the user doesn't want to // Do not check if the user doesn't want to
// Do not check with dev version // Do not check with dev version
if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') { if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
return false; return false;
} }
@ -111,7 +116,7 @@ public static function checkUpdate(
return false; return false;
} }
if (! in_array($branch, self::$GIT_BRANCHES)) { if (!in_array($branch, self::$GIT_BRANCHES)) {
throw new Exception( throw new Exception(
'Invalid branch selected for updates: "' . $branch . '"' 'Invalid branch selected for updates: "' . $branch . '"'
); );
@ -123,7 +128,7 @@ public static function checkUpdate(
self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
); );
if (! $latestVersion) { if (!$latestVersion) {
// Only update the file's modification date // Only update the file's modification date
file_put_contents($updateFile, $currentVersion); file_put_contents($updateFile, $currentVersion);
return false; return false;
@ -152,9 +157,9 @@ public static function checkPHPVersion($minVersion, $curVersion)
if (version_compare($curVersion, $minVersion) < 0) { if (version_compare($curVersion, $minVersion) < 0) {
$msg = t( $msg = t(
'Your PHP version is obsolete!' 'Your PHP version is obsolete!'
. ' Shaarli requires at least PHP %s, and thus cannot run.' . ' Shaarli requires at least PHP %s, and thus cannot run.'
. ' Your PHP version has known security vulnerabilities and should be' . ' Your PHP version has known security vulnerabilities and should be'
. ' updated as soon as possible.' . ' updated as soon as possible.'
); );
throw new Exception(sprintf($msg, $minVersion)); throw new Exception(sprintf($msg, $minVersion));
} }
@ -174,50 +179,50 @@ public static function checkResourcePermissions($conf)
// Check script and template directories are readable // Check script and template directories are readable
foreach (array( foreach (array(
'application', 'application',
'inc', 'inc',
'plugins', 'plugins',
$rainTplDir, $rainTplDir,
$rainTplDir.'/'.$conf->get('resource.theme'), $rainTplDir . '/' . $conf->get('resource.theme'),
) as $path) { ) as $path) {
if (! is_readable(realpath($path))) { if (!is_readable(realpath($path))) {
$errors[] = '"'.$path.'" '. t('directory is not readable'); $errors[] = '"' . $path . '" ' . t('directory is not readable');
} }
} }
// Check cache and data directories are readable and writable // Check cache and data directories are readable and writable
foreach (array( foreach (array(
$conf->get('resource.thumbnails_cache'), $conf->get('resource.thumbnails_cache'),
$conf->get('resource.data_dir'), $conf->get('resource.data_dir'),
$conf->get('resource.page_cache'), $conf->get('resource.page_cache'),
$conf->get('resource.raintpl_tmp'), $conf->get('resource.raintpl_tmp'),
) as $path) { ) as $path) {
if (! is_readable(realpath($path))) { if (!is_readable(realpath($path))) {
$errors[] = '"'.$path.'" '. t('directory is not readable'); $errors[] = '"' . $path . '" ' . t('directory is not readable');
} }
if (! is_writable(realpath($path))) { if (!is_writable(realpath($path))) {
$errors[] = '"'.$path.'" '. t('directory is not writable'); $errors[] = '"' . $path . '" ' . t('directory is not writable');
} }
} }
// Check configuration files are readable and writable // Check configuration files are readable and writable
foreach (array( foreach (array(
$conf->getConfigFileExt(), $conf->getConfigFileExt(),
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
$conf->get('resource.ban_file'), $conf->get('resource.ban_file'),
$conf->get('resource.log'), $conf->get('resource.log'),
$conf->get('resource.update_check'), $conf->get('resource.update_check'),
) as $path) { ) as $path) {
if (! is_file(realpath($path))) { if (!is_file(realpath($path))) {
# the file may not exist yet # the file may not exist yet
continue; continue;
} }
if (! is_readable(realpath($path))) { if (!is_readable(realpath($path))) {
$errors[] = '"'.$path.'" '. t('file is not readable'); $errors[] = '"' . $path . '" ' . t('file is not readable');
} }
if (! is_writable(realpath($path))) { if (!is_writable(realpath($path))) {
$errors[] = '"'.$path.'" '. t('file is not writable'); $errors[] = '"' . $path . '" ' . t('file is not writable');
} }
} }

View file

@ -1,6 +1,8 @@
<?php <?php
require_once 'exceptions/IOException.php'; namespace Shaarli;
use Shaarli\Exceptions\IOException;
/** /**
* Class FileUtils * Class FileUtils
@ -44,7 +46,7 @@ public static function writeFlatDB($file, $content)
return file_put_contents( return file_put_contents(
$file, $file,
self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix
); );
} }
@ -62,7 +64,7 @@ public static function readFlatDB($file, $default = null)
{ {
// Note that gzinflate is faster than gzuncompress. // Note that gzinflate is faster than gzuncompress.
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
if (! is_readable($file)) { if (!is_readable($file)) {
return $default; return $default;
} }

View file

@ -1,4 +1,8 @@
<?php <?php
namespace Shaarli;
use DateTime;
use Exception;
/** /**
* Class History * Class History
@ -66,7 +70,7 @@ class History
* History constructor. * History constructor.
* *
* @param string $historyFilePath History file path. * @param string $historyFilePath History file path.
* @param int $retentionTime History content rentention time in seconds. * @param int $retentionTime History content retention time in seconds.
* *
* @throws Exception if something goes wrong. * @throws Exception if something goes wrong.
*/ */
@ -166,11 +170,11 @@ protected function addEvent($status, $id = null)
*/ */
protected function check() protected function check()
{ {
if (! is_file($this->historyFilePath)) { if (!is_file($this->historyFilePath)) {
FileUtils::writeFlatDB($this->historyFilePath, []); FileUtils::writeFlatDB($this->historyFilePath, []);
} }
if (! is_writable($this->historyFilePath)) { if (!is_writable($this->historyFilePath)) {
throw new Exception(t('History file isn\'t readable or writable')); throw new Exception(t('History file isn\'t readable or writable'));
} }
} }
@ -191,7 +195,7 @@ protected function read()
*/ */
protected function write() protected function write()
{ {
$comparaison = new DateTime('-'. $this->retentionTime . ' seconds'); $comparaison = new DateTime('-' . $this->retentionTime . ' seconds');
foreach ($this->history as $key => $value) { foreach ($this->history as $key => $value) {
if ($value['datetime'] < $comparaison) { if ($value['datetime'] < $comparaison) {
unset($this->history[$key]); unset($this->history[$key]);

View file

@ -3,7 +3,6 @@
namespace Shaarli; namespace Shaarli;
use Gettext\GettextTranslator; use Gettext\GettextTranslator;
use Gettext\Merge;
use Gettext\Translations; use Gettext\Translations;
use Gettext\Translator; use Gettext\Translator;
use Gettext\TranslatorInterface; use Gettext\TranslatorInterface;

View file

@ -1,4 +1,5 @@
<?php <?php
namespace Shaarli;
/** /**
* Class Router * Class Router
@ -37,6 +38,8 @@ class Router
public static $PAGE_DELETELINK = 'delete_link'; public static $PAGE_DELETELINK = 'delete_link';
public static $PAGE_CHANGE_VISIBILITY = 'change_visibility';
public static $PAGE_PINLINK = 'pin'; public static $PAGE_PINLINK = 'pin';
public static $PAGE_EXPORT = 'export'; public static $PAGE_EXPORT = 'export';
@ -75,43 +78,43 @@ public static function findPage($query, $get, $loggedIn)
return self::$PAGE_LINKLIST; return self::$PAGE_LINKLIST;
} }
if (startsWith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) { if (startsWith($query, 'do=' . self::$PAGE_LOGIN) && $loggedIn === false) {
return self::$PAGE_LOGIN; return self::$PAGE_LOGIN;
} }
if (startsWith($query, 'do='. self::$PAGE_PICWALL)) { if (startsWith($query, 'do=' . self::$PAGE_PICWALL)) {
return self::$PAGE_PICWALL; return self::$PAGE_PICWALL;
} }
if (startsWith($query, 'do='. self::$PAGE_TAGCLOUD)) { if (startsWith($query, 'do=' . self::$PAGE_TAGCLOUD)) {
return self::$PAGE_TAGCLOUD; return self::$PAGE_TAGCLOUD;
} }
if (startsWith($query, 'do='. self::$PAGE_TAGLIST)) { if (startsWith($query, 'do=' . self::$PAGE_TAGLIST)) {
return self::$PAGE_TAGLIST; return self::$PAGE_TAGLIST;
} }
if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { if (startsWith($query, 'do=' . self::$PAGE_OPENSEARCH)) {
return self::$PAGE_OPENSEARCH; return self::$PAGE_OPENSEARCH;
} }
if (startsWith($query, 'do='. self::$PAGE_DAILY)) { if (startsWith($query, 'do=' . self::$PAGE_DAILY)) {
return self::$PAGE_DAILY; return self::$PAGE_DAILY;
} }
if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) { if (startsWith($query, 'do=' . self::$PAGE_FEED_ATOM)) {
return self::$PAGE_FEED_ATOM; return self::$PAGE_FEED_ATOM;
} }
if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) { if (startsWith($query, 'do=' . self::$PAGE_FEED_RSS)) {
return self::$PAGE_FEED_RSS; return self::$PAGE_FEED_RSS;
} }
if (startsWith($query, 'do='. self::$PAGE_THUMBS_UPDATE)) { if (startsWith($query, 'do=' . self::$PAGE_THUMBS_UPDATE)) {
return self::$PAGE_THUMBS_UPDATE; return self::$PAGE_THUMBS_UPDATE;
} }
if (startsWith($query, 'do='. self::$AJAX_THUMB_UPDATE)) { if (startsWith($query, 'do=' . self::$AJAX_THUMB_UPDATE)) {
return self::$AJAX_THUMB_UPDATE; return self::$AJAX_THUMB_UPDATE;
} }
@ -120,23 +123,23 @@ public static function findPage($query, $get, $loggedIn)
return self::$PAGE_LINKLIST; return self::$PAGE_LINKLIST;
} }
if (startsWith($query, 'do='. self::$PAGE_TOOLS)) { if (startsWith($query, 'do=' . self::$PAGE_TOOLS)) {
return self::$PAGE_TOOLS; return self::$PAGE_TOOLS;
} }
if (startsWith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) { if (startsWith($query, 'do=' . self::$PAGE_CHANGEPASSWORD)) {
return self::$PAGE_CHANGEPASSWORD; return self::$PAGE_CHANGEPASSWORD;
} }
if (startsWith($query, 'do='. self::$PAGE_CONFIGURE)) { if (startsWith($query, 'do=' . self::$PAGE_CONFIGURE)) {
return self::$PAGE_CONFIGURE; return self::$PAGE_CONFIGURE;
} }
if (startsWith($query, 'do='. self::$PAGE_CHANGETAG)) { if (startsWith($query, 'do=' . self::$PAGE_CHANGETAG)) {
return self::$PAGE_CHANGETAG; return self::$PAGE_CHANGETAG;
} }
if (startsWith($query, 'do='. self::$PAGE_ADDLINK)) { if (startsWith($query, 'do=' . self::$PAGE_ADDLINK)) {
return self::$PAGE_ADDLINK; return self::$PAGE_ADDLINK;
} }
@ -148,27 +151,31 @@ public static function findPage($query, $get, $loggedIn)
return self::$PAGE_DELETELINK; return self::$PAGE_DELETELINK;
} }
if (startsWith($query, 'do='. self::$PAGE_PINLINK)) { if (isset($get[self::$PAGE_CHANGE_VISIBILITY])) {
return self::$PAGE_CHANGE_VISIBILITY;
}
if (startsWith($query, 'do=' . self::$PAGE_PINLINK)) {
return self::$PAGE_PINLINK; return self::$PAGE_PINLINK;
} }
if (startsWith($query, 'do='. self::$PAGE_EXPORT)) { if (startsWith($query, 'do=' . self::$PAGE_EXPORT)) {
return self::$PAGE_EXPORT; return self::$PAGE_EXPORT;
} }
if (startsWith($query, 'do='. self::$PAGE_IMPORT)) { if (startsWith($query, 'do=' . self::$PAGE_IMPORT)) {
return self::$PAGE_IMPORT; return self::$PAGE_IMPORT;
} }
if (startsWith($query, 'do='. self::$PAGE_PLUGINSADMIN)) { if (startsWith($query, 'do=' . self::$PAGE_PLUGINSADMIN)) {
return self::$PAGE_PLUGINSADMIN; return self::$PAGE_PLUGINSADMIN;
} }
if (startsWith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) { if (startsWith($query, 'do=' . self::$PAGE_SAVE_PLUGINSADMIN)) {
return self::$PAGE_SAVE_PLUGINSADMIN; return self::$PAGE_SAVE_PLUGINSADMIN;
} }
if (startsWith($query, 'do='. self::$GET_TOKEN)) { if (startsWith($query, 'do=' . self::$GET_TOKEN)) {
return self::$GET_TOKEN; return self::$GET_TOKEN;
} }

View file

@ -3,9 +3,9 @@
namespace Shaarli; namespace Shaarli;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
use WebThumbnailer\Exception\WebThumbnailerException; use WebThumbnailer\Exception\WebThumbnailerException;
use WebThumbnailer\WebThumbnailer; use WebThumbnailer\WebThumbnailer;
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
/** /**
* Class Thumbnailer * Class Thumbnailer

View file

@ -1,9 +1,8 @@
<?php <?php
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Api\Exceptions\ApiException;
use Shaarli\Api\Exceptions\ApiAuthorizationException; use Shaarli\Api\Exceptions\ApiAuthorizationException;
use Shaarli\Api\Exceptions\ApiException;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Request; use Slim\Http\Request;
@ -127,12 +126,10 @@ protected function checkToken($request)
*/ */
protected function setLinkDb($conf) protected function setLinkDb($conf)
{ {
$linkDb = new \LinkDB( $linkDb = new \Shaarli\Bookmark\LinkDB(
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
true, true,
$conf->get('privacy.hide_public_links'), $conf->get('privacy.hide_public_links')
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
); );
$this->container['db'] = $linkDb; $this->container['db'] = $linkDb;
} }

View file

@ -1,8 +1,8 @@
<?php <?php
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Base64Url;
use Shaarli\Api\Exceptions\ApiAuthorizationException; use Shaarli\Api\Exceptions\ApiAuthorizationException;
use Shaarli\Http\Base64Url;
/** /**
* REST API utilities * REST API utilities
@ -12,7 +12,7 @@ class ApiUtils
/** /**
* Validates a JWT token authenticity. * Validates a JWT token authenticity.
* *
* @param string $token JWT token extracted from the headers. * @param string $token JWT token extracted from the headers.
* @param string $secret API secret set in the settings. * @param string $secret API secret set in the settings.
* *
* @throws ApiAuthorizationException the token is not valid. * @throws ApiAuthorizationException the token is not valid.
@ -50,7 +50,7 @@ public static function validateJwtToken($token, $secret)
/** /**
* Format a Link for the REST API. * Format a Link for the REST API.
* *
* @param array $link Link data read from the datastore. * @param array $link Link data read from the datastore.
* @param string $indexUrl Shaarli's index URL (used for relative URL). * @param string $indexUrl Shaarli's index URL (used for relative URL).
* *
* @return array Link data formatted for the REST API. * @return array Link data formatted for the REST API.
@ -59,7 +59,7 @@ public static function formatLink($link, $indexUrl)
{ {
$out['id'] = $link['id']; $out['id'] = $link['id'];
// Not an internal link // Not an internal link
if ($link['url'][0] != '?') { if (! is_note($link['url'])) {
$out['url'] = $link['url']; $out['url'] = $link['url'];
} else { } else {
$out['url'] = $indexUrl . $link['url']; $out['url'] = $indexUrl . $link['url'];

View file

@ -2,8 +2,9 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use \Slim\Container; use Slim\Container;
/** /**
* Abstract Class ApiController * Abstract Class ApiController
@ -25,12 +26,12 @@ abstract class ApiController
protected $conf; protected $conf;
/** /**
* @var \LinkDB * @var LinkDB
*/ */
protected $linkDb; protected $linkDb;
/** /**
* @var \History * @var HistoryController
*/ */
protected $history; protected $history;

View file

@ -14,7 +14,7 @@
* *
* @package Shaarli\Api\Controllers * @package Shaarli\Api\Controllers
*/ */
class History extends ApiController class HistoryController extends ApiController
{ {
/** /**
* Service providing operation regarding Shaarli datastore and settings. * Service providing operation regarding Shaarli datastore and settings.

View file

@ -4,7 +4,6 @@
use Shaarli\Api\ApiUtils; use Shaarli\Api\ApiUtils;
use Shaarli\Api\Exceptions\ApiBadParametersException; use Shaarli\Api\Exceptions\ApiBadParametersException;
use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
use Shaarli\Api\Exceptions\ApiTagNotFoundException; use Shaarli\Api\Exceptions\ApiTagNotFoundException;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;

View file

@ -2,8 +2,6 @@
namespace Shaarli\Api\Exceptions; namespace Shaarli\Api\Exceptions;
use Slim\Http\Response;
/** /**
* Class ApiLinkNotFoundException * Class ApiLinkNotFoundException
* *

View file

@ -2,8 +2,6 @@
namespace Shaarli\Api\Exceptions; namespace Shaarli\Api\Exceptions;
use Slim\Http\Response;
/** /**
* Class ApiTagNotFoundException * Class ApiTagNotFoundException
* *

View file

@ -1,4 +1,15 @@
<?php <?php
namespace Shaarli\Bookmark;
use ArrayAccess;
use Countable;
use DateTime;
use Iterator;
use Shaarli\Bookmark\Exception\LinkNotFoundException;
use Shaarli\Exceptions\IOException;
use Shaarli\FileUtils;
/** /**
* Data storage for links. * Data storage for links.
* *
@ -18,10 +29,10 @@
* - private: Is this link private? 0=no, other value=yes * - private: Is this link private? 0=no, other value=yes
* - tags: tags attached to this entry (separated by spaces) * - tags: tags attached to this entry (separated by spaces)
* - title Title of the link * - title Title of the link
* - url URL of the link. Used for displayable links (no redirector, relative, etc.). * - url URL of the link. Used for displayable links.
* Can be absolute or relative. * Can be absolute or relative in the database but the relative links
* Relative URLs are permalinks (e.g.'?m-ukcw') * will be converted to absolute ones in templates.
* - real_url Absolute processed URL. * - real_url Raw URL in stored in the DB (absolute or relative).
* - shorturl Permalink smallhash * - shorturl Permalink smallhash
* *
* Implements 3 interfaces: * Implements 3 interfaces:
@ -77,19 +88,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// Hide public links // Hide public links
private $hidePublicLinks; private $hidePublicLinks;
// link redirector set in user settings.
private $redirector;
/**
* Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched.
*
* Example:
* anonym.to needs clean URL while dereferer.org needs urlencoded URL.
*
* @var boolean $redirectorEncode parameter: true or false
*/
private $redirectorEncode;
/** /**
* Creates a new LinkDB * Creates a new LinkDB
* *
@ -98,21 +96,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
* @param string $datastore datastore file path. * @param string $datastore datastore file path.
* @param boolean $isLoggedIn is the user logged in? * @param boolean $isLoggedIn is the user logged in?
* @param boolean $hidePublicLinks if true all links are private. * @param boolean $hidePublicLinks if true all links are private.
* @param string $redirector link redirector set in user settings.
* @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true).
*/ */
public function __construct( public function __construct(
$datastore, $datastore,
$isLoggedIn, $isLoggedIn,
$hidePublicLinks, $hidePublicLinks
$redirector = '',
$redirectorEncode = true
) { ) {
$this->datastore = $datastore; $this->datastore = $datastore;
$this->loggedIn = $isLoggedIn; $this->loggedIn = $isLoggedIn;
$this->hidePublicLinks = $hidePublicLinks; $this->hidePublicLinks = $hidePublicLinks;
$this->redirector = $redirector;
$this->redirectorEncode = $redirectorEncode === true;
$this->check(); $this->check();
$this->read(); $this->read();
} }
@ -137,7 +130,7 @@ public function offsetSet($offset, $value)
if (!isset($value['id']) || empty($value['url'])) { if (!isset($value['id']) || empty($value['url'])) {
die(t('Internal Error: A link should always have an id and URL.')); die(t('Internal Error: A link should always have an id and URL.'));
} }
if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { if (($offset !== null && !is_int($offset)) || !is_int($value['id'])) {
die(t('You must specify an integer as a key.')); die(t('You must specify an integer as a key.'));
} }
if ($offset !== null && $offset !== $value['id']) { if ($offset !== null && $offset !== $value['id']) {
@ -247,19 +240,19 @@ private function check()
$this->links = array(); $this->links = array();
$link = array( $link = array(
'id' => 1, 'id' => 1,
'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'), 'title' => t('The personal, minimalist, super-fast, database free, bookmarking service'),
'url'=>'https://shaarli.readthedocs.io', 'url' => 'https://shaarli.readthedocs.io',
'description'=>t( 'description' => t(
'Welcome to Shaarli! This is your first public bookmark. ' 'Welcome to Shaarli! This is your first public bookmark. '
.'To edit or delete me, you must first login. . 'To edit or delete me, you must first login.
To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page. To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
), ),
'private'=>0, 'private' => 0,
'created'=> new DateTime(), 'created' => new DateTime(),
'tags'=>'opensource software', 'tags' => 'opensource software',
'sticky' => false, 'sticky' => false,
); );
$link['shorturl'] = link_small_hash($link['created'], $link['id']); $link['shorturl'] = link_small_hash($link['created'], $link['id']);
@ -267,12 +260,12 @@ private function check()
$link = array( $link = array(
'id' => 0, 'id' => 0,
'title'=> t('My secret stuff... - Pastebin.com'), 'title' => t('My secret stuff... - Pastebin.com'),
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'), 'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
'private'=>1, 'private' => 1,
'created'=> new DateTime('1 minute ago'), 'created' => new DateTime('1 minute ago'),
'tags'=>'secretstuff', 'tags' => 'secretstuff',
'sticky' => false, 'sticky' => false,
); );
$link['shorturl'] = link_small_hash($link['created'], $link['id']); $link['shorturl'] = link_small_hash($link['created'], $link['id']);
@ -299,7 +292,7 @@ private function read()
$toremove = array(); $toremove = array();
foreach ($this->links as $key => &$link) { foreach ($this->links as $key => &$link) {
if (! $this->loggedIn && $link['private'] != 0) { if (!$this->loggedIn && $link['private'] != 0) {
// Transition for not upgraded databases. // Transition for not upgraded databases.
unset($this->links[$key]); unset($this->links[$key]);
continue; continue;
@ -309,29 +302,21 @@ private function read()
sanitizeLink($link); sanitizeLink($link);
// Remove private tags if the user is not logged in. // Remove private tags if the user is not logged in.
if (! $this->loggedIn) { if (!$this->loggedIn) {
$link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']); $link['tags'] = preg_replace('/(^|\s+)\.[^($|\s)]+\s*/', ' ', $link['tags']);
} }
// Do not use the redirector for internal links (Shaarli note URL starting with a '?'). $link['real_url'] = $link['url'];
if (!empty($this->redirector) && !startsWith($link['url'], '?')) {
$link['real_url'] = $this->redirector; $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
if ($this->redirectorEncode) {
$link['real_url'] .= urlencode(unescape($link['url']));
} else {
$link['real_url'] .= $link['url'];
}
} else {
$link['real_url'] = $link['url'];
}
$link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false; $link['sticky'] = isset($link['sticky']) ? $link['sticky'] : false;
// To be able to load links before running the update, and prepare the update // To be able to load links before running the update, and prepare the update
if (! isset($link['created'])) { if (!isset($link['created'])) {
$link['id'] = $link['linkdate']; $link['id'] = $link['linkdate'];
$link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']); $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
if (! empty($link['updated'])) { if (!empty($link['updated'])) {
$link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']); $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
} }
$link['shorturl'] = smallHash($link['linkdate']); $link['shorturl'] = smallHash($link['linkdate']);
@ -417,12 +402,12 @@ public function filterDay($request)
/** /**
* Filter links according to search parameters. * Filter links according to search parameters.
* *
* @param array $filterRequest Search request content. Supported keys: * @param array $filterRequest Search request content. Supported keys:
* - 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 string $visibility return only all/private/public links * @param string $visibility return only all/private/public links
* @param string $untaggedonly return only untagged links * @param bool $untaggedonly return only untagged 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.
*/ */
@ -432,6 +417,7 @@ public function filterSearch(
$visibility = 'all', $visibility = 'all',
$untaggedonly = false $untaggedonly = false
) { ) {
// Filter link database according to parameters. // Filter link database according to parameters.
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
@ -447,8 +433,8 @@ public function filterSearch(
/** /**
* Returns the list tags appearing in the links with the given tags * Returns the list tags appearing in the links with the given tags
* *
* @param array $filteringTags tags selecting the links to consider * @param array $filteringTags tags selecting the links to consider
* @param string $visibility process only all/private/public links * @param string $visibility process only all/private/public links
* *
* @return array tag => linksCount * @return array tag => linksCount
*/ */

View file

@ -1,5 +1,10 @@
<?php <?php
namespace Shaarli\Bookmark;
use Exception;
use Shaarli\Bookmark\Exception\LinkNotFoundException;
/** /**
* Class LinkFilter. * Class LinkFilter.
* *
@ -10,22 +15,22 @@ class LinkFilter
/** /**
* @var string permalinks. * @var string permalinks.
*/ */
public static $FILTER_HASH = 'permalink'; public static $FILTER_HASH = 'permalink';
/** /**
* @var string text search. * @var string text search.
*/ */
public static $FILTER_TEXT = 'fulltext'; public static $FILTER_TEXT = 'fulltext';
/** /**
* @var string tag filter. * @var string tag filter.
*/ */
public static $FILTER_TAG = 'tags'; public static $FILTER_TAG = 'tags';
/** /**
* @var string filter by day. * @var string filter by day.
*/ */
public static $FILTER_DAY = 'FILTER_DAY'; public static $FILTER_DAY = 'FILTER_DAY';
/** /**
* @var string Allowed characters for hashtags (regex syntax). * @var string Allowed characters for hashtags (regex syntax).
@ -58,7 +63,7 @@ public function __construct($links)
*/ */
public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false) public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
{ {
if (! in_array($visibility, ['all', 'public', 'private'])) { if (!in_array($visibility, ['all', 'public', 'private'])) {
$visibility = 'all'; $visibility = 'all';
} }
@ -117,7 +122,7 @@ private function noFilter($visibility = 'all')
foreach ($this->links as $key => $value) { foreach ($this->links as $key => $value) {
if ($value['private'] && $visibility === 'private') { if ($value['private'] && $visibility === 'private') {
$out[$key] = $value; $out[$key] = $value;
} elseif (! $value['private'] && $visibility === 'public') { } elseif (!$value['private'] && $visibility === 'public') {
$out[$key] = $value; $out[$key] = $value;
} }
} }
@ -132,7 +137,7 @@ private function noFilter($visibility = 'all')
* *
* @return array $filtered array containing permalink data. * @return array $filtered array containing permalink data.
* *
* @throws LinkNotFoundException if the smallhash doesn't match any link. * @throws \Shaarli\Bookmark\Exception\LinkNotFoundException if the smallhash doesn't match any link.
*/ */
private function filterSmallHash($smallHash) private function filterSmallHash($smallHash)
{ {
@ -169,7 +174,7 @@ 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 string $visibility Optional: return only all/private/public links. * @param string $visibility Optional: return only all/private/public links.
* *
* @return array search results. * @return array search results.
*/ */
@ -207,7 +212,7 @@ private function filterFulltext($searchterms, $visibility = 'all')
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 ($visibility !== 'all') { if ($visibility !== 'all') {
if (! $link['private'] && $visibility === 'private') { if (!$link['private'] && $visibility === 'private') {
continue; continue;
} elseif ($link['private'] && $visibility === 'public') { } elseif ($link['private'] && $visibility === 'public') {
continue; continue;
@ -250,7 +255,9 @@ private function filterFulltext($searchterms, $visibility = 'all')
/** /**
* generate a regex fragment out of a tag * generate a regex fragment out of a tag
*
* @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard * @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
*
* @return string generated regex fragment * @return string generated regex fragment
*/ */
private static function tag2regex($tag) private static function tag2regex($tag)
@ -334,7 +341,7 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
// check level of visibility // check level of visibility
// ignore non private links when 'privateonly' is on. // ignore non private links when 'privateonly' is on.
if ($visibility !== 'all') { if ($visibility !== 'all') {
if (! $link['private'] && $visibility === 'private') { if (!$link['private'] && $visibility === 'private') {
continue; continue;
} elseif ($link['private'] && $visibility === 'public') { } elseif ($link['private'] && $visibility === 'public') {
continue; continue;
@ -377,7 +384,7 @@ public function filterUntagged($visibility)
$filtered = []; $filtered = [];
foreach ($this->links as $key => $link) { foreach ($this->links as $key => $link) {
if ($visibility !== 'all') { if ($visibility !== 'all') {
if (! $link['private'] && $visibility === 'private') { if (!$link['private'] && $visibility === 'private') {
continue; continue;
} elseif ($link['private'] && $visibility === 'public') { } elseif ($link['private'] && $visibility === 'public') {
continue; continue;
@ -406,7 +413,7 @@ public function filterUntagged($visibility)
*/ */
public function filterDay($day) public function filterDay($day)
{ {
if (! checkDateFormat('Ymd', $day)) { if (!checkDateFormat('Ymd', $day)) {
throw new Exception('Invalid date format'); throw new Exception('Invalid date format');
} }
@ -440,14 +447,3 @@ public static function tagsStrToArray($tags, $casesensitive)
return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY); return preg_split('/\s+/', $tagsOut, -1, PREG_SPLIT_NO_EMPTY);
} }
} }
class LinkNotFoundException extends Exception
{
/**
* LinkNotFoundException constructor.
*/
public function __construct()
{
$this->message = t('The link you are trying to reach does not exist or has been deleted.');
}
}

View file

@ -1,17 +1,31 @@
<?php <?php
use Shaarli\Bookmark\LinkDB;
/** /**
* Get cURL callback function for CURLOPT_WRITEFUNCTION * Get cURL callback function for CURLOPT_WRITEFUNCTION
* *
* @param string $charset to extract from the downloaded page (reference) * @param string $charset to extract from the downloaded page (reference)
* @param string $title to extract from the downloaded page (reference) * @param string $title to extract from the downloaded page (reference)
* @param string $curlGetInfo Optionnaly overrides curl_getinfo function * @param string $description to extract from the downloaded page (reference)
* @param string $keywords to extract from the downloaded page (reference)
* @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
* @param string $curlGetInfo Optionally overrides curl_getinfo function
* *
* @return Closure * @return Closure
*/ */
function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo') function get_curl_download_callback(
{ &$charset,
&$title,
&$description,
&$keywords,
$retrieveDescription,
$curlGetInfo = 'curl_getinfo'
) {
$isRedirected = false; $isRedirected = false;
$currentChunk = 0;
$foundChunk = null;
/** /**
* cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
* *
@ -23,7 +37,18 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
* *
* @return int|bool length of $data or false if we need to stop the download * @return int|bool length of $data or false if we need to stop the download
*/ */
return function (&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) { return function (&$ch, $data) use (
$retrieveDescription,
$curlGetInfo,
&$charset,
&$title,
&$description,
&$keywords,
&$isRedirected,
&$currentChunk,
&$foundChunk
) {
$currentChunk++;
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
if (!empty($responseCode) && in_array($responseCode, [301, 302])) { if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
$isRedirected = true; $isRedirected = true;
@ -48,9 +73,34 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
} }
if (empty($title)) { if (empty($title)) {
$title = html_extract_title($data); $title = html_extract_title($data);
$foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
} }
if ($retrieveDescription && empty($description)) {
$description = html_extract_tag('description', $data);
$foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
}
if ($retrieveDescription && empty($keywords)) {
$keywords = html_extract_tag('keywords', $data);
if (! empty($keywords)) {
$foundChunk = $currentChunk;
// Keywords use the format tag1, tag2 multiple words, tag
// So we format them to match Shaarli's separator and glue multiple words with '-'
$keywords = implode(' ', array_map(function($keyword) {
return implode('-', preg_split('/\s+/', trim($keyword)));
}, explode(',', $keywords)));
}
}
// We got everything we want, stop the download. // We got everything we want, stop the download.
if (!empty($responseCode) && !empty($contentType) && !empty($charset) && !empty($title)) { // If we already found either the title, description or keywords,
// it's highly unlikely that we'll found the other metas further than
// in the same chunk of data or the next one. So we also stop the download after that.
if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
&& (! $retrieveDescription
|| $foundChunk < $currentChunk
|| (!empty($title) && !empty($description) && !empty($keywords))
)
) {
return false; return false;
} }
@ -108,6 +158,35 @@ function html_extract_charset($html)
return false; return false;
} }
/**
* Extract meta tag from HTML content in either:
* - OpenGraph: <meta property="og:[tag]" ...>
* - Meta tag: <meta name="[tag]" ...>
*
* @param string $tag Name of the tag to retrieve.
* @param string $html HTML content where to look for charset.
*
* @return bool|string Charset string if found, false otherwise.
*/
function html_extract_tag($tag, $html)
{
$propertiesKey = ['property', 'name', 'itemprop'];
$properties = implode('|', $propertiesKey);
// Try to retrieve OpenGraph image.
$ogRegex = '#<meta[^>]+(?:'. $properties .')=["\']?(?:og:)?'. $tag .'["\'\s][^>]*content=["\']?(.*?)["\'/>]#';
// If the attributes are not in the order property => content (e.g. Github)
// New regex to keep this readable... more or less.
$ogRegexReverse = '#<meta[^>]+content=["\']([^"\']+)[^>]+(?:'. $properties .')=["\']?(?:og)?:'. $tag .'["\'\s/>]#';
if (preg_match($ogRegex, $html, $matches) > 0
|| preg_match($ogRegexReverse, $html, $matches) > 0
) {
return $matches[1];
}
return false;
}
/** /**
* Count private links in given linklist. * Count private links in given linklist.
* *
@ -131,29 +210,15 @@ function count_private($links)
* In a string, converts URLs to clickable links. * In a string, converts URLs to clickable links.
* *
* @param string $text input string. * @param string $text input string.
* @param string $redirector if a redirector is set, use it to gerenate links.
* @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
* *
* @return string returns $text with all links converted to HTML links. * @return string returns $text with all links converted to HTML links.
* *
* @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
*/ */
function text2clickable($text, $redirector = '', $urlEncode = true) function text2clickable($text)
{ {
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si'; $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
return preg_replace($regex, '<a href="$1">$1</a>', $text);
if (empty($redirector)) {
return preg_replace($regex, '<a href="$1">$1</a>', $text);
}
// Redirector is set, urlencode the final URL.
return preg_replace_callback(
$regex,
function ($matches) use ($redirector, $urlEncode) {
$url = $urlEncode ? urlencode($matches[1]) : $matches[1];
return '<a href="' . $redirector . $url .'">'. $matches[1] .'</a>';
},
$text
);
} }
/** /**
@ -195,15 +260,13 @@ function space2nbsp($text)
* Format Shaarli's description * Format Shaarli's description
* *
* @param string $description shaare's description. * @param string $description shaare's description.
* @param string $redirector if a redirector is set, use it to gerenate links.
* @param bool $urlEncode Use `urlencode()` on the URL after the redirector or not.
* @param string $indexUrl URL to Shaarli's index. * @param string $indexUrl URL to Shaarli's index.
* @return string formatted description. * @return string formatted description.
*/ */
function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') function format_description($description, $indexUrl = '')
{ {
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl))); return nl2br(space2nbsp(hashtag_autolink(text2clickable($description), $indexUrl)));
} }
/** /**
@ -218,3 +281,16 @@ function link_small_hash($date, $id)
{ {
return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id); return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
} }
/**
* Returns whether or not the link is an internal note.
* Its URL starts by `?` because it's actually a permalink.
*
* @param string $linkUrl
*
* @return bool true if internal note, false otherwise.
*/
function is_note($linkUrl)
{
return isset($linkUrl[0]) && $linkUrl[0] === '?';
}

View file

@ -0,0 +1,15 @@
<?php
namespace Shaarli\Bookmark\Exception;
use Exception;
class LinkNotFoundException extends Exception
{
/**
* LinkNotFoundException constructor.
*/
public function __construct()
{
$this->message = t('The link you are trying to reach does not exist or has been deleted.');
}
}

View file

@ -47,7 +47,7 @@ public function write($filepath, $conf)
$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 \Shaarli\Exceptions\IOException(
$filepath, $filepath,
t('Shaarli could not create the config file. '. t('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.')

View file

@ -207,7 +207,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 \Shaarli\Exceptions\IOException: an error occurred while writing the new config file.
*/ */
public function write($isLoggedIn) public function write($isLoggedIn)
{ {
@ -221,7 +221,6 @@ public function write($isLoggedIn)
'general.title', 'general.title',
'general.header_link', 'general.header_link',
'privacy.default_private_links', 'privacy.default_private_links',
'redirector.url',
); );
// Only logged in user can alter config. // Only logged in user can alter config.
@ -366,6 +365,7 @@ protected function setDefaultValues()
$this->setEmpty('general.links_per_page', 20); $this->setEmpty('general.links_per_page', 20);
$this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
$this->setEmpty('general.default_note_title', 'Note: '); $this->setEmpty('general.default_note_title', 'Note: ');
$this->setEmpty('general.retrieve_description', false);
$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');
@ -381,9 +381,6 @@ protected function setDefaultValues()
// default state of the 'remember me' checkbox of the login form // default state of the 'remember me' checkbox of the login form
$this->setEmpty('privacy.remember_user_default', true); $this->setEmpty('privacy.remember_user_default', true);
$this->setEmpty('redirector.url', '');
$this->setEmpty('redirector.encode_url', true);
$this->setEmpty('thumbnails.width', '125'); $this->setEmpty('thumbnails.width', '125');
$this->setEmpty('thumbnails.height', '90'); $this->setEmpty('thumbnails.height', '90');

View file

@ -27,7 +27,7 @@ class ConfigPhp implements ConfigIO
/** /**
* Map legacy config keys with the new ones. * Map legacy config keys with the new ones.
* If ConfigPhp is used, getting <newkey> will actually look for <legacykey>. * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>.
* The Updater will use this array to transform keys when switching to JSON. * The updater will use this array to transform keys when switching to JSON.
* *
* @var array current key => legacy key. * @var array current key => legacy key.
*/ */
@ -124,7 +124,7 @@ public 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 \Shaarli\Exceptions\IOException(
$filepath, $filepath,
t('Shaarli could not create the config file. '. t('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.')

View file

@ -1,4 +1,7 @@
<?php <?php
namespace Shaarli\Exceptions;
use Exception;
/** /**
* Exception class thrown when a filesystem access failure happens * Exception class thrown when a filesystem access failure happens
@ -17,6 +20,6 @@ public function __construct($path, $message = '')
{ {
$this->path = $path; $this->path = $path;
$this->message = empty($message) ? t('Error accessing') : $message; $this->message = empty($message) ? t('Error accessing') : $message;
$this->message .= ' "' . $this->path .'"'; $this->message .= ' "' . $this->path . '"';
} }
} }

View file

@ -1,4 +1,7 @@
<?php <?php
namespace Shaarli\Feed;
/** /**
* Simple cache system, mainly for the RSS/ATOM feeds * Simple cache system, mainly for the RSS/ATOM feeds
*/ */
@ -24,7 +27,7 @@ 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->filename = $this->cacheDir.'/'.sha1($url).'.cache'; $this->filename = $this->cacheDir . '/' . sha1($url) . '.cache';
$this->shouldBeCached = $shouldBeCached; $this->shouldBeCached = $shouldBeCached;
} }

View file

@ -1,4 +1,7 @@
<?php <?php
namespace Shaarli\Feed;
use DateTime;
/** /**
* FeedBuilder class. * FeedBuilder class.
@ -28,7 +31,7 @@ class FeedBuilder
public static $DEFAULT_NB_LINKS = 50; public static $DEFAULT_NB_LINKS = 50;
/** /**
* @var LinkDB instance. * @var \Shaarli\Bookmark\LinkDB instance.
*/ */
protected $linkDB; protected $linkDB;
@ -38,12 +41,12 @@ class FeedBuilder
protected $feedType; protected $feedType;
/** /**
* @var array $_SERVER. * @var array $_SERVER
*/ */
protected $serverInfo; protected $serverInfo;
/** /**
* @var array $_GET. * @var array $_GET
*/ */
protected $userInput; protected $userInput;
@ -75,11 +78,12 @@ class FeedBuilder
/** /**
* Feed constructor. * Feed constructor.
* *
* @param LinkDB $linkDB LinkDB instance. * @param \Shaarli\Bookmark\LinkDB $linkDB LinkDB instance.
* @param string $feedType Type of feed. * @param string $feedType Type of feed.
* @param array $serverInfo $_SERVER. * @param array $serverInfo $_SERVER.
* @param array $userInput $_GET. * @param array $userInput $_GET.
* @param boolean $isLoggedIn True if the user is currently logged in, false otherwise. * @param boolean $isLoggedIn True if the user is currently logged in,
* false otherwise.
*/ */
public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn) public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn)
{ {
@ -124,7 +128,7 @@ public function buildData()
$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.
$data['self_link'] = escape(server_url($this->serverInfo)) $data['self_link'] = escape(server_url($this->serverInfo))
. escape($this->serverInfo['REQUEST_URI']); . escape($this->serverInfo['REQUEST_URI']);
$data['index_url'] = $pageaddr; $data['index_url'] = $pageaddr;
$data['usepermalinks'] = $this->usePermalinks === true; $data['usepermalinks'] = $this->usePermalinks === true;
$data['links'] = $linkDisplayed; $data['links'] = $linkDisplayed;
@ -142,18 +146,19 @@ public function buildData()
*/ */
protected function buildItem($link, $pageaddr) protected function buildItem($link, $pageaddr)
{ {
$link['guid'] = $pageaddr .'?'. $link['shorturl']; $link['guid'] = $pageaddr . '?' . $link['shorturl'];
// Check for both signs of a note: starting with ? and 7 chars long. // Prepend the root URL for notes
if ($link['url'][0] === '?' && strlen($link['url']) === 7) { if (is_note($link['url'])) {
$link['url'] = $pageaddr . $link['url']; $link['url'] = $pageaddr . $link['url'];
} }
if ($this->usePermalinks === true) { if ($this->usePermalinks === true) {
$permalink = '<a href="'. $link['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>'; $permalink = '<a href="' . $link['url'] . '" title="' . t('Direct link') . '">' . t('Direct link') . '</a>';
} else { } else {
$permalink = '<a href="'. $link['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>'; $permalink = '<a href="' . $link['guid'] . '" title="' . t('Permalink') . '">' . t('Permalink') . '</a>';
} }
$link['description'] = format_description($link['description'], '', false, $pageaddr); $link['description'] = format_description($link['description'], $pageaddr);
$link['permalink'] = PHP_EOL .'<br> &#8212; '. $permalink; $link['description'] .= PHP_EOL . '<br>&#8212; ' . $permalink;
$pubDate = $link['created']; $pubDate = $link['created'];
$link['pub_iso_date'] = $this->getIsoDate($pubDate); $link['pub_iso_date'] = $this->getIsoDate($pubDate);
@ -163,7 +168,6 @@ protected function buildItem($link, $pageaddr)
$link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM); $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
} else { } else {
$link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM); $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);
;
} }
// Save the more recent item. // Save the more recent item.
@ -222,11 +226,11 @@ public function setLocale($locale)
public function getTypeLanguage() public function getTypeLanguage()
{ {
// Use the locale do define the language, if available. // Use the locale do define the language, if available.
if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { if (!empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) {
$length = ($this->feedType == self::$FEED_RSS) ? 5 : 2; $length = ($this->feedType === self::$FEED_RSS) ? 5 : 2;
return str_replace('_', '-', substr($this->locale, 0, $length)); return str_replace('_', '-', substr($this->locale, 0, $length));
} }
return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en'; return ($this->feedType === self::$FEED_RSS) ? 'en-en' : 'en';
} }
/** /**
@ -286,7 +290,7 @@ public function getNbLinks($max)
} }
$intNb = intval($this->userInput['nb']); $intNb = intval($this->userInput['nb']);
if (! is_int($intNb) || $intNb == 0) { if (!is_int($intNb) || $intNb == 0) {
return self::$DEFAULT_NB_LINKS; return self::$DEFAULT_NB_LINKS;
} }

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Shaarli; namespace Shaarli\Http;
/** /**
* URL-safe Base64 operations * URL-safe Base64 operations

View file

@ -1,4 +1,7 @@
<?php <?php
use Shaarli\Http\Url;
/** /**
* GET an HTTP URL to retrieve its content * GET an HTTP URL to retrieve its content
* Uses the cURL library or a fallback method * Uses the cURL library or a fallback method
@ -38,7 +41,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
$cleanUrl = $urlObj->idnToAscii(); $cleanUrl = $urlObj->idnToAscii();
if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) { if (!filter_var($cleanUrl, FILTER_VALIDATE_URL) || !$urlObj->isHttp()) {
return array(array(0 => 'Invalid HTTP Url'), false); return array(array(0 => 'Invalid HTTP UrlUtils'), false);
} }
$userAgent = $userAgent =

View file

@ -1,91 +1,6 @@
<?php <?php
/**
* Converts an array-represented URL to a string
*
* Source: http://php.net/manual/en/function.parse-url.php#106731
*
* @see http://php.net/manual/en/function.parse-url.php
*
* @param array $parsedUrl an array-represented URL
*
* @return string the string representation of the URL
*/
function unparse_url($parsedUrl)
{
$scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'].'://' : '';
$host = isset($parsedUrl['host']) ? $parsedUrl['host'] : '';
$port = isset($parsedUrl['port']) ? ':'.$parsedUrl['port'] : '';
$user = isset($parsedUrl['user']) ? $parsedUrl['user'] : '';
$pass = isset($parsedUrl['pass']) ? ':'.$parsedUrl['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
$query = isset($parsedUrl['query']) ? '?'.$parsedUrl['query'] : '';
$fragment = isset($parsedUrl['fragment']) ? '#'.$parsedUrl['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment"; namespace Shaarli\Http;
}
/**
* Removes undesired query parameters and fragments
*
* @param string url Url to be cleaned
*
* @return string the string representation of this URL after cleanup
*/
function cleanup_url($url)
{
$obj_url = new Url($url);
return $obj_url->cleanup();
}
/**
* Get URL scheme.
*
* @param string url Url for which the scheme is requested
*
* @return mixed the URL scheme or false if none is provided.
*/
function get_url_scheme($url)
{
$obj_url = new Url($url);
return $obj_url->getScheme();
}
/**
* Adds a trailing slash at the end of URL if necessary.
*
* @param string $url URL to check/edit.
*
* @return string $url URL with a end trailing slash.
*/
function add_trailing_slash($url)
{
return $url . (!endsWith($url, '/') ? '/' : '');
}
/**
* Replace not whitelisted protocols by 'http://' from given URL.
*
* @param string $url URL to clean
* @param array $protocols List of allowed protocols (aside from http(s)).
*
* @return string URL with allowed protocol
*/
function whitelist_protocols($url, $protocols)
{
if (startsWith($url, '?') || startsWith($url, '/')) {
return $url;
}
$protocols = array_merge(['http', 'https'], $protocols);
$protocol = preg_match('#^(\w+):/?/?#', $url, $match);
// Protocol not allowed: we remove it and replace it with http
if ($protocol === 1 && ! in_array($match[1], $protocols)) {
$url = str_replace($match[0], 'http://', $url);
} elseif ($protocol !== 1) {
$url = 'http://' . $url;
}
return $url;
}
/** /**
* URL representation and cleanup utilities * URL representation and cleanup utilities
@ -182,7 +97,7 @@ protected static function removeFirefoxAboutReader($input)
} }
return $input; return $input;
} }
/** /**
* Returns a string representation of this URL * Returns a string representation of this URL
*/ */
@ -196,7 +111,7 @@ public function toString()
*/ */
protected function cleanupQuery() protected function cleanupQuery()
{ {
if (! isset($this->parts['query'])) { if (!isset($this->parts['query'])) {
return; return;
} }
@ -224,7 +139,7 @@ protected function cleanupQuery()
*/ */
protected function cleanupFragment() protected function cleanupFragment()
{ {
if (! isset($this->parts['fragment'])) { if (!isset($this->parts['fragment'])) {
return; return;
} }
@ -257,7 +172,7 @@ public function cleanup()
public function idnToAscii() public function idnToAscii()
{ {
$out = $this->cleanup(); $out = $this->cleanup();
if (! function_exists('idn_to_ascii') || ! isset($this->parts['host'])) { if (!function_exists('idn_to_ascii') || !isset($this->parts['host'])) {
return $out; return $out;
} }
$asciiHost = idn_to_ascii($this->parts['host'], 0, INTL_IDNA_VARIANT_UTS46); $asciiHost = idn_to_ascii($this->parts['host'], 0, INTL_IDNA_VARIANT_UTS46);
@ -291,7 +206,7 @@ public function getHost()
} }
/** /**
* Test if the Url is an HTTP one. * Test if the UrlUtils is an HTTP one.
* *
* @return true is HTTP, false otherwise. * @return true is HTTP, false otherwise.
*/ */

View file

@ -0,0 +1,88 @@
<?php
/**
* Converts an array-represented URL to a string
*
* Source: http://php.net/manual/en/function.parse-url.php#106731
*
* @see http://php.net/manual/en/function.parse-url.php
*
* @param array $parsedUrl an array-represented URL
*
* @return string the string representation of the URL
*/
function unparse_url($parsedUrl)
{
$scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'].'://' : '';
$host = isset($parsedUrl['host']) ? $parsedUrl['host'] : '';
$port = isset($parsedUrl['port']) ? ':'.$parsedUrl['port'] : '';
$user = isset($parsedUrl['user']) ? $parsedUrl['user'] : '';
$pass = isset($parsedUrl['pass']) ? ':'.$parsedUrl['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
$query = isset($parsedUrl['query']) ? '?'.$parsedUrl['query'] : '';
$fragment = isset($parsedUrl['fragment']) ? '#'.$parsedUrl['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
/**
* Removes undesired query parameters and fragments
*
* @param string url UrlUtils to be cleaned
*
* @return string the string representation of this URL after cleanup
*/
function cleanup_url($url)
{
$obj_url = new \Shaarli\Http\Url($url);
return $obj_url->cleanup();
}
/**
* Get URL scheme.
*
* @param string url UrlUtils for which the scheme is requested
*
* @return mixed the URL scheme or false if none is provided.
*/
function get_url_scheme($url)
{
$obj_url = new \Shaarli\Http\Url($url);
return $obj_url->getScheme();
}
/**
* Adds a trailing slash at the end of URL if necessary.
*
* @param string $url URL to check/edit.
*
* @return string $url URL with a end trailing slash.
*/
function add_trailing_slash($url)
{
return $url . (!endsWith($url, '/') ? '/' : '');
}
/**
* Replace not whitelisted protocols by 'http://' from given URL.
*
* @param string $url URL to clean
* @param array $protocols List of allowed protocols (aside from http(s)).
*
* @return string URL with allowed protocol
*/
function whitelist_protocols($url, $protocols)
{
if (startsWith($url, '?') || startsWith($url, '/')) {
return $url;
}
$protocols = array_merge(['http', 'https'], $protocols);
$protocol = preg_match('#^(\w+):/?/?#', $url, $match);
// Protocol not allowed: we remove it and replace it with http
if ($protocol === 1 && ! in_array($match[1], $protocols)) {
$url = str_replace($match[0], 'http://', $url);
} elseif ($protocol !== 1) {
$url = 'http://' . $url;
}
return $url;
}

View file

@ -1,9 +1,16 @@
<?php <?php
use Psr\Log\LogLevel; namespace Shaarli\Netscape;
use Shaarli\Config\ConfigManager;
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser; use DateTime;
use DateTimeZone;
use Exception;
use Katzgrau\KLogger\Logger; use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
/** /**
* Utilities to import and export bookmarks using the Netscape format * Utilities to import and export bookmarks using the Netscape format
@ -31,8 +38,8 @@ class NetscapeBookmarkUtils
public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl) public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl)
{ {
// see tpl/export.html for possible values // see tpl/export.html for possible values
if (! in_array($selection, array('all', 'public', 'private'))) { if (!in_array($selection, array('all', 'public', 'private'))) {
throw new Exception(t('Invalid export selection:') .' "'.$selection.'"'); throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
} }
$bookmarkLinks = array(); $bookmarkLinks = array();
@ -47,7 +54,7 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
$link['timestamp'] = $date->getTimestamp(); $link['timestamp'] = $date->getTimestamp();
$link['taglist'] = str_replace(' ', ',', $link['tags']); $link['taglist'] = str_replace(' ', ',', $link['tags']);
if (startsWith($link['url'], '?') && $prependNoteUrl) { if (is_note($link['url']) && $prependNoteUrl) {
$link['url'] = $indexUrl . $link['url']; $link['url'] = $indexUrl . $link['url'];
} }
@ -84,7 +91,7 @@ private static function importStatus(
$status .= vsprintf( $status .= vsprintf(
t( t(
'was successfully processed in %d seconds: ' 'was successfully processed in %d seconds: '
.'%d links imported, %d links overwritten, %d links skipped.' . '%d links imported, %d links overwritten, %d links skipped.'
), ),
[$duration, $importCount, $overwriteCount, $skipCount] [$duration, $importCount, $overwriteCount, $skipCount]
); );
@ -95,11 +102,11 @@ 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 ConfigManager $conf instance * @param ConfigManager $conf instance
* @param History $history History instance * @param History $history History instance
* *
* @return string Summary of the bookmark import status * @return string Summary of the bookmark import status
*/ */
@ -115,7 +122,7 @@ public static function import($post, $files, $linkDb, $conf, $history)
} }
// Overwrite existing links? // Overwrite existing links?
$overwrite = ! empty($post['overwrite']); $overwrite = !empty($post['overwrite']);
// Add tags to all imported links? // Add tags to all imported links?
if (empty($post['default_tags'])) { if (empty($post['default_tags'])) {
@ -138,7 +145,7 @@ public static function import($post, $files, $linkDb, $conf, $history)
); );
$logger = new Logger( $logger = new Logger(
$conf->get('resource.data_dir'), $conf->get('resource.data_dir'),
! $conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG, !$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
[ [
'prefix' => 'import.', 'prefix' => 'import.',
'extension' => 'log', 'extension' => 'log',
@ -193,7 +200,7 @@ public static function import($post, $files, $linkDb, $conf, $history)
} }
// Add a new link - @ used for UNIX timestamps // Add a new link - @ used for UNIX timestamps
$newLinkDate = new DateTime('@'.strval($bkm['time'])); $newLinkDate = new DateTime('@' . strval($bkm['time']));
$newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get())); $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
$newLink['created'] = $newLinkDate; $newLink['created'] = $newLinkDate;
$newLink['id'] = $linkDb->getNextId(); $newLink['id'] = $linkDb->getNextId();

View file

@ -1,4 +1,8 @@
<?php <?php
namespace Shaarli\Plugin;
use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\Exception\PluginFileNotFoundException;
/** /**
* Class PluginManager * Class PluginManager
@ -9,12 +13,14 @@ class PluginManager
{ {
/** /**
* List of authorized plugins from configuration file. * List of authorized plugins from configuration file.
*
* @var array $authorizedPlugins * @var array $authorizedPlugins
*/ */
private $authorizedPlugins; private $authorizedPlugins;
/** /**
* List of loaded plugins. * List of loaded plugins.
*
* @var array $loadedPlugins * @var array $loadedPlugins
*/ */
private $loadedPlugins = array(); private $loadedPlugins = array();
@ -31,12 +37,14 @@ class PluginManager
/** /**
* Plugins subdirectory. * Plugins subdirectory.
*
* @var string $PLUGINS_PATH * @var string $PLUGINS_PATH
*/ */
public static $PLUGINS_PATH = 'plugins'; public static $PLUGINS_PATH = 'plugins';
/** /**
* Plugins meta files extension. * Plugins meta files extension.
*
* @var string $META_EXT * @var string $META_EXT
*/ */
public static $META_EXT = 'meta'; public static $META_EXT = 'meta';
@ -84,9 +92,9 @@ public function load($authorizedPlugins)
/** /**
* Execute all plugins registered hook. * Execute all plugins registered hook.
* *
* @param string $hook name of the hook to trigger. * @param string $hook name of the hook to trigger.
* @param array $data list of data to manipulate passed by reference. * @param array $data list of data to manipulate passed by reference.
* @param array $params additional parameters such as page target. * @param array $params additional parameters such as page target.
* *
* @return void * @return void
*/ */
@ -118,7 +126,7 @@ public function executeHooks($hook, &$data, $params = array())
* @param string $pluginName plugin's name. * @param string $pluginName plugin's name.
* *
* @return void * @return void
* @throws PluginFileNotFoundException - plugin files not found. * @throws \Shaarli\Plugin\Exception\PluginFileNotFoundException - plugin files not found.
*/ */
private function loadPlugin($dir, $pluginName) private function loadPlugin($dir, $pluginName)
{ {
@ -204,8 +212,8 @@ public function getPluginsMeta()
$metaData[$plugin]['parameters'][$param]['value'] = ''; $metaData[$plugin]['parameters'][$param]['value'] = '';
// Optional parameter description in parameter.PARAM_NAME= // Optional parameter description in parameter.PARAM_NAME=
if (isset($metaData[$plugin]['parameter.'. $param])) { if (isset($metaData[$plugin]['parameter.' . $param])) {
$metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.'. $param]); $metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.' . $param]);
} }
} }
} }
@ -223,22 +231,3 @@ public function getErrors()
return $this->errors; return $this->errors;
} }
} }
/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginFileNotFoundException extends Exception
{
/**
* Construct exception with plugin name.
* Generate message.
*
* @param string $pluginName name of the plugin not found
*/
public function __construct($pluginName)
{
$this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Shaarli\Plugin\Exception;
use Exception;
/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginFileNotFoundException extends Exception
{
/**
* Construct exception with plugin name.
* Generate message.
*
* @param string $pluginName name of the plugin not found
*/
public function __construct($pluginName)
{
$this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
}
}

View file

@ -1,5 +1,11 @@
<?php <?php
namespace Shaarli\Render;
use Exception;
use RainTPL;
use Shaarli\ApplicationUtils;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
@ -37,7 +43,9 @@ class PageBuilder
*/ */
protected $token; protected $token;
/** @var bool $isLoggedIn Whether the user is logged in **/ /**
* @var bool $isLoggedIn Whether the user is logged in
*/
protected $isLoggedIn = false; protected $isLoggedIn = false;
/** /**
@ -101,7 +109,7 @@ private function initialize()
ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt')) ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
); );
$this->tpl->assign('index_url', index_url($_SERVER)); $this->tpl->assign('index_url', index_url($_SERVER));
$visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; $visibility = !empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
$this->tpl->assign('visibility', $visibility); $this->tpl->assign('visibility', $visibility);
$this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly'])); $this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
$this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli')); $this->tpl->assign('pagetitle', $this->conf->get('general.title', 'Shaarli'));
@ -115,6 +123,8 @@ private function initialize()
$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', $this->token); $this->tpl->assign('token', $this->token);
$this->tpl->assign('language', $this->conf->get('translation.language'));
if ($this->linkDB !== null) { if ($this->linkDB !== null) {
$this->tpl->assign('tags', $this->linkDB->linksCountPerTag()); $this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
} }
@ -126,7 +136,7 @@ private function initialize()
$this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width')); $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
$this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height')); $this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height'));
if (! empty($_SESSION['warnings'])) { if (!empty($_SESSION['warnings'])) {
$this->tpl->assign('global_warnings', $_SESSION['warnings']); $this->tpl->assign('global_warnings', $_SESSION['warnings']);
unset($_SESSION['warnings']); unset($_SESSION['warnings']);
} }
@ -189,16 +199,16 @@ public function renderPage($page)
/** /**
* Render a 404 page (uses the template : tpl/404.tpl) * Render a 404 page (uses the template : tpl/404.tpl)
* usage : $PAGE->render404('The link was deleted') * usage: $PAGE->render404('The link was deleted')
* *
* @param string $message A messate to display what is not found * @param string $message A message to display what is not found
*/ */
public function render404($message = '') public function render404($message = '')
{ {
if (empty($message)) { if (empty($message)) {
$message = t('The page you are trying to reach does not exist or has been deleted.'); $message = t('The page you are trying to reach does not exist or has been deleted.');
} }
header($_SERVER['SERVER_PROTOCOL'] .' '. t('404 Not Found')); header($_SERVER['SERVER_PROTOCOL'] . ' ' . t('404 Not Found'));
$this->tpl->assign('error_message', $message); $this->tpl->assign('error_message', $message);
$this->renderPage('404'); $this->renderPage('404');
} }

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Shaarli; namespace Shaarli\Render;
/** /**
* Class ThemeUtils * Class ThemeUtils

View file

@ -0,0 +1,213 @@
<?php
namespace Shaarli\Security;
use Shaarli\FileUtils;
/**
* Class BanManager
*
* Failed login attempts will store the associated IP address.
* After N failed attempts, the IP will be prevented from log in for duration D.
* Both N and D can be set in the configuration file.
*
* @package Shaarli\Security
*/
class BanManager
{
/** @var array List of allowed proxies IP */
protected $trustedProxies;
/** @var int Number of allowed failed attempt before the ban */
protected $nbAttempts;
/** @var int Ban duration in seconds */
protected $banDuration;
/** @var string Path to the file containing IP bans and failures */
protected $banFile;
/** @var string Path to the log file, used to log bans */
protected $logFile;
/** @var array List of IP with their associated number of failed attempts */
protected $failures = [];
/** @var array List of banned IP with their associated unban timestamp */
protected $bans = [];
/**
* BanManager constructor.
*
* @param array $trustedProxies List of allowed proxies IP
* @param int $nbAttempts Number of allowed failed attempt before the ban
* @param int $banDuration Ban duration in seconds
* @param string $banFile Path to the file containing IP bans and failures
* @param string $logFile Path to the log file, used to log bans
*/
public function __construct($trustedProxies, $nbAttempts, $banDuration, $banFile, $logFile) {
$this->trustedProxies = $trustedProxies;
$this->nbAttempts = $nbAttempts;
$this->banDuration = $banDuration;
$this->banFile = $banFile;
$this->logFile = $logFile;
$this->readBanFile();
}
/**
* Handle a failed login and ban the IP after too many failed attempts
*
* @param array $server The $_SERVER array
*/
public function handleFailedAttempt($server)
{
$ip = $this->getIp($server);
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we do nothing
if (empty($ip)) {
return;
}
// increment the fail count for this IP
if (isset($this->failures[$ip])) {
$this->failures[$ip]++;
} else {
$this->failures[$ip] = 1;
}
if ($this->failures[$ip] >= $this->nbAttempts) {
$this->bans[$ip] = time() + $this->banDuration;
logm(
$this->logFile,
$server['REMOTE_ADDR'],
'IP address banned from login: '. $ip
);
}
$this->writeBanFile();
}
/**
* Remove failed attempts for the provided client.
*
* @param array $server $_SERVER
*/
public function clearFailures($server)
{
$ip = $this->getIp($server);
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we do nothing
if (empty($ip)) {
return;
}
if (isset($this->failures[$ip])) {
unset($this->failures[$ip]);
}
$this->writeBanFile();
}
/**
* Check whether the client IP is banned or not.
*
* @param array $server $_SERVER
*
* @return bool True if the IP is banned, false otherwise
*/
public function isBanned($server)
{
$ip = $this->getIp($server);
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we allow the authentication attempt.
if (empty($ip)) {
return false;
}
// the user is not banned
if (! isset($this->bans[$ip])) {
return false;
}
// the user is still banned
if ($this->bans[$ip] > time()) {
return true;
}
// the ban has expired, the user can attempt to log in again
if (isset($this->failures[$ip])) {
unset($this->failures[$ip]);
}
unset($this->bans[$ip]);
logm($this->logFile, $server['REMOTE_ADDR'], 'Ban lifted for: '. $ip);
$this->writeBanFile();
return false;
}
/**
* Retrieve the IP from $_SERVER.
* If the actual IP is behind an allowed reverse proxy,
* we try to extract the forwarded IP from HTTP headers.
*
* @param array $server $_SERVER
*
* @return string|bool The IP or false if none could be extracted
*/
protected function getIp($server)
{
$ip = $server['REMOTE_ADDR'];
if (! in_array($ip, $this->trustedProxies)) {
return $ip;
}
return getIpAddressFromProxy($server, $this->trustedProxies);
}
/**
* Read a file containing banned IPs
*/
protected function readBanFile()
{
$data = FileUtils::readFlatDB($this->banFile);
if (isset($data['failures']) && is_array($data['failures'])) {
$this->failures = $data['failures'];
}
if (isset($data['bans']) && is_array($data['bans'])) {
$this->bans = $data['bans'];
}
}
/**
* Write the banned IPs to a file
*/
protected function writeBanFile()
{
return FileUtils::writeFlatDB(
$this->banFile,
[
'failures' => $this->failures,
'bans' => $this->bans,
]
);
}
/**
* Get the Failures (for UT purpose).
*
* @return array
*/
public function getFailures()
{
return $this->failures;
}
/**
* Get the Bans (for UT purpose).
*
* @return array
*/
public function getBans()
{
return $this->bans;
}
}

View file

@ -20,8 +20,8 @@ class LoginManager
/** @var SessionManager Session Manager instance **/ /** @var SessionManager Session Manager instance **/
protected $sessionManager = null; protected $sessionManager = null;
/** @var string Path to the file containing IP bans */ /** @var BanManager Ban Manager instance **/
protected $banFile = ''; protected $banManager;
/** @var bool Whether the user is logged in **/ /** @var bool Whether the user is logged in **/
protected $isLoggedIn = false; protected $isLoggedIn = false;
@ -35,17 +35,21 @@ class LoginManager
/** /**
* Constructor * Constructor
* *
* @param array $globals The $GLOBALS array (reference)
* @param ConfigManager $configManager Configuration Manager instance * @param ConfigManager $configManager Configuration Manager instance
* @param SessionManager $sessionManager SessionManager instance * @param SessionManager $sessionManager SessionManager instance
*/ */
public function __construct(& $globals, $configManager, $sessionManager) public function __construct($configManager, $sessionManager)
{ {
$this->globals = &$globals;
$this->configManager = $configManager; $this->configManager = $configManager;
$this->sessionManager = $sessionManager; $this->sessionManager = $sessionManager;
$this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php'); $this->banManager = new BanManager(
$this->readBanFile(); $this->configManager->get('security.trusted_proxies', []),
$this->configManager->get('security.ban_after'),
$this->configManager->get('security.ban_duration'),
$this->configManager->get('resource.ban_file', 'data/ipbans.php'),
$this->configManager->get('resource.log')
);
if ($this->configManager->get('security.open_shaarli') === true) { if ($this->configManager->get('security.open_shaarli') === true) {
$this->openShaarli = true; $this->openShaarli = true;
} }
@ -58,6 +62,9 @@ public function __construct(& $globals, $configManager, $sessionManager)
*/ */
public function generateStaySignedInToken($clientIpAddress) public function generateStaySignedInToken($clientIpAddress)
{ {
if ($this->configManager->get('security.session_protection_disabled') === true) {
$clientIpAddress = '';
}
$this->staySignedInToken = sha1( $this->staySignedInToken = sha1(
$this->configManager->get('credentials.hash') $this->configManager->get('credentials.hash')
. $clientIpAddress . $clientIpAddress
@ -154,31 +161,6 @@ public function checkCredentials($remoteIp, $clientIpId, $login, $password)
return true; return true;
} }
/**
* Read a file containing banned IPs
*/
protected function readBanFile()
{
if (! file_exists($this->banFile)) {
return;
}
include $this->banFile;
}
/**
* Write the banned IPs to a file
*/
protected function writeBanFile()
{
if (! array_key_exists('IPBANS', $this->globals)) {
return;
}
file_put_contents(
$this->banFile,
"<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
);
}
/** /**
* Handle a failed login and ban the IP after too many failed attempts * Handle a failed login and ban the IP after too many failed attempts
* *
@ -186,34 +168,7 @@ protected function writeBanFile()
*/ */
public function handleFailedLogin($server) public function handleFailedLogin($server)
{ {
$ip = $server['REMOTE_ADDR']; $this->banManager->handleFailedAttempt($server);
$trusted = $this->configManager->get('security.trusted_proxies', []);
if (in_array($ip, $trusted)) {
$ip = getIpAddressFromProxy($server, $trusted);
if (! $ip) {
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we do nothing
return;
}
}
// increment the fail count for this IP
if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
$this->globals['IPBANS']['FAILURES'][$ip]++;
} else {
$this->globals['IPBANS']['FAILURES'][$ip] = 1;
}
if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
$this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
logm(
$this->configManager->get('resource.log'),
$server['REMOTE_ADDR'],
'IP address banned from login'
);
}
$this->writeBanFile();
} }
/** /**
@ -223,13 +178,7 @@ public function handleFailedLogin($server)
*/ */
public function handleSuccessfulLogin($server) public function handleSuccessfulLogin($server)
{ {
$ip = $server['REMOTE_ADDR']; $this->banManager->clearFailures($server);
// FIXME unban when behind a trusted proxy?
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
} }
/** /**
@ -241,24 +190,6 @@ public function handleSuccessfulLogin($server)
*/ */
public function canLogin($server) public function canLogin($server)
{ {
$ip = $server['REMOTE_ADDR']; return ! $this->banManager->isBanned($server);
if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
// the user is not banned
return true;
}
if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
// the user is still banned
return false;
}
// the ban has expired, the user can attempt to log in again
logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
return true;
} }
} }

View file

@ -1,11 +1,24 @@
<?php <?php
namespace Shaarli\Updater;
use Exception;
use RainTPL;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use Shaarli\ApplicationUtils;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Bookmark\LinkFilter;
use Shaarli\Config\ConfigJson; use Shaarli\Config\ConfigJson;
use Shaarli\Config\ConfigPhp;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Config\ConfigPhp;
use Shaarli\Exceptions\IOException;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
use Shaarli\Updater\Exception\UpdaterException;
/** /**
* Class Updater. * Class updater.
* Used to update stuff when a new Shaarli's version is reached. * Used to update stuff when a new Shaarli's version is reached.
* Update methods are ran only once, and the stored in a JSON file. * Update methods are ran only once, and the stored in a JSON file.
*/ */
@ -83,12 +96,12 @@ public function update()
} }
if ($this->methods === null) { if ($this->methods === null) {
throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.')); throw new UpdaterException(t('Couldn\'t retrieve updater class methods.'));
} }
foreach ($this->methods as $method) { foreach ($this->methods as $method) {
// Not an update method or already done, pass. // Not an update method or already done, pass.
if (! startsWith($method->getName(), 'updateMethod') if (!startsWith($method->getName(), 'updateMethod')
|| in_array($method->getName(), $this->doneUpdates) || in_array($method->getName(), $this->doneUpdates)
) { ) {
continue; continue;
@ -139,7 +152,7 @@ public function updateMethodMergeDeprecatedConfigFile()
} }
} }
$this->conf->write($this->isLoggedIn); $this->conf->write($this->isLoggedIn);
unlink($this->conf->get('resource.data_dir').'/options.php'); unlink($this->conf->get('resource.data_dir') . '/options.php');
} }
return true; return true;
@ -174,10 +187,10 @@ public function updateMethodConfigToJson()
$subConfig = array('config', 'plugins'); $subConfig = array('config', 'plugins');
foreach ($subConfig as $sub) { foreach ($subConfig as $sub) {
foreach ($oldConfig[$sub] as $key => $value) { foreach ($oldConfig[$sub] as $key => $value) {
if (isset($legacyMap[$sub .'.'. $key])) { if (isset($legacyMap[$sub . '.' . $key])) {
$configKey = $legacyMap[$sub .'.'. $key]; $configKey = $legacyMap[$sub . '.' . $key];
} else { } else {
$configKey = $sub .'.'. $key; $configKey = $sub . '.' . $key;
} }
$this->conf->set($configKey, $value); $this->conf->set($configKey, $value);
} }
@ -205,7 +218,6 @@ public function updateMethodEscapeUnescapedConfig()
try { try {
$this->conf->set('general.title', escape($this->conf->get('general.title'))); $this->conf->set('general.title', escape($this->conf->get('general.title')));
$this->conf->set('general.header_link', escape($this->conf->get('general.header_link'))); $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
$this->conf->set('redirector.url', escape($this->conf->get('redirector.url')));
$this->conf->write($this->isLoggedIn); $this->conf->write($this->isLoggedIn);
} catch (Exception $e) { } catch (Exception $e) {
error_log($e->getMessage()); error_log($e->getMessage());
@ -233,7 +245,7 @@ public function updateMethodDatastoreIds()
return true; return true;
} }
$save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php'; $save = $this->conf->get('resource.data_dir') . '/datastore.' . date('YmdHis') . '.php';
copy($this->conf->get('resource.datastore'), $save); copy($this->conf->get('resource.datastore'), $save);
$links = array(); $links = array();
@ -307,7 +319,7 @@ public function updateMethodDefaultTheme()
// We run the update only if this folder still contains the template files. // We run the update only if this folder still contains the template files.
$tplDir = $this->conf->get('resource.raintpl_tpl'); $tplDir = $this->conf->get('resource.raintpl_tpl');
$tplFile = $tplDir . '/linklist.html'; $tplFile = $tplDir . '/linklist.html';
if (! file_exists($tplFile)) { if (!file_exists($tplFile)) {
return true; return true;
} }
@ -331,7 +343,7 @@ public function updateMethodDefaultTheme()
*/ */
public function updateMethodMoveUserCss() public function updateMethodMoveUserCss()
{ {
if (! is_file('inc/user.css')) { if (!is_file('inc/user.css')) {
return true; return true;
} }
@ -367,11 +379,11 @@ public function updateMethodEscapeMarkdown()
*/ */
public function updateMethodPiwikUrl() public function updateMethodPiwikUrl()
{ {
if (! $this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) { if (!$this->conf->exists('plugins.PIWIK_URL') || startsWith($this->conf->get('plugins.PIWIK_URL'), 'http')) {
return true; return true;
} }
$this->conf->set('plugins.PIWIK_URL', 'http://'. $this->conf->get('plugins.PIWIK_URL')); $this->conf->set('plugins.PIWIK_URL', 'http://' . $this->conf->get('plugins.PIWIK_URL'));
$this->conf->write($this->isLoggedIn); $this->conf->write($this->isLoggedIn);
return true; return true;
@ -481,11 +493,11 @@ public function updateMethodDownloadSizeAndTimeoutConf()
return true; return true;
} }
if (! $this->conf->exists('general.download_max_size')) { if (!$this->conf->exists('general.download_max_size')) {
$this->conf->set('general.download_max_size', 1024*1024*4); $this->conf->set('general.download_max_size', 1024 * 1024 * 4);
} }
if (! $this->conf->exists('general.download_timeout')) { if (!$this->conf->exists('general.download_timeout')) {
$this->conf->set('general.download_timeout', 30); $this->conf->set('general.download_timeout', 30);
} }
@ -537,97 +549,14 @@ public function updateMethodSetSticky()
return true; return true;
} }
}
/**
* Class UpdaterException.
*/
class UpdaterException extends Exception
{
/**
* @var string Method where the error occurred.
*/
protected $method;
/** /**
* @var Exception The parent exception. * Remove redirector settings.
*/ */
protected $previous; public function updateMethodRemoveRedirector()
/**
* Constructor.
*
* @param string $message Force the error message if set.
* @param string $method Method where the error occurred.
* @param Exception|bool $previous Parent exception.
*/
public function __construct($message = '', $method = '', $previous = false)
{ {
$this->method = $method; $this->conf->remove('redirector');
$this->previous = $previous; $this->conf->write(true);
$this->message = $this->buildMessage($message); return true;
}
/**
* Build the exception error message.
*
* @param string $message Optional given error message.
*
* @return string The built error message.
*/
private function buildMessage($message)
{
$out = '';
if (! empty($message)) {
$out .= $message . PHP_EOL;
}
if (! empty($this->method)) {
$out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
}
if (! empty($this->previous)) {
$out .= ' '. $this->previous->getMessage();
}
return $out;
}
}
/**
* Read the updates file, and return already done updates.
*
* @param string $updatesFilepath Updates file path.
*
* @return array Already done update methods.
*/
function read_updates_file($updatesFilepath)
{
if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
$content = file_get_contents($updatesFilepath);
if (! empty($content)) {
return explode(';', $content);
}
}
return array();
}
/**
* Write updates file.
*
* @param string $updatesFilepath Updates file path.
* @param array $updates Updates array to write.
*
* @throws Exception Couldn't write version number.
*/
function write_updates_file($updatesFilepath, $updates)
{
if (empty($updatesFilepath)) {
throw new Exception(t('Updates file path is not set, can\'t write updates.'));
}
$res = file_put_contents($updatesFilepath, implode(';', $updates));
if ($res === false) {
throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
} }
} }

View file

@ -0,0 +1,39 @@
<?php
/**
* Read the updates file, and return already done updates.
*
* @param string $updatesFilepath Updates file path.
*
* @return array Already done update methods.
*/
function read_updates_file($updatesFilepath)
{
if (! empty($updatesFilepath) && is_file($updatesFilepath)) {
$content = file_get_contents($updatesFilepath);
if (! empty($content)) {
return explode(';', $content);
}
}
return array();
}
/**
* Write updates file.
*
* @param string $updatesFilepath Updates file path.
* @param array $updates Updates array to write.
*
* @throws Exception Couldn't write version number.
*/
function write_updates_file($updatesFilepath, $updates)
{
if (empty($updatesFilepath)) {
throw new Exception(t('Updates file path is not set, can\'t write updates.'));
}
$res = file_put_contents($updatesFilepath, implode(';', $updates));
if ($res === false) {
throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Shaarli\Updater\Exception;
use Exception;
/**
* Class UpdaterException.
*/
class UpdaterException extends Exception
{
/**
* @var string Method where the error occurred.
*/
protected $method;
/**
* @var Exception The parent exception.
*/
protected $previous;
/**
* Constructor.
*
* @param string $message Force the error message if set.
* @param string $method Method where the error occurred.
* @param Exception|bool $previous Parent exception.
*/
public function __construct($message = '', $method = '', $previous = false)
{
$this->method = $method;
$this->previous = $previous;
$this->message = $this->buildMessage($message);
}
/**
* Build the exception error message.
*
* @param string $message Optional given error message.
*
* @return string The built error message.
*/
private function buildMessage($message)
{
$out = '';
if (!empty($message)) {
$out .= $message . PHP_EOL;
}
if (!empty($this->method)) {
$out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
}
if (!empty($this->previous)) {
$out .= ' ' . $this->previous->getMessage();
}
return $out;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

View file

@ -466,6 +466,28 @@ function init(description) {
}); });
} }
const changeVisibilityButtons = document.querySelectorAll('.actions-change-visibility');
if (changeVisibilityButtons != null && token != null) {
[...changeVisibilityButtons].forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
const visibility = event.target.getAttribute('data-visibility');
const links = [];
const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
[...linkCheckedCheckboxes].forEach((checkbox) => {
links.push({
id: checkbox.value,
title: document.querySelector(`.linklist-item[data-id="${checkbox.value}"] .linklist-link`).innerHTML,
});
});
const ids = links.map(item => item.id);
window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`;
});
});
}
/** /**
* Select all button * Select all button
*/ */

View file

@ -1,6 +1,6 @@
$fa-font-path: '~font-awesome/fonts'; $fa-font-path: '~fork-awesome/fonts';
@import '~font-awesome/scss/font-awesome'; @import '~fork-awesome/scss/fork-awesome';
@import '~purecss/build/pure.css'; @import '~purecss/build/pure.css';
@import '~purecss/build/grids-responsive.css'; @import '~purecss/build/grids-responsive.css';
@import '~pure-extras/css/pure-extras.css'; @import '~pure-extras/css/pure-extras.css';
@ -13,7 +13,7 @@ $dark-grey: #252525;
$light-grey: #797979; $light-grey: #797979;
$main-green: #1b926c; $main-green: #1b926c;
$light-green: #b0ddce; $light-green: #b0ddce;
$dark-green: #2a4c41; $dark-green: #186446;
$red: #ac2925; $red: #ac2925;
$orange: #f89406; $orange: #f89406;
$blue: #0b5ea6; $blue: #0b5ea6;
@ -25,9 +25,15 @@ $warning-text: #97600d;
$form-input-border: #d8d8d8; $form-input-border: #d8d8d8;
$form-input-background: #eee; $form-input-background: #eee;
:root {
--main-color: #{$main-green};
--background-color: #{$background-color};
--dark-main-color: #{$dark-green};
}
// General // General
body { body {
background: $background-color; background: var(--background-color);
} }
.strong { .strong {
@ -143,7 +149,7 @@ body,
} }
.pure-alert-success { .pure-alert-success {
background-color: $main-green; background-color: var(--main-color);
} }
.pure-alert-warning { .pure-alert-warning {
@ -169,7 +175,7 @@ body,
top: 0; top: 0;
transition: max-height .5s; transition: max-height .5s;
z-index: 999; z-index: 999;
background: $main-green; background: var(--main-color);
width: 100%; width: 100%;
// Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919 // Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919
max-height: 45px; max-height: 45px;
@ -322,7 +328,7 @@ body,
button { button {
border: 0; border: 0;
border-radius: 2px; border-radius: 2px;
background-color: $main-green; background-color: var(--main-color);
padding: 4px 8px 6px; padding: 4px 8px 6px;
color: $almost-white; color: $almost-white;
} }
@ -358,7 +364,7 @@ body,
.search-tagcloud { .search-tagcloud {
button { button {
&:hover { &:hover {
color: $background-color; color: var(--background-color);
} }
} }
} }
@ -389,7 +395,7 @@ body,
position: fixed; position: fixed;
visibility: hidden; visibility: hidden;
z-index: 999; z-index: 999;
background: $main-green; background: var(--main-color);
padding: 5px 0; padding: 5px 0;
width: 100%; width: 100%;
height: 30px; height: 30px;
@ -411,7 +417,7 @@ body,
margin: 0 0 5px; margin: 0 0 5px;
border: 1px solid $almost-white; border: 1px solid $almost-white;
border-radius: 2px; border-radius: 2px;
background: $main-green; background: var(--main-color);
padding: 4px 0; padding: 4px 0;
width: 100px; width: 100px;
height: 28px; height: 28px;
@ -419,7 +425,7 @@ body,
&:hover { &:hover {
background: $almost-white; background: $almost-white;
color: $main-green; color: var(--main-color);
} }
} }
@ -544,7 +550,10 @@ body,
color: $dark-grey; color: $dark-grey;
font-size: .9em; font-size: .9em;
a { a {
display: inline-block;
margin: 3px 0;
padding: 5px 8px; padding: 5px 8px;
text-decoration: none; text-decoration: none;
} }
@ -555,7 +564,7 @@ body,
} }
.filter-on { .filter-on {
background: $main-green; background: var(--main-color);
color: $light-green; color: $light-green;
} }
@ -694,7 +703,7 @@ body,
&:visited { &:visited {
.linklist-link { .linklist-link {
color: $dark-green; color: var(--dark-main-color);
} }
} }
@ -705,7 +714,7 @@ body,
} }
.linklist-link { .linklist-link {
color: $main-green; color: var(--main-color);
font-size: 1.1em; font-size: 1.1em;
&:hover { &:hover {
@ -713,11 +722,19 @@ body,
} }
} }
.label {
font-family: Arial, sans-serif;
font-size: .65em;
}
.label-private { .label-private {
border: solid 1px $orange; border: solid 1px $orange;
color: $orange; color: $orange;
font-family: Arial, sans-serif; }
font-size: .65em;
.label-sticky {
border: solid 1px $blue;
color: $blue;
} }
} }
@ -772,14 +789,14 @@ body,
a { a {
text-decoration: none; text-decoration: none;
color: $main-green; color: var(--main-color);
&:hover { &:hover {
color: $dark-grey; color: $dark-grey;
} }
&:visited { &:visited {
color: $dark-green; color: var(--dark-main-color);
} }
} }
} }
@ -877,7 +894,7 @@ body,
&::before { &::before {
display: block; display: block;
margin: 10px auto; margin: 10px auto;
background: linear-gradient(to right, $background-color, $dark-grey, $background-color); background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color));
width: 80%; width: 80%;
height: 1px; height: 1px;
content: ''; content: '';
@ -906,7 +923,7 @@ body,
margin: 15px 5px; margin: 15px 5px;
border: 0; border: 0;
box-shadow: 1px 1px 1px $form-input-border, -1px -1px 6px $form-input-border, -1px 1px 2px $form-input-border, 1px -1px 2px $form-input-border; box-shadow: 1px 1px 1px $form-input-border, -1px -1px 6px $form-input-border, -1px 1px 2px $form-input-border, 1px -1px 2px $form-input-border;
background: $main-green; background: var(--main-color);
min-width: 150px; min-width: 150px;
height: 35px; height: 35px;
vertical-align: center; vertical-align: center;
@ -930,7 +947,7 @@ body,
padding: 10px 0; padding: 10px 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: $main-green; color: var(--main-color);
} }
.window-subtitle { .window-subtitle {
@ -939,7 +956,7 @@ body,
a { a {
text-decoration: none; text-decoration: none;
color: $main-green; color: var(--main-color);
font-weight: bold; font-weight: bold;
&.button { &.button {
@ -1061,7 +1078,6 @@ body,
.page-form { .page-form {
.submit-buttons { .submit-buttons {
.button { .button {
display: block;
margin: auto; margin: auto;
} }
} }
@ -1267,7 +1283,7 @@ form {
.pure-button { .pure-button {
&:hover { &:hover {
background-color: $main-green; background-color: var(--main-color);
background-image: none; background-image: none;
color: $almost-white; color: $almost-white;
} }
@ -1351,7 +1367,7 @@ form {
} }
.validate-rename-tag { .validate-rename-tag {
color: $main-green; color: var(--main-color);
} }
} }
@ -1447,7 +1463,7 @@ form {
&::after { &::after {
display: block; display: block;
margin: 10px auto; margin: 10px auto;
background: linear-gradient(to right, $background-color, $dark-grey, $background-color); background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color));
width: 90%; width: 90%;
height: 1px; height: 1px;
content: ''; content: '';
@ -1497,14 +1513,14 @@ form {
.daily-entry-description { .daily-entry-description {
a { a {
text-decoration: none; text-decoration: none;
color: $main-green; color: var(--main-color);
&:hover { &:hover {
text-shadow: 1px 1px $background-linklist-info; text-shadow: 1px 1px $background-linklist-info;
} }
&:visited { &:visited {
color: $dark-green; color: var(--dark-main-color);
} }
} }
} }
@ -1561,12 +1577,12 @@ form {
} }
.pure-button-shaarli { .pure-button-shaarli {
background-color: $main-green; background-color: var(--main-color);
} }
.progressbar { .progressbar {
border-radius: 6px; border-radius: 6px;
background-color: $main-green; background-color: var(--main-color);
padding: 1px; padding: 1px;
> div { > div {
@ -1575,8 +1591,8 @@ form {
-45deg, -45deg,
$almost-white, $almost-white,
$almost-white 6px, $almost-white 6px,
$background-color 6px, var(--background-color) 6px,
$background-color 12px var(--background-color) 12px
); );
width: 0%; width: 0%;
height: 10px; height: 10px;
@ -1600,3 +1616,17 @@ form {
white-space: nowrap; white-space: nowrap;
} }
} }
// Print rules
@media print {
.shaarli-menu {
position: absolute;
}
.search-linklist,
.link-count-block,
.linklist-item-infos-controls-group,
.mobile-buttons {
display: none;
}
}

View file

@ -16,6 +16,8 @@
}, },
"require": { "require": {
"php": ">=5.6", "php": ">=5.6",
"ext-json": "*",
"ext-zlib": "*",
"shaarli/netscape-bookmark-parser": "^2.1", "shaarli/netscape-bookmark-parser": "^2.1",
"erusev/parsedown": "^1.6", "erusev/parsedown": "^1.6",
"slim/slim": "^3.0", "slim/slim": "^3.0",
@ -24,19 +26,39 @@
"gettext/gettext": "^4.4" "gettext/gettext": "^4.4"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-master",
"phpunit/phpcov": "*", "phpunit/phpcov": "*",
"phpunit/phpunit": "^5.0", "phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "2.*" "squizlabs/php_codesniffer": "2.*"
}, },
"suggest": {
"ext-curl": "Allows fetching web pages and thumbnails in a more robust way",
"ext-gd": "Required for thumbnail generation",
"ext-gettext": "Enables faster translation system in gettext mode",
"ext-intl": "Provides localized text sorting",
"ext-mbstring": "Provides multibyte (Unicode) string support"
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Shaarli\\": "application", "Shaarli\\": "application",
"Shaarli\\Api\\": "application/api/", "Shaarli\\Api\\": "application/api/",
"Shaarli\\Api\\Controllers\\": "application/api/controllers", "Shaarli\\Api\\Controllers\\": "application/api/controllers",
"Shaarli\\Api\\Exceptions\\": "application/api/exceptions", "Shaarli\\Api\\Exceptions\\": "application/api/exceptions",
"Shaarli\\Bookmark\\": "application/bookmark",
"Shaarli\\Bookmark\\Exception\\": "application/bookmark/exception",
"Shaarli\\Config\\": "application/config/", "Shaarli\\Config\\": "application/config/",
"Shaarli\\Config\\Exception\\": "application/config/exception", "Shaarli\\Config\\Exception\\": "application/config/exception",
"Shaarli\\Security\\": "application/security" "Shaarli\\Exceptions\\": "application/exceptions",
"Shaarli\\Feed\\": "application/feed",
"Shaarli\\Http\\": "application/http",
"Shaarli\\Netscape\\": "application/netscape",
"Shaarli\\Plugin\\": "application/plugin",
"Shaarli\\Plugin\\Exception\\": "application/plugin/exception",
"Shaarli\\Plugin\\Wallabag\\": "plugins/wallabag",
"Shaarli\\Render\\": "application/render",
"Shaarli\\Security\\": "application/security",
"Shaarli\\Updater\\": "application/updater",
"Shaarli\\Updater\\Exception\\": "application/updater/exception"
} }
} }
} }

253
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3876b34296fedb365517b785af8384de", "content-hash": "432005c9db3e890f42fde27036d2a70f",
"packages": [ "packages": [
{ {
"name": "arthurhoaro/web-thumbnailer", "name": "arthurhoaro/web-thumbnailer",
@ -1470,6 +1470,210 @@
"abandoned": true, "abandoned": true,
"time": "2017-06-30T09:13:00+00:00" "time": "2017-06-30T09:13:00+00:00"
}, },
{
"name": "roave/security-advisories",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "d155baccb43ba2542941fbcba258b85ce7786419"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/d155baccb43ba2542941fbcba258b85ce7786419",
"reference": "d155baccb43ba2542941fbcba258b85ce7786419",
"shasum": ""
},
"conflict": {
"3f/pygmentize": "<1.2",
"adodb/adodb-php": "<5.20.12",
"alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
"amphp/artax": "<1.0.6|>=2,<2.0.6",
"amphp/http": "<1.0.1",
"api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6",
"asymmetricrypt/asymmetricrypt": ">=0,<9.9.99",
"aws/aws-sdk-php": ">=3,<3.2.1",
"brightlocal/phpwhois": "<=4.2.5",
"bugsnag/bugsnag-laravel": ">=2,<2.0.2",
"cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4|>=3.4,<3.4.14|>=3.5,<3.5.17|>=3.6,<3.6.4",
"cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
"cartalyst/sentry": "<=2.1.6",
"codeigniter/framework": "<=3.0.6",
"composer/composer": "<=1.0.0-alpha11",
"contao-components/mediaelement": ">=2.14.2,<2.21.1",
"contao/core": ">=2,<3.5.35",
"contao/core-bundle": ">=4,<4.4.18|>=4.5,<4.5.8",
"contao/listing-bundle": ">=4,<4.4.8",
"contao/newsletter-bundle": ">=4,<4.1",
"david-garcia/phpwhois": "<=4.3.1",
"doctrine/annotations": ">=1,<1.2.7",
"doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2",
"doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1",
"doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2",
"doctrine/doctrine-bundle": "<1.5.2",
"doctrine/doctrine-module": "<=0.7.1",
"doctrine/mongodb-odm": ">=1,<1.0.2",
"doctrine/mongodb-odm-bundle": ">=2,<3.0.1",
"doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1",
"dompdf/dompdf": ">=0.6,<0.6.2",
"drupal/core": ">=7,<7.60|>=8,<8.5.8|>=8.6,<8.6.2",
"drupal/drupal": ">=7,<7.60|>=8,<8.5.8|>=8.6,<8.6.2",
"erusev/parsedown": "<1.7",
"ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1",
"ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3",
"ezsystems/repository-forms": ">=2.3,<2.3.2.1",
"ezyang/htmlpurifier": "<4.1.1",
"firebase/php-jwt": "<2",
"fooman/tcpdf": "<6.2.22",
"fossar/tcpdf-parser": "<6.2.22",
"friendsofsymfony/rest-bundle": ">=1.2,<1.2.2",
"friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
"fuel/core": "<1.8.1",
"gree/jose": "<=2.2",
"gregwar/rst": "<1.0.3",
"guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1",
"illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10",
"illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30",
"illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29",
"illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15",
"ivankristianto/phpwhois": "<=4.3",
"james-heinrich/getid3": "<1.9.9",
"joomla/session": "<1.3.1",
"jsmitty12/phpwhois": "<5.1",
"kazist/phpwhois": "<=4.2.6",
"kreait/firebase-php": ">=3.2,<3.8.1",
"la-haute-societe/tcpdf": "<6.2.22",
"laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30",
"laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10",
"league/commonmark": ">=0.15.6,<0.18.1",
"magento/magento1ce": "<1.9.4",
"magento/magento1ee": ">=1.9,<1.14.4",
"magento/product-community-edition": ">=2,<2.2.7",
"monolog/monolog": ">=1.8,<1.12",
"namshi/jose": "<2.2",
"onelogin/php-saml": "<2.10.4",
"openid/php-openid": "<2.3",
"oro/crm": ">=1.7,<1.7.4",
"oro/platform": ">=1.7,<1.7.4",
"padraic/humbug_get_contents": "<1.1.2",
"pagarme/pagarme-php": ">=0,<3",
"paragonie/random_compat": "<2",
"paypal/merchant-sdk-php": "<3.12",
"pear/archive_tar": "<1.4.4",
"phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6",
"phpoffice/phpexcel": "<=1.8.1",
"phpoffice/phpspreadsheet": "<=1.5",
"phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
"phpwhois/phpwhois": "<=4.2.5",
"phpxmlrpc/extras": "<0.6.1",
"propel/propel": ">=2.0.0-alpha1,<=2.0.0-alpha7",
"propel/propel1": ">=1,<=1.7.1",
"pusher/pusher-php-server": "<2.2.1",
"robrichards/xmlseclibs": ">=1,<3.0.2",
"sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9",
"sensiolabs/connect": "<4.2.3",
"serluck/phpwhois": "<=4.2.6",
"shopware/shopware": "<5.3.7",
"silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11",
"silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
"silverstripe/framework": ">=3,<3.3",
"silverstripe/userforms": "<3",
"simple-updates/phpwhois": "<=1",
"simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4",
"simplesamlphp/simplesamlphp": "<1.16.3",
"simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
"slim/slim": "<2.6",
"smarty/smarty": "<3.1.33",
"socalnick/scn-social-auth": "<1.15.2",
"spoonity/tcpdf": "<6.2.22",
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
"stormpath/sdk": ">=0,<9.9.99",
"swiftmailer/swiftmailer": ">=4,<5.4.5",
"sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
"sylius/sylius": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
"symfony/dependency-injection": ">=2,<2.0.17",
"symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2",
"symfony/http-foundation": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3",
"symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8",
"symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
"symfony/polyfill": ">=1,<1.10",
"symfony/polyfill-php55": ">=1,<1.10",
"symfony/routing": ">=2,<2.0.19",
"symfony/security": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.19|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
"symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7",
"symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
"symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
"symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/serializer": ">=2,<2.0.11",
"symfony/symfony": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/translation": ">=2,<2.0.17",
"symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
"symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
"symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7",
"tecnickcom/tcpdf": "<6.2.22",
"thelia/backoffice-default-template": ">=2.1,<2.1.2",
"thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2",
"theonedemon/phpwhois": "<=4.2.5",
"titon/framework": ">=0,<9.9.99",
"truckersmp/phpwhois": "<=4.3.1",
"twig/twig": "<1.20",
"typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.21|>=9,<9.5.2",
"typo3/cms-core": ">=8,<8.7.21|>=9,<9.5.2",
"typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
"typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
"ua-parser/uap-php": "<3.8",
"wallabag/tcpdf": "<6.2.22",
"willdurand/js-translation-bundle": "<2.1.1",
"yiisoft/yii": ">=1.1.14,<1.1.15",
"yiisoft/yii2": "<2.0.15",
"yiisoft/yii2-bootstrap": "<2.0.4",
"yiisoft/yii2-dev": "<2.0.15",
"yiisoft/yii2-elasticsearch": "<2.0.5",
"yiisoft/yii2-gii": "<2.0.4",
"yiisoft/yii2-jui": "<2.0.4",
"yiisoft/yii2-redis": "<2.0.8",
"zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3",
"zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2",
"zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2",
"zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5",
"zendframework/zend-diactoros": ">=1,<1.8.4",
"zendframework/zend-feed": ">=1,<2.10.3",
"zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1",
"zendframework/zend-http": ">=1,<2.8.1",
"zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6",
"zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3",
"zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2",
"zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1",
"zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4",
"zendframework/zend-validator": ">=2.3,<2.3.6",
"zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1",
"zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6",
"zendframework/zendframework": "<2.5.1",
"zendframework/zendframework1": "<1.12.20",
"zendframework/zendopenid": ">=2,<2.0.2",
"zendframework/zendxml": ">=1,<1.0.1",
"zetacomponents/mail": "<1.8.2",
"zf-commons/zfc-user": "<1.2.2",
"zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
"zfr/zfr-oauth2-server-module": "<0.1.2"
},
"type": "metapackage",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"role": "maintainer"
}
],
"description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
"time": "2019-01-15T19:39:37+00:00"
},
{ {
"name": "sebastian/code-unit-reverse-lookup", "name": "sebastian/code-unit-reverse-lookup",
"version": "1.0.1", "version": "1.0.1",
@ -2102,16 +2306,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v3.4.22", "version": "v3.4.21",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "069bf3f0e8f871a2169a06e43d9f3f03f355e9be" "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/069bf3f0e8f871a2169a06e43d9f3f03f355e9be", "url": "https://api.github.com/repos/symfony/console/zipball/a700b874d3692bc8342199adfb6d3b99f62cc61a",
"reference": "069bf3f0e8f871a2169a06e43d9f3f03f355e9be", "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2170,20 +2374,20 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-01-25T10:42:12+00:00" "time": "2019-01-04T04:42:43+00:00"
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v3.4.22", "version": "v3.4.21",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8" "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8", "url": "https://api.github.com/repos/symfony/debug/zipball/26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
"reference": "667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8", "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2226,20 +2430,20 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-01-25T10:19:25+00:00" "time": "2019-01-01T13:45:19+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v3.4.22", "version": "v3.4.21",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "7c0c627220308928e958a87c293108e5891cde1d" "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/7c0c627220308928e958a87c293108e5891cde1d", "url": "https://api.github.com/repos/symfony/finder/zipball/3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
"reference": "7c0c627220308928e958a87c293108e5891cde1d", "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2275,7 +2479,7 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-01-16T13:43:35+00:00" "time": "2019-01-01T13:45:19+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@ -2396,16 +2600,16 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.4.22", "version": "v3.4.21",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d" "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/ba11776e9e6c15ad5759a07bffb15899bac75c2d", "url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
"reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d", "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2451,7 +2655,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-01-16T10:59:17+00:00" "time": "2019-01-01T13:45:19+00:00"
}, },
{ {
"name": "theseer/fdomdocument", "name": "theseer/fdomdocument",
@ -2548,12 +2752,15 @@
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": {
"pubsubhubbub/publisher": 20 "pubsubhubbub/publisher": 20,
"roave/security-advisories": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": ">=5.6" "php": ">=5.6",
"ext-json": "*",
"ext-zlib": "*"
}, },
"platform-dev": [], "platform-dev": [],
"platform-overrides": { "platform-overrides": {

View file

@ -22,7 +22,8 @@ See [REST API](REST-API) for a list of official and community clients.
- [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli - [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli
- [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline. - [shaarli2mastodon](https://github.com/kalvn/shaarli2mastodon) by [@kalvn](https://github.com/kalvn) - This Shaarli plugin allows you to automatically publish links you post on your Mastodon timeline.
- [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a link. - [shaarli-descriptor](https://github.com/immanuelfodor/shaarli-descriptor) by [@immanuelfodor](https://github.com/immanuelfodor) - Customize the default height/number of rows of the Description field when editing a link.
- [urlextern](https://github.com/trailjeep/shaarli-urlextern) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to open external links in a new tab/window.
- [favicons](https://github.com/trailjeep/shaarli-favicons) by [@trailjeep](https://github.com/trailjeep) - Shaarli plugin to add favicon/filetype icons to links.
### Third-party themes ### Third-party themes
See [Theming](Theming) for a list of community-contributed themes, and an installation guide. See [Theming](Theming) for a list of community-contributed themes, and an installation guide.

View file

@ -24,11 +24,11 @@ Using one of the following methods:
In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. Download our **shaarli-full** archive to include dependencies. In most cases, you should download the latest Shaarli release from the [releases](https://github.com/shaarli/Shaarli/releases) page. Download our **shaarli-full** archive to include dependencies.
The current latest released version is `v0.9.7` The current latest released version is `v0.10.4`
```bash ```bash
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.9.7/shaarli-v0.9.7-full.zip $ wget https://github.com/shaarli/Shaarli/releases/download/v0.10.4/shaarli-v0.10.4-full.zip
$ unzip shaarli-v0.9.7-full.zip $ unzip shaarli-v0.10.4-full.zip
$ mv Shaarli /path/to/shaarli/ $ mv Shaarli /path/to/shaarli/
``` ```

View file

@ -137,6 +137,7 @@ If it's still not working, please [open an issue](https://github.com/shaarli/Sha
| [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. | | [render_feed](#render_feed) | Allow to do add tags in RSS and ATOM feeds. |
| [save_link](#save_link) | Allow to alter the link being saved in the datastore. | | [save_link](#save_link) | Allow to alter the link being saved in the datastore. |
| [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. | | [delete_link](#delete_link) | Allow to do an action before a link is deleted from the datastore. |
| [save_plugin_parameters](#save_plugin_parameters) | Allow to manipulate plugin parameters before they're saved. |
@ -471,6 +472,22 @@ Allow to execute any action before the link is actually removed from the datasto
- created - created
- updated - updated
#### save_plugin_parameters
Triggered when the plugin parameters are saved from the plugin administration page.
Plugins can perform an action every times their settings are updated.
For example it is used to update the CSS file of the `default_colors` plugins.
##### Data
`$data` input contains the `$_POST` array.
So if the plugin has a parameter called `MYPLUGIN_PARAMETER`,
the array will contain an entry with `MYPLUGIN_PARAMETER` as a key.
## Guide for template designer ## Guide for template designer
### Plugin administration ### Plugin administration

View file

@ -63,8 +63,12 @@ Usage of each plugin is documented in it's README file:
* `addlink-toolbar`: Adds the addlink input on the linklist page * `addlink-toolbar`: Adds the addlink input on the linklist page
* `archiveorg`: For each link, add an Archive.org icon * `archiveorg`: For each link, add an Archive.org icon
* `default_colors`: Override default theme colors.
* `isso`: Let visitor comment your shaares on permalinks with Isso.
* [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax. * [`markdown`](https://github.com/shaarli/Shaarli/blob/master/plugins/markdown/README.md): Render shaare description with Markdown syntax.
* `piwik`: A plugin that adds Piwik tracking code to Shaarli pages.
* [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos. * [`playvideos`](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md): Add a button in the toolbar allowing to watch all videos.
* `pubsubhubbub`: Enable PubSubHubbub feed publishing
* `qrcode`: For each link, add a QRCode icon. * `qrcode`: For each link, add a QRCode icon.
* [`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. * [`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.

View file

@ -17,7 +17,7 @@ Version | Status | Shaarli compatibility
:---:|:---:|:---: :---:|:---:|:---:
7.2 | Supported | Yes 7.2 | Supported | Yes
7.1 | Supported | Yes 7.1 | Supported | Yes
7.0 | Supported | Yes 7.0 | EOL: 2018-12-03 | Yes (up to Shaarli 0.10.x)
5.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x) 5.6 | EOL: 2018-12-31 | Yes (up to Shaarli 0.10.x)
5.5 | EOL: 2016-07-10 | Yes 5.5 | EOL: 2016-07-10 | Yes
5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x) 5.4 | EOL: 2015-09-14 | Yes (up to Shaarli 0.8.x)
@ -404,6 +404,8 @@ If Shaarli is served behind a proxy (i.e. there is a proxy server between client
- `X-Forwarded-Host` - `X-Forwarded-Host`
- `X-Forwarded-For` - `X-Forwarded-For`
In you [Shaarli configuration](Shaarli-configuration) `data/config.json.php`, add the public IP of your proxy under `security.trusted_proxies`.
See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues. See also [proxy-related](https://github.com/shaarli/Shaarli/issues?utf8=%E2%9C%93&q=label%3Aproxy+) issues.
## Robots and crawlers ## Robots and crawlers

View file

@ -4,7 +4,7 @@
Once your Shaarli instance is installed, the file `data/config.json.php` is generated: Once your Shaarli instance is installed, the file `data/config.json.php` is generated:
* it contains all settings in JSON format, and can be edited to customize values * it contains all settings in JSON format, and can be edited to customize values
* it defines which [plugins](Plugin-System) are enabled[](.html) * it defines which [plugins](Plugin-System) are enabled
* its values override those defined in `index.php` * its values override those defined in `index.php`
* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration * it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration
@ -32,13 +32,13 @@ On a Linux distribution:
- to give it access to Shaarli, either: - to give it access to Shaarli, either:
- unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner
- put users in the same group as the web server, and set the appropriate access rights - put users in the same group as the web server, and set the appropriate access rights
- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[](.html) - if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly
## Configuration ## Configuration
In `data/config.json.php`. In `data/config.json.php`.
See also [Plugin System](Plugin-System.html). See also [Plugin System](Plugin-System).
### Credentials ### Credentials
@ -56,6 +56,8 @@ _These settings should not be edited_
- **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). - **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php).
- **enabled_plugins**: List of enabled plugins. - **enabled_plugins**: List of enabled plugins.
- **default_note_title**: Default title of a new note. - **default_note_title**: Default title of a new note.
- **retrieve_description** (boolean): If set to true, for every new links Shaarli will try
to retrieve the description and keywords from the HTML meta tags.
### Security ### Security
@ -120,11 +122,6 @@ Must be an associative array: `translation domain => translation path`.
- **enable_thumbnails**: Enable or disable thumbnail display. - **enable_thumbnails**: Enable or disable thumbnail display.
- **enable_localcache**: Enable or disable local cache. - **enable_localcache**: Enable or disable local cache.
### Redirector
- **url**: Redirector URL, such as `anonym.to`.
- **encode_url**: Enable this if the redirector needs encoded URL to work properly.
## Configuration file example ## Configuration file example
```json ```json
@ -185,8 +182,6 @@ Must be an associative array: `translation domain => translation path`.
"hide_public_links": false, "hide_public_links": false,
"hide_timestamps": false, "hide_timestamps": false,
"open_shaarli": false, "open_shaarli": false,
"redirector": "http://anonym.to/?",
"redirector_encode_url": false
}, },
"general": { "general": {
"header_link": "?", "header_link": "?",
@ -218,10 +213,6 @@ Must be an associative array: `translation domain => translation path`.
"enable_thumbnails": true, "enable_thumbnails": true,
"enable_localcache": true "enable_localcache": true
}, },
"redirector": {
"url": "http://anonym.to/?",
"encode_url": false
},
"plugins": { "plugins": {
"WALLABAG_URL": "http://demo.wallabag.org", "WALLABAG_URL": "http://demo.wallabag.org",
"WALLABAG_VERSION": "1" "WALLABAG_VERSION": "1"

File diff suppressed because it is too large Load diff

156
index.php
View file

@ -56,31 +56,33 @@
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
// Shaarli library // Shaarli library
require_once 'application/ApplicationUtils.php'; require_once 'application/bookmark/LinkUtils.php';
require_once 'application/Cache.php';
require_once 'application/CachedPage.php';
require_once 'application/config/ConfigPlugin.php'; require_once 'application/config/ConfigPlugin.php';
require_once 'application/FeedBuilder.php'; require_once 'application/feed/Cache.php';
require_once 'application/http/HttpUtils.php';
require_once 'application/http/UrlUtils.php';
require_once 'application/updater/UpdaterUtils.php';
require_once 'application/FileUtils.php'; require_once 'application/FileUtils.php';
require_once 'application/History.php';
require_once 'application/HttpUtils.php';
require_once 'application/LinkDB.php';
require_once 'application/LinkFilter.php';
require_once 'application/LinkUtils.php';
require_once 'application/NetscapeBookmarkUtils.php';
require_once 'application/PageBuilder.php';
require_once 'application/TimeZone.php'; require_once 'application/TimeZone.php';
require_once 'application/Url.php';
require_once 'application/Utils.php'; require_once 'application/Utils.php';
require_once 'application/PluginManager.php';
require_once 'application/Router.php'; use \Shaarli\ApplicationUtils;
require_once 'application/Updater.php'; use \Shaarli\Bookmark\Exception\LinkNotFoundException;
use \Shaarli\Bookmark\LinkDB;
use \Shaarli\Config\ConfigManager; use \Shaarli\Config\ConfigManager;
use \Shaarli\Feed\CachedPage;
use \Shaarli\Feed\FeedBuilder;
use \Shaarli\History;
use \Shaarli\Languages; use \Shaarli\Languages;
use \Shaarli\Netscape\NetscapeBookmarkUtils;
use \Shaarli\Plugin\PluginManager;
use \Shaarli\Render\PageBuilder;
use \Shaarli\Render\ThemeUtils;
use \Shaarli\Router;
use \Shaarli\Security\LoginManager; use \Shaarli\Security\LoginManager;
use \Shaarli\Security\SessionManager; use \Shaarli\Security\SessionManager;
use \Shaarli\ThemeUtils;
use \Shaarli\Thumbnailer; use \Shaarli\Thumbnailer;
use \Shaarli\Updater\Updater;
// Ensure the PHP version is supported // Ensure the PHP version is supported
try { try {
@ -123,7 +125,7 @@
$conf = new ConfigManager(); $conf = new ConfigManager();
$sessionManager = new SessionManager($_SESSION, $conf); $sessionManager = new SessionManager($_SESSION, $conf);
$loginManager = new LoginManager($GLOBALS, $conf, $sessionManager); $loginManager = new LoginManager($conf, $sessionManager);
$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
$clientIpId = client_ip_id($_SERVER); $clientIpId = client_ip_id($_SERVER);
@ -310,9 +312,7 @@ function showDailyRSS($conf, $loginManager)
$LINKSDB = new LinkDB( $LINKSDB = new LinkDB(
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
$loginManager->isLoggedIn(), $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'), $conf->get('privacy.hide_public_links')
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
); );
/* Some Shaarlies may have very few links, so we need to look /* Some Shaarlies may have very few links, so we need to look
@ -354,13 +354,9 @@ function showDailyRSS($conf, $loginManager)
// We pre-format some fields for proper output. // We pre-format some fields for proper output.
foreach ($links as &$link) { foreach ($links as &$link) {
$link['formatedDescription'] = format_description( $link['formatedDescription'] = format_description($link['description']);
$link['description'],
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
);
$link['timestamp'] = $link['created']->getTimestamp(); $link['timestamp'] = $link['created']->getTimestamp();
if (startsWith($link['url'], '?')) { if (is_note($link['url'])) {
$link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
} }
} }
@ -396,9 +392,16 @@ function showDailyRSS($conf, $loginManager)
*/ */
function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
{ {
$day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
if (isset($_GET['day'])) { if (isset($_GET['day'])) {
$day = $_GET['day']; $day = $_GET['day'];
if ($day === date('Ymd', strtotime('now'))) {
$pageBuilder->assign('dayDesc', t('Today'));
} elseif ($day === date('Ymd', strtotime('-1 days'))) {
$pageBuilder->assign('dayDesc', t('Yesterday'));
}
} else {
$day = date('Ymd', strtotime('now')); // Today, in format YYYYMMDD.
$pageBuilder->assign('dayDesc', t('Today'));
} }
$days = $LINKSDB->days(); $days = $LINKSDB->days();
@ -431,11 +434,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
$taglist = explode(' ', $link['tags']); $taglist = explode(' ', $link['tags']);
uasort($taglist, 'strcasecmp'); uasort($taglist, 'strcasecmp');
$linksToDisplay[$key]['taglist']=$taglist; $linksToDisplay[$key]['taglist']=$taglist;
$linksToDisplay[$key]['formatedDescription'] = format_description( $linksToDisplay[$key]['formatedDescription'] = format_description($link['description']);
$link['description'],
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
);
$linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
} }
@ -1044,6 +1043,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$conf->set('general.timezone', $tz); $conf->set('general.timezone', $tz);
$conf->set('general.title', escape($_POST['title'])); $conf->set('general.title', escape($_POST['title']));
$conf->set('general.header_link', escape($_POST['titleLink'])); $conf->set('general.header_link', escape($_POST['titleLink']));
$conf->set('general.retrieve_description', !empty($_POST['retrieveDescription']));
$conf->set('resource.theme', escape($_POST['theme'])); $conf->set('resource.theme', escape($_POST['theme']));
$conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection']));
$conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault']));
@ -1092,6 +1092,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
); );
$PAGE->assign('continents', $continents); $PAGE->assign('continents', $continents);
$PAGE->assign('cities', $cities); $PAGE->assign('cities', $cities);
$PAGE->assign('retrieve_description', $conf->get('general.retrieve_description'));
$PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
$PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
$PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
@ -1100,7 +1101,6 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$PAGE->assign('api_enabled', $conf->get('api.enabled', true)); $PAGE->assign('api_enabled', $conf->get('api.enabled', true));
$PAGE->assign('api_secret', $conf->get('api.secret')); $PAGE->assign('api_secret', $conf->get('api.secret'));
$PAGE->assign('languages', Languages::getAvailableLanguages()); $PAGE->assign('languages', Languages::getAvailableLanguages());
$PAGE->assign('language', $conf->get('translation.language'));
$PAGE->assign('gd_enabled', extension_loaded('gd')); $PAGE->assign('gd_enabled', extension_loaded('gd'));
$PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
$PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli'));
@ -1154,22 +1154,24 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
// lf_id should only be present if the link exists. // lf_id should only be present if the link exists.
$id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId(); $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
$link['id'] = $id;
// Linkdate is kept here to: // Linkdate is kept here to:
// - use the same permalink for notes as they're displayed when creating them // - use the same permalink for notes as they're displayed when creating them
// - let users hack creation date of their posts // - let users hack creation date of their posts
// See: https://shaarli.readthedocs.io/en/master/guides/various-hacks/#changing-the-timestamp-for-a-shaare // See: https://shaarli.readthedocs.io/en/master/guides/various-hacks/#changing-the-timestamp-for-a-shaare
$linkdate = escape($_POST['lf_linkdate']); $linkdate = escape($_POST['lf_linkdate']);
$link['created'] = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
if (isset($LINKSDB[$id])) { if (isset($LINKSDB[$id])) {
// Edit // Edit
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $link['updated'] = new DateTime();
$updated = new DateTime(); $link['shorturl'] = $LINKSDB[$id]['shorturl'];
$shortUrl = $LINKSDB[$id]['shorturl']; $link['sticky'] = isset($LINKSDB[$id]['sticky']) ? $LINKSDB[$id]['sticky'] : false;
$new = false; $new = false;
} else { } else {
// New link // New link
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $link['updated'] = null;
$updated = null; $link['shorturl'] = link_small_hash($link['created'], $id);
$shortUrl = link_small_hash($created, $id); $link['sticky'] = false;
$new = true; $new = true;
} }
@ -1185,24 +1187,22 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
} }
$url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols')); $url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols'));
$link = array( $link = array_merge($link, [
'id' => $id,
'title' => trim($_POST['lf_title']), 'title' => trim($_POST['lf_title']),
'url' => $url, 'url' => $url,
'description' => $_POST['lf_description'], 'description' => $_POST['lf_description'],
'private' => (isset($_POST['lf_private']) ? 1 : 0), 'private' => (isset($_POST['lf_private']) ? 1 : 0),
'created' => $created,
'updated' => $updated,
'tags' => str_replace(',', ' ', $tags), 'tags' => str_replace(',', ' ', $tags),
'shorturl' => $shortUrl, ]);
);
// If title is empty, use the URL as title. // If title is empty, use the URL as title.
if ($link['title'] == '') { if ($link['title'] == '') {
$link['title'] = $link['url']; $link['title'] = $link['url'];
} }
if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE) { if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE
&& ! is_note($link['url'])
) {
$thumbnailer = new Thumbnailer($conf); $thumbnailer = new Thumbnailer($conf);
$link['thumbnail'] = $thumbnailer->get($url); $link['thumbnail'] = $thumbnailer->get($url);
} }
@ -1301,6 +1301,51 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
exit; exit;
} }
// -------- User clicked either "Set public" or "Set private" bulk operation
if ($targetPage == Router::$PAGE_CHANGE_VISIBILITY) {
if (! $sessionManager->checkToken($_GET['token'])) {
die(t('Wrong token.'));
}
$ids = trim($_GET['ids']);
if (strpos($ids, ' ') !== false) {
// multiple, space-separated ids provided
$ids = array_values(array_filter(preg_split('/\s+/', escape($ids))));
} else {
// only a single id provided
$ids = [$ids];
}
// assert at least one id is given
if (!count($ids)) {
die('no id provided');
}
// assert that the visibility is valid
if (!isset($_GET['newVisibility']) || !in_array($_GET['newVisibility'], ['public', 'private'])) {
die('invalid visibility');
} else {
$private = $_GET['newVisibility'] === 'private';
}
foreach ($ids as $id) {
$id = (int) escape($id);
$link = $LINKSDB[$id];
$link['private'] = $private;
$pluginManager->executeHooks('save_link', $link);
$LINKSDB[$id] = $link;
}
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk
$location = '?';
if (isset($_SERVER['HTTP_REFERER'])) {
$location = generateLocation(
$_SERVER['HTTP_REFERER'],
$_SERVER['HTTP_HOST']
);
}
header('Location: ' . $location); // After deleting the link, redirect to appropriate location
exit;
}
// -------- User clicked the "EDIT" button on a link: Display link edit form. // -------- User clicked the "EDIT" button on a link: Display link edit form.
if (isset($_GET['edit_link'])) { if (isset($_GET['edit_link'])) {
$id = (int) escape($_GET['edit_link']); $id = (int) escape($_GET['edit_link']);
@ -1347,13 +1392,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
// If this is an HTTP(S) link, we try go get the page to extract // If this is an HTTP(S) link, we try go get the page to extract
// the title (otherwise we will to straight to the edit form.) // the title (otherwise we will to straight to the edit form.)
if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
$retrieveDescription = $conf->get('general.retrieve_description');
// Short timeout to keep the application responsive // Short timeout to keep the application responsive
// The callback will fill $charset and $title with data from the downloaded page. // The callback will fill $charset and $title with data from the downloaded page.
get_http_response( get_http_response(
$url, $url,
$conf->get('general.download_timeout', 30), $conf->get('general.download_timeout', 30),
$conf->get('general.download_max_size', 4194304), $conf->get('general.download_max_size', 4194304),
get_curl_download_callback($charset, $title) get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription)
); );
if (! empty($title) && strtolower($charset) != 'utf-8') { if (! empty($title) && strtolower($charset) != 'utf-8') {
$title = mb_convert_encoding($title, 'utf-8', $charset); $title = mb_convert_encoding($title, 'utf-8', $charset);
@ -1547,6 +1593,7 @@ function ($a, $b) {
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
try { try {
if (isset($_POST['parameters_form'])) { if (isset($_POST['parameters_form'])) {
$pluginManager->executeHooks('save_plugin_parameters', $_POST);
unset($_POST['parameters_form']); unset($_POST['parameters_form']);
foreach ($_POST as $param => $value) { foreach ($_POST as $param => $value) {
$conf->set('plugins.'. $param, escape($value)); $conf->set('plugins.'. $param, escape($value));
@ -1586,7 +1633,7 @@ function ($a, $b) {
$ids = []; $ids = [];
foreach ($LINKSDB as $link) { foreach ($LINKSDB as $link) {
// A note or not HTTP(S) // A note or not HTTP(S)
if ($link['url'][0] === '?' || ! startsWith(strtolower($link['url']), 'http')) { if (is_note($link['url']) || ! startsWith(strtolower($link['url']), 'http')) {
continue; continue;
} }
$ids[] = $link['id']; $ids[] = $link['id'];
@ -1690,11 +1737,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
$linkDisp = array(); $linkDisp = array();
while ($i<$end && $i<count($keys)) { while ($i<$end && $i<count($keys)) {
$link = $linksToDisplay[$keys[$i]]; $link = $linksToDisplay[$keys[$i]];
$link['description'] = format_description( $link['description'] = format_description($link['description']);
$link['description'],
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
);
$classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
$link['class'] = $link['private'] == 0 ? $classLi : 'private'; $link['class'] = $link['private'] == 0 ? $classLi : 'private';
$link['timestamp'] = $link['created']->getTimestamp(); $link['timestamp'] = $link['created']->getTimestamp();
@ -1755,7 +1798,6 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
'search_term' => $searchterm, 'search_term' => $searchterm,
'search_tags' => $searchtags, 'search_tags' => $searchtags,
'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '', 'visibility' => ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '',
'redirector' => $conf->get('redirector.url'), // Optional redirector URL.
'links' => $linkDisp, 'links' => $linkDisp,
); );
@ -1905,9 +1947,7 @@ function install($conf, $sessionManager, $loginManager)
$linkDb = new LinkDB( $linkDb = new LinkDB(
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
$loginManager->isLoggedIn(), $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'), $conf->get('privacy.hide_public_links')
$conf->get('redirector.url'),
$conf->get('redirector.encode_url')
); );
$container = new \Slim\Container(); $container = new \Slim\Container();
@ -1930,7 +1970,7 @@ function install($conf, $sessionManager, $loginManager)
$this->put('/tags/{tagName:[\w]+}', '\Shaarli\Api\Controllers\Tags:putTag')->setName('putTag'); $this->put('/tags/{tagName:[\w]+}', '\Shaarli\Api\Controllers\Tags:putTag')->setName('putTag');
$this->delete('/tags/{tagName:[\w]+}', '\Shaarli\Api\Controllers\Tags:deleteTag')->setName('deleteTag'); $this->delete('/tags/{tagName:[\w]+}', '\Shaarli\Api\Controllers\Tags:deleteTag')->setName('deleteTag');
$this->get('/history', '\Shaarli\Api\Controllers\History:getHistory')->setName('getHistory'); $this->get('/history', '\Shaarli\Api\Controllers\HistoryController:getHistory')->setName('getHistory');
})->add('\Shaarli\Api\ApiMiddleware'); })->add('\Shaarli\Api\ApiMiddleware');
$response = $app->run(true); $response = $app->run(true);

View file

@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"awesomplete": "^1.1.2", "awesomplete": "^1.1.2",
"blazy": "^1.8.2", "blazy": "^1.8.2",
"font-awesome": "^4.7.0", "fork-awesome": "^1.1.7",
"pure-extras": "^1.0.0", "pure-extras": "^1.0.0",
"purecss": "^1.0.0" "purecss": "^1.0.0"
}, },
@ -21,7 +21,7 @@
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6", "file-loader": "^1.1.6",
"node-sass": "^4.7.2", "node-sass": "^4.12.0",
"sass-lint": "^1.12.1", "sass-lint": "^1.12.1",
"sass-loader": "^6.0.6", "sass-loader": "^6.0.6",
"style-loader": "^0.19.1", "style-loader": "^0.19.1",

21
phpdoc.dist.xml Normal file
View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<title><![CDATA[Shaarli API Reference]]></title>
<parser>
<encoding>utf8</encoding>
<markers>
<item>FIXME</item>
<item>TODO</item>
<item>WIP</item>
</markers>
<target>doc/phpdoc</target>
</parser>
<transformer>
<target>doc/phpdoc</target>
</transformer>
<files>
<file>index.php</file>
<directory>application</directory>
<directory>plugins</directory>
</files>
</phpdoc>

View file

@ -5,6 +5,8 @@
* Adds the addlink input on the linklist page. * Adds the addlink input on the linklist page.
*/ */
use Shaarli\Router;
/** /**
* When linklist is displayed, add play videos to header's toolbar. * When linklist is displayed, add play videos to header's toolbar.
* *

View file

@ -5,6 +5,8 @@
* Add an icon in the link list for archive.org. * Add an icon in the link list for archive.org.
*/ */
use Shaarli\Plugin\PluginManager;
/** /**
* Add archive.org icon to link_plugin when rendering linklist. * Add archive.org icon to link_plugin when rendering linklist.
* *

View file

@ -0,0 +1,3 @@
:root {
%s
}

View file

@ -0,0 +1,5 @@
description="Override default theme colors. Use any CSS valid color."
parameters="DEFAULT_COLORS_MAIN;DEFAULT_COLORS_BACKGROUND;DEFAULT_COLORS_DARK_MAIN"
parameter.DEFAULT_COLORS_MAIN="Main color (navbar green)"
parameter.DEFAULT_COLORS_BACKGROUND="Background color (light grey)"
parameter.DEFAULT_COLORS_DARK_MAIN="Dark main color (e.g. visited links)"

View file

@ -0,0 +1,111 @@
<?php
/**
* Plugin default_colors.
*
* Allow users to easily overrides colors of the default theme.
*/
use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
const DEFAULT_COLORS_PLACEHOLDERS = [
'DEFAULT_COLORS_MAIN',
'DEFAULT_COLORS_BACKGROUND',
'DEFAULT_COLORS_DARK_MAIN',
];
/**
* Display an error if the plugin is active a no color is configured.
*
* @param $conf ConfigManager instance
*
* @return array|null The errors array or null of there is none.
*/
function default_colors_init($conf)
{
$params = '';
foreach (DEFAULT_COLORS_PLACEHOLDERS as $placeholder) {
$params .= trim($conf->get('plugins.'. $placeholder, ''));
}
if (empty($params)) {
$error = t('Default colors plugin error: '.
'This plugin is active and no custom color is configured.');
return array($error);
}
}
/**
* When plugin parameters are saved, we regenerate the custom CSS file with provided settings.
*
* @param array $data $_POST array
*
* @return array Updated $_POST array
*/
function hook_default_colors_save_plugin_parameters($data)
{
$file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css';
$template = file_get_contents(PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css.template');
$content = '';
foreach (DEFAULT_COLORS_PLACEHOLDERS as $rule) {
$content .= ! empty($data[$rule])
? default_colors_format_css_rule($data, $rule) .';'. PHP_EOL
: '';
}
if (! empty($content)) {
file_put_contents($file, sprintf($template, $content));
}
return $data;
}
/**
* When linklist is displayed, include default_colors CSS file.
*
* @param array $data - header data.
*
* @return mixed - header data with default_colors CSS file added.
*/
function hook_default_colors_render_includes($data)
{
$file = PluginManager::$PLUGINS_PATH . '/default_colors/default_colors.css';
if (file_exists($file )) {
$data['css_files'][] = $file ;
}
return $data;
}
/**
* Create a valid CSS rule from parameters settings and plugin parameter.
*
* @param array $data $_POST array
* @param string $parameter Plugin parameter name
*
* @return string CSS rules for the provided parameter and its matching value.
*/
function default_colors_format_css_rule($data, $parameter)
{
if (empty($data[$parameter])) {
return '';
}
$key = str_replace('DEFAULT_COLORS_', '', $parameter);
$key = str_replace('_', '-', strtolower($key)) .'-color';
return ' --'. $key .': '. $data[$parameter];
}
/**
* This function is never called, but contains translation calls for GNU gettext extraction.
*/
function default_colors_translation()
{
// meta
t('Override default theme colors. Use any CSS valid color.');
t('Main color (navbar green)');
t('Background color (light grey)');
t('Dark main color (e.g. visited links)');
}

View file

@ -1 +1,4 @@
description="A demo plugin covering all use cases for template designers and plugin developers." description="A demo plugin covering all use cases for template designers and plugin developers."
parameters="DEMO_PLUGIN_PARAMETER;DEMO_PLUGIN_OTHER_PARAMETER"
parameter.DEMO_PLUGIN_PARAMETER="This is a parameter dedicated to the demo plugin. It'll be suffixed."
parameter.DEMO_PLUGIN_OTHER_PARAMETER="Other demo parameter"

View file

@ -15,6 +15,8 @@
*/ */
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* In the footer hook, there is a working example of a translation extension for Shaarli. * In the footer hook, there is a working example of a translation extension for Shaarli.
@ -454,6 +456,23 @@ function hook_demo_plugin_render_feed($data)
return $data; return $data;
} }
/**
* When plugin parameters are saved.
*
* @param array $data $_POST array
*
* @return array Updated $_POST array
*/
function hook_demo_plugin_save_plugin_parameters($data)
{
// Here we edit the provided value, but we can use this to generate config files, etc.
if (! empty($data['DEMO_PLUGIN_PARAMETER']) && ! endsWith($data['DEMO_PLUGIN_PARAMETER'], '_SUFFIX')) {
$data['DEMO_PLUGIN_PARAMETER'] .= '_SUFFIX';
}
return $data;
}
/** /**
* This function is never called, but contains translation calls for GNU gettext extraction. * This function is never called, but contains translation calls for GNU gettext extraction.
*/ */
@ -461,4 +480,6 @@ function demo_dummy_translation()
{ {
// meta // meta
t('A demo plugin covering all use cases for template designers and plugin developers.'); t('A demo plugin covering all use cases for template designers and plugin developers.');
t('This is a parameter dedicated to the demo plugin. It\'ll be suffixed.');
t('Other demo parameter');
} }

View file

@ -5,6 +5,8 @@
*/ */
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* Display an error everywhere if the plugin is enabled without configuration. * Display an error everywhere if the plugin is enabled without configuration.

View file

@ -7,6 +7,8 @@
*/ */
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/* /*
* If this tag is used on a shaare, the description won't be processed by Parsedown. * If this tag is used on a shaare, the description won't be processed by Parsedown.

View file

@ -4,6 +4,10 @@
* Plugin origin * Plugin origin
*/ */
use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* Hook render_editlink. * Hook render_editlink.

View file

@ -4,6 +4,8 @@
* Adds tracking code on each page. * Adds tracking code on each page.
*/ */
use Shaarli\Plugin\PluginManager;
/** /**
* Initialization function. * Initialization function.
* It will be called when the plugin is loaded. * It will be called when the plugin is loaded.

View file

@ -6,6 +6,9 @@
* Note: this plugin adds jQuery. * Note: this plugin adds jQuery.
*/ */
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* When linklist is displayed, add play videos to header's toolbar. * When linklist is displayed, add play videos to header's toolbar.
* *

View file

@ -11,6 +11,9 @@
use pubsubhubbub\publisher\Publisher; use pubsubhubbub\publisher\Publisher;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Feed\FeedBuilder;
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* Plugin init function - set the hub to the default appspot one. * Plugin init function - set the hub to the default appspot one.

View file

@ -1,5 +1,5 @@
<div class="linkqrcode"> <div class="linkqrcode">
<a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s"> <a href="#" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
<img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code" alt="QRCode"> <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code" alt="QRCode">
</a> </a>
</div> </div>

View file

@ -5,6 +5,9 @@
* Display a QRCode icon in link list. * Display a QRCode icon in link list.
*/ */
use Shaarli\Plugin\PluginManager;
use Shaarli\Router;
/** /**
* Add qrcode icon to link_plugin when rendering linklist. * Add qrcode icon to link_plugin when rendering linklist.
* *
@ -19,7 +22,6 @@ function hook_qrcode_render_linklist($data)
foreach ($data['links'] as &$value) { foreach ($data['links'] as &$value) {
$qrcode = sprintf( $qrcode = sprintf(
$qrcode_html, $qrcode_html,
urlencode($value['url']),
$value['url'], $value['url'],
PluginManager::$PLUGINS_PATH PluginManager::$PLUGINS_PATH
); );

View file

@ -1,4 +1,5 @@
<?php <?php
namespace Shaarli\Plugin\Wallabag;
/** /**
* Class WallabagInstance. * Class WallabagInstance.

View file

@ -1,11 +1,11 @@
<?php <?php
/** /**
* Plugin Wallabag. * Wallabag plugin
*/ */
require_once 'WallabagInstance.php';
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Plugin\PluginManager;
use Shaarli\Plugin\Wallabag\WallabagInstance;
/** /**
* Init function, return an error if the server is not set. * Init function, return an error if the server is not set.

View file

@ -1 +1 @@
<?php /* 0.10.4 */ ?> <?php /* 0.11.0 */ ?>

View file

@ -1,33 +1,14 @@
<?php <?php
namespace Shaarli;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
/** require_once 'tests/utils/FakeApplicationUtils.php';
* ApplicationUtils' tests
*/
require_once 'application/ApplicationUtils.php';
/**
* Fake ApplicationUtils class to avoid HTTP requests
*/
class FakeApplicationUtils extends ApplicationUtils
{
public static $VERSION_CODE = '';
/**
* Toggle HTTP requests, allow overriding the version code
*/
public static function getVersion($url, $timeout = 0)
{
return self::$VERSION_CODE;
}
}
/** /**
* Unitary tests for Shaarli utilities * Unitary tests for Shaarli utilities
*/ */
class ApplicationUtilsTest extends PHPUnit_Framework_TestCase class ApplicationUtilsTest extends \PHPUnit\Framework\TestCase
{ {
protected static $testUpdateFile = 'sandbox/update.txt'; protected static $testUpdateFile = 'sandbox/update.txt';
protected static $testVersion = '0.5.0'; protected static $testVersion = '0.5.0';

View file

@ -1,13 +1,15 @@
<?php <?php
require_once 'application/FileUtils.php'; namespace Shaarli;
use Exception;
/** /**
* Class FileUtilsTest * Class FileUtilsTest
* *
* Test file utility class. * Test file utility class.
*/ */
class FileUtilsTest extends PHPUnit_Framework_TestCase class FileUtilsTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string Test file path. * @var string Test file path.
@ -48,7 +50,7 @@ public function testSimpleWriteRead()
/** /**
* File not writable: raise an exception. * File not writable: raise an exception.
* *
* @expectedException IOException * @expectedException Shaarli\Exceptions\IOException
* @expectedExceptionMessage Error accessing "sandbox/flat.db" * @expectedExceptionMessage Error accessing "sandbox/flat.db"
*/ */
public function testWriteWithoutPermission() public function testWriteWithoutPermission()
@ -61,7 +63,7 @@ public function testWriteWithoutPermission()
/** /**
* Folder non existent: raise an exception. * Folder non existent: raise an exception.
* *
* @expectedException IOException * @expectedException Shaarli\Exceptions\IOException
* @expectedExceptionMessage Error accessing "nopefolder" * @expectedExceptionMessage Error accessing "nopefolder"
*/ */
public function testWriteFolderDoesNotExist() public function testWriteFolderDoesNotExist()
@ -72,7 +74,7 @@ public function testWriteFolderDoesNotExist()
/** /**
* Folder non writable: raise an exception. * Folder non writable: raise an exception.
* *
* @expectedException IOException * @expectedException Shaarli\Exceptions\IOException
* @expectedExceptionMessage Error accessing "sandbox" * @expectedExceptionMessage Error accessing "sandbox"
*/ */
public function testWriteFolderPermission() public function testWriteFolderPermission()

View file

@ -1,9 +1,11 @@
<?php <?php
require_once 'application/History.php'; namespace Shaarli;
use DateTime;
use Exception;
class HistoryTest extends PHPUnit_Framework_TestCase class HistoryTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string History file path * @var string History file path

View file

@ -7,7 +7,7 @@
/** /**
* Class LanguagesTest. * Class LanguagesTest.
*/ */
class LanguagesTest extends \PHPUnit_Framework_TestCase class LanguagesTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string Config file path (without extension). * @var string Config file path (without extension).

View file

@ -1,16 +1,12 @@
<?php <?php
namespace Shaarli\Plugin;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
/**
* Plugin Manager tests
*/
require_once 'application/PluginManager.php';
/** /**
* Unit tests for Plugins * Unit tests for Plugins
*/ */
class PluginManagerTest extends PHPUnit_Framework_TestCase class PluginManagerTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* Path to tests plugin. * Path to tests plugin.

View file

@ -1,15 +1,10 @@
<?php <?php
namespace Shaarli;
/**
* Router tests
*/
require_once 'application/Router.php';
/** /**
* Unit tests for Router * Unit tests for Router
*/ */
class RouterTest extends PHPUnit_Framework_TestCase class RouterTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* Test findPage: login page output. * Test findPage: login page output.

View file

@ -8,7 +8,7 @@
/** /**
* Unitary tests for timezone utilities * Unitary tests for timezone utilities
*/ */
class TimeZoneTest extends PHPUnit_Framework_TestCase class TimeZoneTest extends PHPUnit\Framework\TestCase
{ {
/** /**
* @var array of timezones * @var array of timezones

View file

@ -10,7 +10,7 @@
/** /**
* Unitary tests for Shaarli utilities * Unitary tests for Shaarli utilities
*/ */
class UtilsTest extends PHPUnit_Framework_TestCase class UtilsTest extends PHPUnit\Framework\TestCase
{ {
// Log file // Log file
protected static $testLogFile = 'tests.log'; protected static $testLogFile = 'tests.log';

View file

@ -2,7 +2,6 @@
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -18,7 +17,7 @@
* *
* @package Api * @package Api
*/ */
class ApiMiddlewareTest extends \PHPUnit_Framework_TestCase class ApiMiddlewareTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations

View file

@ -2,12 +2,12 @@
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Base64Url; use Shaarli\Http\Base64Url;
/** /**
* Class ApiUtilsTest * Class ApiUtilsTest
*/ */
class ApiUtilsTest extends \PHPUnit_Framework_TestCase class ApiUtilsTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* Force the timezone for ISO datetimes. * Force the timezone for ISO datetimes.

View file

@ -1,9 +1,9 @@
<?php <?php
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -11,7 +11,7 @@
require_once 'tests/utils/ReferenceHistory.php'; require_once 'tests/utils/ReferenceHistory.php';
class HistoryTest extends \PHPUnit_Framework_TestCase class HistoryTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -34,7 +34,7 @@ class HistoryTest extends \PHPUnit_Framework_TestCase
protected $container; protected $container;
/** /**
* @var History controller instance. * @var HistoryController controller instance.
*/ */
protected $controller; protected $controller;
@ -49,9 +49,9 @@ public function setUp()
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = true; $this->container['db'] = true;
$this->container['history'] = new \History(self::$testHistory); $this->container['history'] = new History(self::$testHistory);
$this->controller = new History($this->container); $this->controller = new HistoryController($this->container);
} }
/** /**
@ -78,35 +78,35 @@ public function testGetHistory()
$this->assertEquals($this->refHistory->count(), count($data)); $this->assertEquals($this->refHistory->count(), count($data));
$this->assertEquals(\History::DELETED, $data[0]['event']); $this->assertEquals(History::DELETED, $data[0]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
$data[0]['datetime'] $data[0]['datetime']
); );
$this->assertEquals(124, $data[0]['id']); $this->assertEquals(124, $data[0]['id']);
$this->assertEquals(\History::SETTINGS, $data[1]['event']); $this->assertEquals(History::SETTINGS, $data[1]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
$data[1]['datetime'] $data[1]['datetime']
); );
$this->assertNull($data[1]['id']); $this->assertNull($data[1]['id']);
$this->assertEquals(\History::UPDATED, $data[2]['event']); $this->assertEquals(History::UPDATED, $data[2]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170301_121214')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170301_121214')->format(\DateTime::ATOM),
$data[2]['datetime'] $data[2]['datetime']
); );
$this->assertEquals(123, $data[2]['id']); $this->assertEquals(123, $data[2]['id']);
$this->assertEquals(\History::CREATED, $data[3]['event']); $this->assertEquals(History::CREATED, $data[3]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170201_121214')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170201_121214')->format(\DateTime::ATOM),
$data[3]['datetime'] $data[3]['datetime']
); );
$this->assertEquals(124, $data[3]['id']); $this->assertEquals(124, $data[3]['id']);
$this->assertEquals(\History::CREATED, $data[4]['event']); $this->assertEquals(History::CREATED, $data[4]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
$data[4]['datetime'] $data[4]['datetime']
@ -131,7 +131,7 @@ public function testGetHistoryLimit()
$this->assertEquals(1, count($data)); $this->assertEquals(1, count($data));
$this->assertEquals(\History::DELETED, $data[0]['event']); $this->assertEquals(History::DELETED, $data[0]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
$data[0]['datetime'] $data[0]['datetime']
@ -156,7 +156,7 @@ public function testGetHistoryOffset()
$this->assertEquals(1, count($data)); $this->assertEquals(1, count($data));
$this->assertEquals(\History::CREATED, $data[0]['event']); $this->assertEquals(History::CREATED, $data[0]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
$data[0]['datetime'] $data[0]['datetime']
@ -181,7 +181,7 @@ public function testGetHistorySince()
$this->assertEquals(1, count($data)); $this->assertEquals(1, count($data));
$this->assertEquals(\History::DELETED, $data[0]['event']); $this->assertEquals(History::DELETED, $data[0]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
$data[0]['datetime'] $data[0]['datetime']
@ -206,7 +206,7 @@ public function testGetHistorySinceOffsetLimit()
$this->assertEquals(1, count($data)); $this->assertEquals(1, count($data));
$this->assertEquals(\History::SETTINGS, $data[0]['event']); $this->assertEquals(History::SETTINGS, $data[0]['event']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM), \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
$data[0]['datetime'] $data[0]['datetime']

View file

@ -2,7 +2,6 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -15,7 +14,7 @@
* *
* @package Api\Controllers * @package Api\Controllers
*/ */
class InfoTest extends \PHPUnit_Framework_TestCase class InfoTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -53,7 +52,7 @@ public function setUp()
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false);
$this->container['history'] = null; $this->container['history'] = null;
$this->controller = new Info($this->container); $this->controller = new Info($this->container);

View file

@ -3,13 +3,15 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
class DeleteLinkTest extends \PHPUnit_Framework_TestCase class DeleteLinkTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -32,12 +34,12 @@ class DeleteLinkTest extends \PHPUnit_Framework_TestCase
protected $refDB = null; protected $refDB = null;
/** /**
* @var \LinkDB instance. * @var LinkDB instance.
*/ */
protected $linkDB; protected $linkDB;
/** /**
* @var \History instance. * @var HistoryController instance.
*/ */
protected $history; protected $history;
@ -59,10 +61,10 @@ public function setUp()
$this->conf = new ConfigManager('tests/utils/config/configJson'); $this->conf = new ConfigManager('tests/utils/config/configJson');
$this->refDB = new \ReferenceLinkDB(); $this->refDB = new \ReferenceLinkDB();
$this->refDB->write(self::$testDatastore); $this->refDB->write(self::$testDatastore);
$this->linkDB = new \LinkDB(self::$testDatastore, true, false); $this->linkDB = new LinkDB(self::$testDatastore, true, false);
$refHistory = new \ReferenceHistory(); $refHistory = new \ReferenceHistory();
$refHistory->write(self::$testHistory); $refHistory->write(self::$testHistory);
$this->history = new \History(self::$testHistory); $this->history = new History(self::$testHistory);
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = $this->linkDB; $this->container['db'] = $this->linkDB;
@ -96,11 +98,11 @@ public function testDeleteLinkValid()
$this->assertEquals(204, $response->getStatusCode()); $this->assertEquals(204, $response->getStatusCode());
$this->assertEmpty((string) $response->getBody()); $this->assertEmpty((string) $response->getBody());
$this->linkDB = new \LinkDB(self::$testDatastore, true, false); $this->linkDB = new LinkDB(self::$testDatastore, true, false);
$this->assertFalse(isset($this->linkDB[$id])); $this->assertFalse(isset($this->linkDB[$id]));
$historyEntry = $this->history->getHistory()[0]; $historyEntry = $this->history->getHistory()[0];
$this->assertEquals(\History::DELETED, $historyEntry['event']); $this->assertEquals(History::DELETED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );
@ -110,7 +112,7 @@ public function testDeleteLinkValid()
/** /**
* Test DELETE link endpoint: reach not existing ID. * Test DELETE link endpoint: reach not existing ID.
* *
* @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException * @expectedException \Shaarli\Api\Exceptions\ApiLinkNotFoundException
*/ */
public function testDeleteLink404() public function testDeleteLink404()
{ {

View file

@ -3,7 +3,6 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -18,7 +17,7 @@
* *
* @package Shaarli\Api\Controllers * @package Shaarli\Api\Controllers
*/ */
class GetLinkIdTest extends \PHPUnit_Framework_TestCase class GetLinkIdTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -61,7 +60,7 @@ public function setUp()
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false);
$this->container['history'] = null; $this->container['history'] = null;
$this->controller = new Links($this->container); $this->controller = new Links($this->container);
@ -108,7 +107,7 @@ public function testGetLinkId()
$this->assertEquals('sTuff', $data['tags'][0]); $this->assertEquals('sTuff', $data['tags'][0]);
$this->assertEquals(false, $data['private']); $this->assertEquals(false, $data['private']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM),
$data['created'] $data['created']
); );
$this->assertEmpty($data['updated']); $this->assertEmpty($data['updated']);

View file

@ -1,8 +1,8 @@
<?php <?php
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -17,7 +17,7 @@
* *
* @package Shaarli\Api\Controllers * @package Shaarli\Api\Controllers
*/ */
class GetLinksTest extends \PHPUnit_Framework_TestCase class GetLinksTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -60,7 +60,7 @@ public function setUp()
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new LinkDB(self::$testDatastore, true, false);
$this->container['history'] = null; $this->container['history'] = null;
$this->controller = new Links($this->container); $this->controller = new Links($this->container);
@ -114,7 +114,7 @@ public function testGetLinks()
$this->assertEquals('sTuff', $first['tags'][0]); $this->assertEquals('sTuff', $first['tags'][0]);
$this->assertEquals(false, $first['private']); $this->assertEquals(false, $first['private']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM), \DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM),
$first['created'] $first['created']
); );
$this->assertEmpty($first['updated']); $this->assertEmpty($first['updated']);
@ -125,7 +125,7 @@ public function testGetLinks()
// Update date // Update date
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20160803_093033')->format(\DateTime::ATOM), \DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')->format(\DateTime::ATOM),
$link['updated'] $link['updated']
); );
} }

View file

@ -4,6 +4,7 @@
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -40,7 +41,7 @@ class PostLinkTest extends TestCase
protected $refDB = null; protected $refDB = null;
/** /**
* @var \History instance. * @var HistoryController instance.
*/ */
protected $history; protected $history;
@ -70,12 +71,12 @@ public function setUp()
$refHistory = new \ReferenceHistory(); $refHistory = new \ReferenceHistory();
$refHistory->write(self::$testHistory); $refHistory->write(self::$testHistory);
$this->history = new \History(self::$testHistory); $this->history = new History(self::$testHistory);
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false);
$this->container['history'] = new \History(self::$testHistory); $this->container['history'] = new History(self::$testHistory);
$this->controller = new Links($this->container); $this->controller = new Links($this->container);
@ -121,7 +122,7 @@ public function testPostLinkMinimal()
$data = json_decode((string) $response->getBody(), true); $data = json_decode((string) $response->getBody(), true);
$this->assertEquals(self::NB_FIELDS_LINK, count($data)); $this->assertEquals(self::NB_FIELDS_LINK, count($data));
$this->assertEquals(43, $data['id']); $this->assertEquals(43, $data['id']);
$this->assertRegExp('/[\w-_]{6}/', $data['shorturl']); $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']);
$this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']); $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']);
$this->assertEquals('?' . $data['shorturl'], $data['title']); $this->assertEquals('?' . $data['shorturl'], $data['title']);
$this->assertEquals('', $data['description']); $this->assertEquals('', $data['description']);
@ -133,7 +134,7 @@ public function testPostLinkMinimal()
$this->assertEquals('', $data['updated']); $this->assertEquals('', $data['updated']);
$historyEntry = $this->history->getHistory()[0]; $historyEntry = $this->history->getHistory()[0];
$this->assertEquals(\History::CREATED, $historyEntry['event']); $this->assertEquals(History::CREATED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );
@ -166,7 +167,7 @@ public function testPostLinkFull()
$data = json_decode((string) $response->getBody(), true); $data = json_decode((string) $response->getBody(), true);
$this->assertEquals(self::NB_FIELDS_LINK, count($data)); $this->assertEquals(self::NB_FIELDS_LINK, count($data));
$this->assertEquals(43, $data['id']); $this->assertEquals(43, $data['id']);
$this->assertRegExp('/[\w-_]{6}/', $data['shorturl']); $this->assertRegExp('/[\w_-]{6}/', $data['shorturl']);
$this->assertEquals('http://' . $link['url'], $data['url']); $this->assertEquals('http://' . $link['url'], $data['url']);
$this->assertEquals($link['title'], $data['title']); $this->assertEquals($link['title'], $data['title']);
$this->assertEquals($link['description'], $data['description']); $this->assertEquals($link['description'], $data['description']);
@ -210,11 +211,11 @@ public function testPostLinkDuplicate()
$this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
$this->assertEquals(false, $data['private']); $this->assertEquals(false, $data['private']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
\DateTime::createFromFormat(\DateTime::ATOM, $data['created']) \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
); );
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
\DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
); );
} }

View file

@ -4,12 +4,13 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
class PutLinkTest extends \PHPUnit_Framework_TestCase class PutLinkTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -32,7 +33,7 @@ class PutLinkTest extends \PHPUnit_Framework_TestCase
protected $refDB = null; protected $refDB = null;
/** /**
* @var \History instance. * @var HistoryController instance.
*/ */
protected $history; protected $history;
@ -62,12 +63,12 @@ public function setUp()
$refHistory = new \ReferenceHistory(); $refHistory = new \ReferenceHistory();
$refHistory->write(self::$testHistory); $refHistory->write(self::$testHistory);
$this->history = new \History(self::$testHistory); $this->history = new History(self::$testHistory);
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new \Shaarli\Bookmark\LinkDB(self::$testDatastore, true, false);
$this->container['history'] = new \History(self::$testHistory); $this->container['history'] = new History(self::$testHistory);
$this->controller = new Links($this->container); $this->controller = new Links($this->container);
@ -119,7 +120,7 @@ public function testPutLinkMinimal()
); );
$historyEntry = $this->history->getHistory()[0]; $historyEntry = $this->history->getHistory()[0];
$this->assertEquals(\History::UPDATED, $historyEntry['event']); $this->assertEquals(History::UPDATED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );
@ -198,11 +199,11 @@ public function testPutLinkDuplicate()
$this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']); $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
$this->assertEquals(false, $data['private']); $this->assertEquals(false, $data['private']);
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'), \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
\DateTime::createFromFormat(\DateTime::ATOM, $data['created']) \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
); );
$this->assertEquals( $this->assertEquals(
\DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'), \DateTime::createFromFormat(\Shaarli\Bookmark\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
\DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
); );
} }

View file

@ -3,13 +3,15 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\History;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
class DeleteTagTest extends \PHPUnit_Framework_TestCase class DeleteTagTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -32,12 +34,12 @@ class DeleteTagTest extends \PHPUnit_Framework_TestCase
protected $refDB = null; protected $refDB = null;
/** /**
* @var \LinkDB instance. * @var LinkDB instance.
*/ */
protected $linkDB; protected $linkDB;
/** /**
* @var \History instance. * @var HistoryController instance.
*/ */
protected $history; protected $history;
@ -59,10 +61,10 @@ public function setUp()
$this->conf = new ConfigManager('tests/utils/config/configJson'); $this->conf = new ConfigManager('tests/utils/config/configJson');
$this->refDB = new \ReferenceLinkDB(); $this->refDB = new \ReferenceLinkDB();
$this->refDB->write(self::$testDatastore); $this->refDB->write(self::$testDatastore);
$this->linkDB = new \LinkDB(self::$testDatastore, true, false); $this->linkDB = new LinkDB(self::$testDatastore, true, false);
$refHistory = new \ReferenceHistory(); $refHistory = new \ReferenceHistory();
$refHistory->write(self::$testHistory); $refHistory->write(self::$testHistory);
$this->history = new \History(self::$testHistory); $this->history = new History(self::$testHistory);
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = $this->linkDB; $this->container['db'] = $this->linkDB;
@ -97,18 +99,18 @@ public function testDeleteTagValid()
$this->assertEquals(204, $response->getStatusCode()); $this->assertEquals(204, $response->getStatusCode());
$this->assertEmpty((string) $response->getBody()); $this->assertEmpty((string) $response->getBody());
$this->linkDB = new \LinkDB(self::$testDatastore, true, false); $this->linkDB = new LinkDB(self::$testDatastore, true, false);
$tags = $this->linkDB->linksCountPerTag(); $tags = $this->linkDB->linksCountPerTag();
$this->assertFalse(isset($tags[$tagName])); $this->assertFalse(isset($tags[$tagName]));
// 2 links affected // 2 links affected
$historyEntry = $this->history->getHistory()[0]; $historyEntry = $this->history->getHistory()[0];
$this->assertEquals(\History::UPDATED, $historyEntry['event']); $this->assertEquals(History::UPDATED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );
$historyEntry = $this->history->getHistory()[1]; $historyEntry = $this->history->getHistory()[1];
$this->assertEquals(\History::UPDATED, $historyEntry['event']); $this->assertEquals(History::UPDATED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );
@ -131,13 +133,13 @@ public function testDeleteTagCaseSensitivity()
$this->assertEquals(204, $response->getStatusCode()); $this->assertEquals(204, $response->getStatusCode());
$this->assertEmpty((string) $response->getBody()); $this->assertEmpty((string) $response->getBody());
$this->linkDB = new \LinkDB(self::$testDatastore, true, false); $this->linkDB = new LinkDB(self::$testDatastore, true, false);
$tags = $this->linkDB->linksCountPerTag(); $tags = $this->linkDB->linksCountPerTag();
$this->assertFalse(isset($tags[$tagName])); $this->assertFalse(isset($tags[$tagName]));
$this->assertTrue($tags[strtolower($tagName)] > 0); $this->assertTrue($tags[strtolower($tagName)] > 0);
$historyEntry = $this->history->getHistory()[0]; $historyEntry = $this->history->getHistory()[0];
$this->assertEquals(\History::UPDATED, $historyEntry['event']); $this->assertEquals(History::UPDATED, $historyEntry['event']);
$this->assertTrue( $this->assertTrue(
(new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime'] (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
); );

View file

@ -2,8 +2,8 @@
namespace Shaarli\Api\Controllers; namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -16,7 +16,7 @@
* *
* @package Shaarli\Api\Controllers * @package Shaarli\Api\Controllers
*/ */
class GetTagNameTest extends \PHPUnit_Framework_TestCase class GetTagNameTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
* @var string datastore to test write operations * @var string datastore to test write operations
@ -59,7 +59,7 @@ public function setUp()
$this->container = new Container(); $this->container = new Container();
$this->container['conf'] = $this->conf; $this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false); $this->container['db'] = new LinkDB(self::$testDatastore, true, false);
$this->container['history'] = null; $this->container['history'] = null;
$this->controller = new Tags($this->container); $this->controller = new Tags($this->container);

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