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:
commit
984073a980
212 changed files with 5312 additions and 5473 deletions
|
@ -2,9 +2,11 @@ options:
|
|||
max-warnings: 0
|
||||
rules:
|
||||
property-sort-order:
|
||||
- 1
|
||||
-
|
||||
order: 'concentric'
|
||||
- 0
|
||||
# Sort order rule does not work with CSS variables: https://github.com/sasstools/sass-lint/issues/1161
|
||||
# - 1
|
||||
# -
|
||||
# order: 'concentric'
|
||||
no-important:
|
||||
- 0
|
||||
no-vendor-prefixes:
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -22,15 +22,17 @@ inc/languages/*/LC_MESSAGES/shaarli.mo
|
|||
|
||||
# Development and test resources
|
||||
coverage
|
||||
doxygen
|
||||
sandbox
|
||||
phpmd.html
|
||||
phpdoc.xml
|
||||
|
||||
# User plugin configuration
|
||||
plugins/*/config.php
|
||||
plugins/default_colors/default_colors.css
|
||||
|
||||
# HTML documentation
|
||||
doc/html/
|
||||
doc/phpdoc/
|
||||
|
||||
# 3rd party themes
|
||||
tpl/*
|
||||
|
@ -51,4 +53,7 @@ tpl/vintage/js
|
|||
tpl/vintage/css
|
||||
tpl/vintage/img
|
||||
|
||||
.composer.lock
|
||||
.composer.lock
|
||||
|
||||
# Documented scripts
|
||||
generate_templates.php
|
||||
|
|
15
.readthedocs.yml
Normal file
15
.readthedocs.yml
Normal 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
|
|
@ -3,6 +3,8 @@ dist: trusty
|
|||
|
||||
matrix:
|
||||
include:
|
||||
- language: php
|
||||
php: 7.3
|
||||
- language: php
|
||||
php: 7.2
|
||||
- language: php
|
||||
|
|
10
AUTHORS
10
AUTHORS
|
@ -1,9 +1,10 @@
|
|||
716 ArthurHoaro <arthur@hoa.ro>
|
||||
372 VirtualTam <virtualtam@flibidi.net>
|
||||
208 nodiscc <nodiscc@gmail.com>
|
||||
769 ArthurHoaro <arthur@hoa.ro>
|
||||
401 VirtualTam <virtualtam@flibidi.net>
|
||||
216 nodiscc <nodiscc@gmail.com>
|
||||
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
|
||||
15 Florian Eula <eula.florian@gmail.com>
|
||||
13 Emilien Klein <emilien@klein.st>
|
||||
13 Luce Carević <lcarevic@access42.net>
|
||||
12 Nicolas Danelon <hi@nicolasmd.com.ar>
|
||||
9 Willi Eggeling <thewilli@gmail.com>
|
||||
8 Christophe HENRY <christophe.henry@sbgodin.fr>
|
||||
|
@ -15,6 +16,7 @@
|
|||
4 Alexandre Alapetite <alexandre@alapetite.fr>
|
||||
4 David Sferruzza <david.sferruzza@gmail.com>
|
||||
4 Immánuel Fodor <immanuelfactor+github@gmail.com>
|
||||
3 Agurato <mail.vmonot@gmail.com>
|
||||
3 Teromene <teromene@teromene.fr>
|
||||
2 Alexandre G.-Raymond <alex@ndre.gr>
|
||||
2 Chris Kuethe <chris.kuethe@gmail.com>
|
||||
|
@ -29,6 +31,7 @@
|
|||
2 julienCXX <software@chmodplusx.eu>
|
||||
2 philipp-r <philipp-r@users.noreply.github.com>
|
||||
2 pips <pips@e5150.fr>
|
||||
2 trailjeep <trailjeep@gmail.com>
|
||||
1 Adrien Oliva <adrien.oliva@yapbreak.fr>
|
||||
1 Adrien le Maire <adrien@alemaire.be>
|
||||
1 Alexis J <alexis@effingo.be>
|
||||
|
@ -56,6 +59,7 @@
|
|||
1 Mark Gerarts <mark.gerarts@gmail.com>
|
||||
1 Marsup <marsup@gmail.com>
|
||||
1 Neros <contact@neros.fr>
|
||||
1 Rajat Hans <rajathans9@gmail.com>
|
||||
1 Sbgodin <Sbgodin@users.noreply.github.com>
|
||||
1 TsT <tst2005@gmail.com>
|
||||
1 dimtion <zizou.xena@gmail.com>
|
||||
|
|
84
CHANGELOG.md
84
CHANGELOG.md
|
@ -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/)
|
||||
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
|
||||
|
||||
### Fixed
|
||||
|
@ -39,6 +40,89 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
### Removed
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -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"
|
||||
MAINTAINER Shaarli Community
|
||||
|
||||
|
|
7
Makefile
7
Makefile
|
@ -146,10 +146,9 @@ authors:
|
|||
@git shortlog -sne > AUTHORS
|
||||
@rm .mailmap
|
||||
|
||||
### generate Doxygen documentation
|
||||
doxygen: clean
|
||||
@rm -rf doxygen
|
||||
@doxygen Doxyfile
|
||||
### generate phpDocumentor documentation
|
||||
phpdoc: clean
|
||||
@docker run --rm -v $(PWD):/data -u `id -u`:`id -g` phpdoc/phpdoc
|
||||
|
||||
### generate HTML documentation from Markdown pages with MkDocs
|
||||
htmldoc:
|
||||
|
|
|
@ -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/travis/shaarli/Shaarli/stable.svg?label=stable)](https://travis-ci.org/shaarli/Shaarli)
|
||||
•
|
||||
[![](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/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)
|
||||
|
||||
[![Join the chat at https://gitter.im/shaarli/Shaarli](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shaarli/Shaarli)
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<?php
|
||||
namespace Shaarli;
|
||||
|
||||
use Exception;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
|
||||
/**
|
||||
* Shaarli (application) utilities
|
||||
*/
|
||||
|
@ -51,7 +56,7 @@ class ApplicationUtils
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
if (! is_file($remote)) {
|
||||
if (!is_file($remote)) {
|
||||
return false;
|
||||
}
|
||||
$data = file_get_contents($remote);
|
||||
|
@ -97,7 +102,7 @@ class ApplicationUtils
|
|||
// Do not check versions for visitors
|
||||
// Do not check if the user doesn't want to
|
||||
// Do not check with dev version
|
||||
if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
|
||||
if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -111,7 +116,7 @@ class ApplicationUtils
|
|||
return false;
|
||||
}
|
||||
|
||||
if (! in_array($branch, self::$GIT_BRANCHES)) {
|
||||
if (!in_array($branch, self::$GIT_BRANCHES)) {
|
||||
throw new Exception(
|
||||
'Invalid branch selected for updates: "' . $branch . '"'
|
||||
);
|
||||
|
@ -123,7 +128,7 @@ class ApplicationUtils
|
|||
self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
|
||||
);
|
||||
|
||||
if (! $latestVersion) {
|
||||
if (!$latestVersion) {
|
||||
// Only update the file's modification date
|
||||
file_put_contents($updateFile, $currentVersion);
|
||||
return false;
|
||||
|
@ -152,9 +157,9 @@ class ApplicationUtils
|
|||
if (version_compare($curVersion, $minVersion) < 0) {
|
||||
$msg = t(
|
||||
'Your PHP version is obsolete!'
|
||||
. ' Shaarli requires at least PHP %s, and thus cannot run.'
|
||||
. ' Your PHP version has known security vulnerabilities and should be'
|
||||
. ' updated as soon as possible.'
|
||||
. ' Shaarli requires at least PHP %s, and thus cannot run.'
|
||||
. ' Your PHP version has known security vulnerabilities and should be'
|
||||
. ' updated as soon as possible.'
|
||||
);
|
||||
throw new Exception(sprintf($msg, $minVersion));
|
||||
}
|
||||
|
@ -174,50 +179,50 @@ class ApplicationUtils
|
|||
|
||||
// Check script and template directories are readable
|
||||
foreach (array(
|
||||
'application',
|
||||
'inc',
|
||||
'plugins',
|
||||
$rainTplDir,
|
||||
$rainTplDir.'/'.$conf->get('resource.theme'),
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" '. t('directory is not readable');
|
||||
'application',
|
||||
'inc',
|
||||
'plugins',
|
||||
$rainTplDir,
|
||||
$rainTplDir . '/' . $conf->get('resource.theme'),
|
||||
) as $path) {
|
||||
if (!is_readable(realpath($path))) {
|
||||
$errors[] = '"' . $path . '" ' . t('directory is not readable');
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache and data directories are readable and writable
|
||||
foreach (array(
|
||||
$conf->get('resource.thumbnails_cache'),
|
||||
$conf->get('resource.data_dir'),
|
||||
$conf->get('resource.page_cache'),
|
||||
$conf->get('resource.raintpl_tmp'),
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" '. t('directory is not readable');
|
||||
$conf->get('resource.thumbnails_cache'),
|
||||
$conf->get('resource.data_dir'),
|
||||
$conf->get('resource.page_cache'),
|
||||
$conf->get('resource.raintpl_tmp'),
|
||||
) as $path) {
|
||||
if (!is_readable(realpath($path))) {
|
||||
$errors[] = '"' . $path . '" ' . t('directory is not readable');
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" '. t('directory is not writable');
|
||||
if (!is_writable(realpath($path))) {
|
||||
$errors[] = '"' . $path . '" ' . t('directory is not writable');
|
||||
}
|
||||
}
|
||||
|
||||
// Check configuration files are readable and writable
|
||||
foreach (array(
|
||||
$conf->getConfigFileExt(),
|
||||
$conf->get('resource.datastore'),
|
||||
$conf->get('resource.ban_file'),
|
||||
$conf->get('resource.log'),
|
||||
$conf->get('resource.update_check'),
|
||||
) as $path) {
|
||||
if (! is_file(realpath($path))) {
|
||||
$conf->getConfigFileExt(),
|
||||
$conf->get('resource.datastore'),
|
||||
$conf->get('resource.ban_file'),
|
||||
$conf->get('resource.log'),
|
||||
$conf->get('resource.update_check'),
|
||||
) as $path) {
|
||||
if (!is_file(realpath($path))) {
|
||||
# the file may not exist yet
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" '. t('file is not readable');
|
||||
if (!is_readable(realpath($path))) {
|
||||
$errors[] = '"' . $path . '" ' . t('file is not readable');
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" '. t('file is not writable');
|
||||
if (!is_writable(realpath($path))) {
|
||||
$errors[] = '"' . $path . '" ' . t('file is not writable');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
require_once 'exceptions/IOException.php';
|
||||
namespace Shaarli;
|
||||
|
||||
use Shaarli\Exceptions\IOException;
|
||||
|
||||
/**
|
||||
* Class FileUtils
|
||||
|
@ -44,7 +46,7 @@ class FileUtils
|
|||
|
||||
return file_put_contents(
|
||||
$file,
|
||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix
|
||||
self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -62,7 +64,7 @@ class FileUtils
|
|||
{
|
||||
// Note that gzinflate is faster than gzuncompress.
|
||||
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
|
||||
if (! is_readable($file)) {
|
||||
if (!is_readable($file)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<?php
|
||||
namespace Shaarli;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class History
|
||||
|
@ -66,7 +70,7 @@ class History
|
|||
* History constructor.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
|
@ -166,11 +170,11 @@ class History
|
|||
*/
|
||||
protected function check()
|
||||
{
|
||||
if (! is_file($this->historyFilePath)) {
|
||||
if (!is_file($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'));
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +195,7 @@ class History
|
|||
*/
|
||||
protected function write()
|
||||
{
|
||||
$comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
|
||||
$comparaison = new DateTime('-' . $this->retentionTime . ' seconds');
|
||||
foreach ($this->history as $key => $value) {
|
||||
if ($value['datetime'] < $comparaison) {
|
||||
unset($this->history[$key]);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Shaarli;
|
||||
|
||||
use Gettext\GettextTranslator;
|
||||
use Gettext\Merge;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Translator;
|
||||
use Gettext\TranslatorInterface;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
namespace Shaarli;
|
||||
|
||||
/**
|
||||
* Class Router
|
||||
|
@ -37,6 +38,8 @@ class Router
|
|||
|
||||
public static $PAGE_DELETELINK = 'delete_link';
|
||||
|
||||
public static $PAGE_CHANGE_VISIBILITY = 'change_visibility';
|
||||
|
||||
public static $PAGE_PINLINK = 'pin';
|
||||
|
||||
public static $PAGE_EXPORT = 'export';
|
||||
|
@ -75,43 +78,43 @@ class Router
|
|||
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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_PICWALL)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_TAGLIST)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_DAILY)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$AJAX_THUMB_UPDATE)) {
|
||||
if (startsWith($query, 'do=' . self::$AJAX_THUMB_UPDATE)) {
|
||||
return self::$AJAX_THUMB_UPDATE;
|
||||
}
|
||||
|
||||
|
@ -120,23 +123,23 @@ class Router
|
|||
return self::$PAGE_LINKLIST;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_TOOLS)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_CONFIGURE)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_ADDLINK)) {
|
||||
if (startsWith($query, 'do=' . self::$PAGE_ADDLINK)) {
|
||||
return self::$PAGE_ADDLINK;
|
||||
}
|
||||
|
||||
|
@ -148,27 +151,31 @@ class Router
|
|||
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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_EXPORT)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_PLUGINSADMIN)) {
|
||||
if (startsWith($query, 'do=' . 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;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$GET_TOKEN)) {
|
||||
if (startsWith($query, 'do=' . self::$GET_TOKEN)) {
|
||||
return self::$GET_TOKEN;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
namespace Shaarli;
|
||||
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
|
||||
use WebThumbnailer\Exception\WebThumbnailerException;
|
||||
use WebThumbnailer\WebThumbnailer;
|
||||
use WebThumbnailer\Application\ConfigManager as WTConfigManager;
|
||||
|
||||
/**
|
||||
* Class Thumbnailer
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
namespace Shaarli\Api;
|
||||
|
||||
use Shaarli\Api\Exceptions\ApiException;
|
||||
use Shaarli\Api\Exceptions\ApiAuthorizationException;
|
||||
|
||||
use Shaarli\Api\Exceptions\ApiException;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Slim\Container;
|
||||
use Slim\Http\Request;
|
||||
|
@ -127,12 +126,10 @@ class ApiMiddleware
|
|||
*/
|
||||
protected function setLinkDb($conf)
|
||||
{
|
||||
$linkDb = new \LinkDB(
|
||||
$linkDb = new \Shaarli\Bookmark\LinkDB(
|
||||
$conf->get('resource.datastore'),
|
||||
true,
|
||||
$conf->get('privacy.hide_public_links'),
|
||||
$conf->get('redirector.url'),
|
||||
$conf->get('redirector.encode_url')
|
||||
$conf->get('privacy.hide_public_links')
|
||||
);
|
||||
$this->container['db'] = $linkDb;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace Shaarli\Api;
|
||||
|
||||
use Shaarli\Base64Url;
|
||||
use Shaarli\Api\Exceptions\ApiAuthorizationException;
|
||||
use Shaarli\Http\Base64Url;
|
||||
|
||||
/**
|
||||
* REST API utilities
|
||||
|
@ -12,7 +12,7 @@ class ApiUtils
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws ApiAuthorizationException the token is not valid.
|
||||
|
@ -50,7 +50,7 @@ class ApiUtils
|
|||
/**
|
||||
* 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).
|
||||
*
|
||||
* @return array Link data formatted for the REST API.
|
||||
|
@ -59,7 +59,7 @@ class ApiUtils
|
|||
{
|
||||
$out['id'] = $link['id'];
|
||||
// Not an internal link
|
||||
if ($link['url'][0] != '?') {
|
||||
if (! is_note($link['url'])) {
|
||||
$out['url'] = $link['url'];
|
||||
} else {
|
||||
$out['url'] = $indexUrl . $link['url'];
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
namespace Shaarli\Api\Controllers;
|
||||
|
||||
use Shaarli\Bookmark\LinkDB;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use \Slim\Container;
|
||||
use Slim\Container;
|
||||
|
||||
/**
|
||||
* Abstract Class ApiController
|
||||
|
@ -25,12 +26,12 @@ abstract class ApiController
|
|||
protected $conf;
|
||||
|
||||
/**
|
||||
* @var \LinkDB
|
||||
* @var LinkDB
|
||||
*/
|
||||
protected $linkDb;
|
||||
|
||||
/**
|
||||
* @var \History
|
||||
* @var HistoryController
|
||||
*/
|
||||
protected $history;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use Slim\Http\Response;
|
|||
*
|
||||
* @package Shaarli\Api\Controllers
|
||||
*/
|
||||
class History extends ApiController
|
||||
class HistoryController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Service providing operation regarding Shaarli datastore and settings.
|
|
@ -4,7 +4,6 @@ namespace Shaarli\Api\Controllers;
|
|||
|
||||
use Shaarli\Api\ApiUtils;
|
||||
use Shaarli\Api\Exceptions\ApiBadParametersException;
|
||||
use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
|
||||
use Shaarli\Api\Exceptions\ApiTagNotFoundException;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Shaarli\Api\Exceptions;
|
||||
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* Class ApiLinkNotFoundException
|
||||
*
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Shaarli\Api\Exceptions;
|
||||
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* Class ApiTagNotFoundException
|
||||
*
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
<?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.
|
||||
*
|
||||
|
@ -18,10 +29,10 @@
|
|||
* - private: Is this link private? 0=no, other value=yes
|
||||
* - tags: tags attached to this entry (separated by spaces)
|
||||
* - title Title of the link
|
||||
* - url URL of the link. Used for displayable links (no redirector, relative, etc.).
|
||||
* Can be absolute or relative.
|
||||
* Relative URLs are permalinks (e.g.'?m-ukcw')
|
||||
* - real_url Absolute processed URL.
|
||||
* - url URL of the link. Used for displayable links.
|
||||
* Can be absolute or relative in the database but the relative links
|
||||
* will be converted to absolute ones in templates.
|
||||
* - real_url Raw URL in stored in the DB (absolute or relative).
|
||||
* - shorturl Permalink smallhash
|
||||
*
|
||||
* Implements 3 interfaces:
|
||||
|
@ -77,19 +88,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess
|
|||
// Hide public links
|
||||
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
|
||||
*
|
||||
|
@ -98,21 +96,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
|
|||
* @param string $datastore datastore file path.
|
||||
* @param boolean $isLoggedIn is the user logged in?
|
||||
* @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(
|
||||
$datastore,
|
||||
$isLoggedIn,
|
||||
$hidePublicLinks,
|
||||
$redirector = '',
|
||||
$redirectorEncode = true
|
||||
$hidePublicLinks
|
||||
) {
|
||||
|
||||
$this->datastore = $datastore;
|
||||
$this->loggedIn = $isLoggedIn;
|
||||
$this->hidePublicLinks = $hidePublicLinks;
|
||||
$this->redirector = $redirector;
|
||||
$this->redirectorEncode = $redirectorEncode === true;
|
||||
$this->check();
|
||||
$this->read();
|
||||
}
|
||||
|
@ -137,7 +130,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
|
|||
if (!isset($value['id']) || empty($value['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.'));
|
||||
}
|
||||
if ($offset !== null && $offset !== $value['id']) {
|
||||
|
@ -247,19 +240,19 @@ class LinkDB implements Iterator, Countable, ArrayAccess
|
|||
$this->links = array();
|
||||
$link = array(
|
||||
'id' => 1,
|
||||
'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'),
|
||||
'url'=>'https://shaarli.readthedocs.io',
|
||||
'description'=>t(
|
||||
'title' => t('The personal, minimalist, super-fast, database free, bookmarking service'),
|
||||
'url' => 'https://shaarli.readthedocs.io',
|
||||
'description' => t(
|
||||
'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.
|
||||
|
||||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
|
||||
),
|
||||
'private'=>0,
|
||||
'created'=> new DateTime(),
|
||||
'tags'=>'opensource software',
|
||||
'private' => 0,
|
||||
'created' => new DateTime(),
|
||||
'tags' => 'opensource software',
|
||||
'sticky' => false,
|
||||
);
|
||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||
|
@ -267,12 +260,12 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
|
||||
$link = array(
|
||||
'id' => 0,
|
||||
'title'=> t('My secret stuff... - Pastebin.com'),
|
||||
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
||||
'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
|
||||
'private'=>1,
|
||||
'created'=> new DateTime('1 minute ago'),
|
||||
'tags'=>'secretstuff',
|
||||
'title' => t('My secret stuff... - Pastebin.com'),
|
||||
'url' => 'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
||||
'description' => t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
|
||||
'private' => 1,
|
||||
'created' => new DateTime('1 minute ago'),
|
||||
'tags' => 'secretstuff',
|
||||
'sticky' => false,
|
||||
);
|
||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||
|
@ -299,7 +292,7 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
|
||||
$toremove = array();
|
||||
foreach ($this->links as $key => &$link) {
|
||||
if (! $this->loggedIn && $link['private'] != 0) {
|
||||
if (!$this->loggedIn && $link['private'] != 0) {
|
||||
// Transition for not upgraded databases.
|
||||
unset($this->links[$key]);
|
||||
continue;
|
||||
|
@ -309,29 +302,21 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
sanitizeLink($link);
|
||||
|
||||
// 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']);
|
||||
}
|
||||
|
||||
// Do not use the redirector for internal links (Shaarli note URL starting with a '?').
|
||||
if (!empty($this->redirector) && !startsWith($link['url'], '?')) {
|
||||
$link['real_url'] = $this->redirector;
|
||||
if ($this->redirectorEncode) {
|
||||
$link['real_url'] .= urlencode(unescape($link['url']));
|
||||
} else {
|
||||
$link['real_url'] .= $link['url'];
|
||||
}
|
||||
} else {
|
||||
$link['real_url'] = $link['url'];
|
||||
}
|
||||
$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
|
||||
if (! isset($link['created'])) {
|
||||
if (!isset($link['created'])) {
|
||||
$link['id'] = $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['shorturl'] = smallHash($link['linkdate']);
|
||||
|
@ -417,12 +402,12 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
/**
|
||||
* 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
|
||||
* - searchterm: term search
|
||||
* @param bool $casesensitive Optional: Perform case sensitive filter
|
||||
* @param string $visibility return only all/private/public links
|
||||
* @param string $untaggedonly return only untagged links
|
||||
* @param bool $casesensitive Optional: Perform case sensitive filter
|
||||
* @param string $visibility return only all/private/public links
|
||||
* @param bool $untaggedonly return only untagged links
|
||||
*
|
||||
* @return array filtered links, all links if no suitable filter was provided.
|
||||
*/
|
||||
|
@ -432,6 +417,7 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
$visibility = 'all',
|
||||
$untaggedonly = false
|
||||
) {
|
||||
|
||||
// Filter link database according to parameters.
|
||||
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
|
||||
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
||||
|
@ -447,8 +433,8 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
/**
|
||||
* Returns the list tags appearing in the links with the given tags
|
||||
*
|
||||
* @param array $filteringTags tags selecting the links to consider
|
||||
* @param string $visibility process only all/private/public links
|
||||
* @param array $filteringTags tags selecting the links to consider
|
||||
* @param string $visibility process only all/private/public links
|
||||
*
|
||||
* @return array tag => linksCount
|
||||
*/
|
|
@ -1,5 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Shaarli\Bookmark;
|
||||
|
||||
use Exception;
|
||||
use Shaarli\Bookmark\Exception\LinkNotFoundException;
|
||||
|
||||
/**
|
||||
* Class LinkFilter.
|
||||
*
|
||||
|
@ -10,22 +15,22 @@ class LinkFilter
|
|||
/**
|
||||
* @var string permalinks.
|
||||
*/
|
||||
public static $FILTER_HASH = 'permalink';
|
||||
public static $FILTER_HASH = 'permalink';
|
||||
|
||||
/**
|
||||
* @var string text search.
|
||||
*/
|
||||
public static $FILTER_TEXT = 'fulltext';
|
||||
public static $FILTER_TEXT = 'fulltext';
|
||||
|
||||
/**
|
||||
* @var string tag filter.
|
||||
*/
|
||||
public static $FILTER_TAG = 'tags';
|
||||
public static $FILTER_TAG = 'tags';
|
||||
|
||||
/**
|
||||
* @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).
|
||||
|
@ -58,7 +63,7 @@ class LinkFilter
|
|||
*/
|
||||
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';
|
||||
}
|
||||
|
||||
|
@ -117,7 +122,7 @@ class LinkFilter
|
|||
foreach ($this->links as $key => $value) {
|
||||
if ($value['private'] && $visibility === 'private') {
|
||||
$out[$key] = $value;
|
||||
} elseif (! $value['private'] && $visibility === 'public') {
|
||||
} elseif (!$value['private'] && $visibility === 'public') {
|
||||
$out[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +137,7 @@ class LinkFilter
|
|||
*
|
||||
* @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)
|
||||
{
|
||||
|
@ -169,7 +174,7 @@ class LinkFilter
|
|||
* - see https://github.com/shaarli/Shaarli/issues/75 for examples
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
|
@ -207,7 +212,7 @@ class LinkFilter
|
|||
foreach ($this->links as $id => $link) {
|
||||
// ignore non private links when 'privatonly' is on.
|
||||
if ($visibility !== 'all') {
|
||||
if (! $link['private'] && $visibility === 'private') {
|
||||
if (!$link['private'] && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link['private'] && $visibility === 'public') {
|
||||
continue;
|
||||
|
@ -250,7 +255,9 @@ class LinkFilter
|
|||
|
||||
/**
|
||||
* generate a regex fragment out of a tag
|
||||
*
|
||||
* @param string $tag to to generate regexs from. may start with '-' to negate, contain '*' as wildcard
|
||||
*
|
||||
* @return string generated regex fragment
|
||||
*/
|
||||
private static function tag2regex($tag)
|
||||
|
@ -334,7 +341,7 @@ class LinkFilter
|
|||
// check level of visibility
|
||||
// ignore non private links when 'privateonly' is on.
|
||||
if ($visibility !== 'all') {
|
||||
if (! $link['private'] && $visibility === 'private') {
|
||||
if (!$link['private'] && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link['private'] && $visibility === 'public') {
|
||||
continue;
|
||||
|
@ -377,7 +384,7 @@ class LinkFilter
|
|||
$filtered = [];
|
||||
foreach ($this->links as $key => $link) {
|
||||
if ($visibility !== 'all') {
|
||||
if (! $link['private'] && $visibility === 'private') {
|
||||
if (!$link['private'] && $visibility === 'private') {
|
||||
continue;
|
||||
} elseif ($link['private'] && $visibility === 'public') {
|
||||
continue;
|
||||
|
@ -406,7 +413,7 @@ class LinkFilter
|
|||
*/
|
||||
public function filterDay($day)
|
||||
{
|
||||
if (! checkDateFormat('Ymd', $day)) {
|
||||
if (!checkDateFormat('Ymd', $day)) {
|
||||
throw new Exception('Invalid date format');
|
||||
}
|
||||
|
||||
|
@ -440,14 +447,3 @@ class LinkFilter
|
|||
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.');
|
||||
}
|
||||
}
|
|
@ -1,17 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Shaarli\Bookmark\LinkDB;
|
||||
|
||||
/**
|
||||
* Get cURL callback function for CURLOPT_WRITEFUNCTION
|
||||
*
|
||||
* @param string $charset 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
|
||||
*/
|
||||
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;
|
||||
$currentChunk = 0;
|
||||
$foundChunk = null;
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
|
||||
$isRedirected = true;
|
||||
|
@ -48,9 +73,34 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
|
|||
}
|
||||
if (empty($title)) {
|
||||
$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.
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -108,6 +158,35 @@ function html_extract_charset($html)
|
|||
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
|
||||
) {
|
||||