Release v0.10.3

-----BEGIN PGP SIGNATURE-----
 
 iQFLBAABCAA1FiEEEv0k8DWUT53dSMUkR6bSrUEA328FAlxxaB0XHHZpcnR1YWx0
 YW1AZmxpYmlkaS5uZXQACgkQR6bSrUEA328mfAf9GA0/rrA/5HMksQ2m9YKN7wJj
 ytCpeGdVksdvm+XRQj8dMp0oZjL+AIuEdd60W9fhMg+lVDlt9kO9GJKDc2kwkinx
 oNxXCl54BYfmlvaW98KF5GWLAkDAUFpaUDg91ZneD1kRXoU9y/NSNiKXZP+GV/L8
 8Niu2z8smypLv0UaRGblpDY+HkVfZkoV2yZJBGEcS9b7wHPy8nVv6rqUb93b+EJM
 IfooUj3DaCoa61dmTFa/a5oWnuu2Iu7F0SfMvL2rFFiMC22nXfSEGpfsKDeYihmG
 fhlSo0Fa665o94BfoetuXNiE2IU5Kez/aDk7sNNKoOoMsbxJPtzg9A0hyKS6eA==
 =xHH4
 -----END PGP SIGNATURE-----

Merge tag 'v0.10.3' into myShaarli_commu

Release v0.10.3
This commit is contained in:
Knah Tsaeb 2019-02-28 14:29:52 +01:00
commit 272b07627b
102 changed files with 1240 additions and 1219 deletions

2
.gitignore vendored
View file

@ -50,3 +50,5 @@ tpl/default/img
tpl/vintage/js
tpl/vintage/css
tpl/vintage/img
.composer.lock

12
AUTHORS
View file

@ -1,6 +1,6 @@
687 ArthurHoaro <arthur@hoa.ro>
355 VirtualTam <virtualtam@flibidi.net>
195 nodiscc <nodiscc@gmail.com>
715 ArthurHoaro <arthur@hoa.ro>
370 VirtualTam <virtualtam@flibidi.net>
208 nodiscc <nodiscc@gmail.com>
56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
15 Florian Eula <eula.florian@gmail.com>
13 Emilien Klein <emilien@klein.st>
@ -8,6 +8,7 @@
9 Willi Eggeling <thewilli@gmail.com>
8 Christophe HENRY <christophe.henry@sbgodin.fr>
6 B. van Berkum <dev@dotmpe.com>
6 llune <llune@users.noreply.github.com>
5 Lucas Cimon <lucas.cimon@gmail.com>
5 Mark Schmitz <kramred@gmail.com>
5 kalvn <kalvnthereal@gmail.com>
@ -15,10 +16,11 @@
4 David Sferruzza <david.sferruzza@gmail.com>
4 Immánuel Fodor <immanuelfactor+github@gmail.com>
3 Teromene <teromene@teromene.fr>
3 llune <llune@users.noreply.github.com>
2 Alexandre G.-Raymond <alex@ndre.gr>
2 Chris Kuethe <chris.kuethe@gmail.com>
2 Felix Bartels <felix@host-consultants.de>
2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
2 Luce Carević <lcarevic@access42.net>
2 Mathieu Chabanon <git@matchab.fr>
2 Miloš Jovanović <mjovanovic@gmail.com>
2 Qwerty <champlywood@free.fr>
@ -29,9 +31,9 @@
2 pips <pips@e5150.fr>
1 Adrien Oliva <adrien.oliva@yapbreak.fr>
1 Adrien le Maire <adrien@alemaire.be>
1 Alexandre G.-Raymond <alex@ndre.gr>
1 Alexis J <alexis@effingo.be>
1 Angristan <angristan@users.noreply.github.com>
1 Bish Erbas <42714627+bisherbas@users.noreply.github.com>
1 BoboTiG <bobotig@gmail.com>
1 Bronco <bronco@warriordudimanche.net>
1 Buster One <37770318+buster-one@users.noreply.github.com>

View file

@ -4,6 +4,36 @@ 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/).
## [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.10.2](https://github.com/shaarli/Shaarli/releases/tag/v0.10.2) - 2018-08-11
### Fixed
@ -12,7 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [v0.10.1](https://github.com/shaarli/Shaarli/releases/tag/v0.10.1) - 2018-08-11
### Changed
### Changed
- Accessibility:
- Remove alt text on the logo
@ -46,7 +76,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Use Travis matrix and stages to run Javascript tests in a dedicated environment
- Add tag endpoint in the REST API
- Build the documentation in Travis builds
- Provide a Docker Compose example
- Provide a Docker Compose example
### Changed
- Use web-thumbnailer to retrieve thumbnails (see #687)

View file

@ -2,8 +2,6 @@
# Makefile for PHP code analysis & testing, documentation and release generation
BIN = vendor/bin
PHP_SOURCE = index.php application tests plugins
PHP_COMMA_SOURCE = index.php,application,tests,plugins
all: static_analysis_summary check_permissions test
@ -17,14 +15,6 @@ docker_%:
rsync -az /shaarli/ ~/shaarli/
cd ~/shaarli && make $*
##
# Concise status of the project
# These targets are non-blocking: || exit 0
##
static_analysis_summary: code_sniffer_source copy_paste mess_detector_summary
@echo
##
# PHP_CodeSniffer
# Detects PHP syntax errors
@ -32,70 +22,26 @@ static_analysis_summary: code_sniffer_source copy_paste mess_detector_summary
# - http://pear.php.net/manual/en/package.php.php-codesniffer.usage.php
# - http://pear.php.net/manual/en/package.php.php-codesniffer.reporting.php
##
PHPCS := $(BIN)/phpcs
code_sniffer: code_sniffer_full
code_sniffer:
@$(PHPCS)
### - errors filtered by coding standard: PEAR, PSR1, PSR2, Zend...
PHPCS_%:
@$(BIN)/phpcs $(PHP_SOURCE) --report-full --report-width=200 --standard=$*
@$(PHPCS) --report-full --report-width=200 --standard=$*
### - errors by Git author
code_sniffer_blame:
@$(BIN)/phpcs $(PHP_SOURCE) --report-gitblame
@$(PHPCS) --report-gitblame
### - all errors/warnings
code_sniffer_full:
@$(BIN)/phpcs $(PHP_SOURCE) --report-full --report-width=200
@$(PHPCS) --report-full --report-width=200
### - errors grouped by kind
code_sniffer_source:
@$(BIN)/phpcs $(PHP_SOURCE) --report-source || exit 0
##
# PHP Copy/Paste Detector
# Detects code redundancy
# Documentation: https://github.com/sebastianbergmann/phpcpd
##
copy_paste:
@echo "-----------------------"
@echo "PHP COPY/PASTE DETECTOR"
@echo "-----------------------"
@$(BIN)/phpcpd $(PHP_SOURCE) || exit 0
@echo
##
# PHP Mess Detector
# Detects PHP syntax errors, sorted by category
# Rules documentation: http://phpmd.org/rules/index.html
##
MESS_DETECTOR_RULES = cleancode,codesize,controversial,design,naming,unusedcode
mess_title:
@echo "-----------------"
@echo "PHP MESS DETECTOR"
@echo "-----------------"
### - all warnings
mess_detector: mess_title
@$(BIN)/phpmd $(PHP_COMMA_SOURCE) text $(MESS_DETECTOR_RULES) | sed 's_.*\/__'
### - all warnings + HTML output contains links to PHPMD's documentation
mess_detector_html:
@$(BIN)/phpmd $(PHP_COMMA_SOURCE) html $(MESS_DETECTOR_RULES) \
--reportfile phpmd.html || exit 0
### - warnings grouped by message, sorted by descending frequency order
mess_detector_grouped: mess_title
@$(BIN)/phpmd $(PHP_SOURCE) text $(MESS_DETECTOR_RULES) \
| cut -f 2 | sort | uniq -c | sort -nr
### - summary: number of warnings by rule set
mess_detector_summary: mess_title
@for rule in $$(echo $(MESS_DETECTOR_RULES) | tr ',' ' '); do \
warnings=$$($(BIN)/phpmd $(PHP_COMMA_SOURCE) text $$rule | wc -l); \
printf "$$warnings\t$$rule\n"; \
done;
@$(PHPCS) --report-source || exit 0
##
# Checks source file & script permissions

View file

@ -9,7 +9,7 @@ _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)
&bull;
[![](https://img.shields.io/badge/latest-v0.10.1-blue.svg)](https://github.com/shaarli/Shaarli/releases/tag/v0.10.1)
[![](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/travis/shaarli/Shaarli/latest.svg?label=latest)](https://travis-ci.org/shaarli/Shaarli)
&bull;
[![](https://img.shields.io/badge/master-v0.10.x-blue.svg)](https://github.com/shaarli/Shaarli)

View file

@ -24,7 +24,7 @@ class ApplicationUtils
*
* @return mixed the version code from the repository if available, else 'false'
*/
public static function getLatestGitVersionCode($url, $timeout=2)
public static function getLatestGitVersionCode($url, $timeout = 2)
{
list($headers, $data) = get_http_response($url, $timeout);
@ -86,13 +86,14 @@ public static function getVersion($remote, $timeout = 2)
*
* @return mixed the new version code if available and greater, else 'false'
*/
public static function checkUpdate($currentVersion,
$updateFile,
$checkInterval,
$enableCheck,
$isLoggedIn,
$branch='stable')
{
public static function checkUpdate(
$currentVersion,
$updateFile,
$checkInterval,
$enableCheck,
$isLoggedIn,
$branch = 'stable'
) {
// Do not check versions for visitors
// Do not check if the user doesn't want to
// Do not check with dev version

View file

@ -2,7 +2,6 @@
namespace Shaarli;
/**
* URL-safe Base64 operations
*
@ -17,7 +16,8 @@ class Base64Url
*
* @return string Base64Url-encoded data
*/
public static function encode($data) {
public static function encode($data)
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
@ -28,7 +28,8 @@ public static function encode($data) {
*
* @return string Decoded data
*/
public static function decode($data) {
public static function decode($data)
{
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
}

View file

@ -162,7 +162,8 @@ protected function buildItem($link, $pageaddr)
$upDate = $link['updated'];
$link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
} else {
$link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);;
$link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);
;
}
// Save the more recent item.
@ -260,7 +261,6 @@ protected function getIsoDate(DateTime $date, $format = false)
}
if ($this->feedType == self::$FEED_RSS) {
return $date->format(DateTime::RSS);
}
return $date->format(DateTime::ATOM);
}

View file

@ -7,7 +7,8 @@
* @param int $timeout network timeout (in seconds)
* @param int $maxBytes maximum downloaded bytes (default: 4 MiB)
* @param callable|string $curlWriteFunction Optional callback called during the download (cURL CURLOPT_WRITEFUNCTION).
* Can be used to add download conditions on the headers (response code, content type, etc.).
* Can be used to add download conditions on the
* headers (response code, content type, etc.).
*
* @return array HTTP response headers, downloaded content
*
@ -64,29 +65,30 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304, $curlWriteF
}
// General cURL settings
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array('Accept-Language: ' . $acceptLanguage)
);
curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRedirs);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
if (is_callable($curlWriteFunction)) {
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $curlWriteFunction);
}
// Max download size management
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
function($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes)
{
curl_setopt($ch, CURLOPT_BUFFERSIZE, 1024*16);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt(
$ch,
CURLOPT_PROGRESSFUNCTION,
function ($arg0, $arg1, $arg2, $arg3, $arg4 = 0) use ($maxBytes) {
if (version_compare(phpversion(), '5.5', '<')) {
// PHP version lower than 5.5
// Callback has 4 arguments
@ -232,7 +234,6 @@ function get_redirected_headers($url, $redirectionLimit = 3)
&& !empty($headers)
&& (strpos($headers[0], '301') !== false || strpos($headers[0], '302') !== false)
&& !empty($headers['Location'])) {
$redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location'];
if ($redirection != $url) {
$redirection = getAbsoluteUrl($url, $redirection);

View file

@ -92,7 +92,7 @@ public function __construct($language, $conf)
/**
* Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
*/
protected function initGettextTranslator ()
protected function initGettextTranslator()
{
$this->translator = new GettextTranslator();
$this->translator->setLanguage($this->language);
@ -125,7 +125,8 @@ protected function initPhpTranslator()
$translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
$translations->setDomain('shaarli');
$this->translator->loadTranslations($translations);
} catch (\InvalidArgumentException $e) {}
} catch (\InvalidArgumentException $e) {
}
// Default extension translation from the current theme
$theme = $this->conf->get('theme');
@ -137,7 +138,8 @@ protected function initPhpTranslator()
);
$translations->setDomain($theme);
$this->translator->loadTranslations($translations);
} catch (\InvalidArgumentException $e) {}
} catch (\InvalidArgumentException $e) {
}
}
// Extension translations (plugins, themes, etc.).
@ -147,10 +149,13 @@ protected function initPhpTranslator()
}
try {
$extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po');
$extension = Translations::fromPoFile(
$translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po'
);
$extension->setDomain($domain);
$this->translator->loadTranslations($extension);
} catch (\InvalidArgumentException $e) {}
} catch (\InvalidArgumentException $e) {
}
}
}

View file

@ -107,8 +107,7 @@ public function __construct(
$hidePublicLinks,
$redirector = '',
$redirectorEncode = true
)
{
) {
$this->datastore = $datastore;
$this->loggedIn = $isLoggedIn;
$this->hidePublicLinks = $hidePublicLinks;
@ -250,11 +249,14 @@ private function check()
'id' => 1,
'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.
'description'=>t(
'Welcome to Shaarli! This is your first public bookmark. '
.'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.'),
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
),
'private'=>0,
'created'=> new DateTime(),
'tags'=>'opensource software'
@ -317,8 +319,7 @@ private function read()
} else {
$link['real_url'] .= $link['url'];
}
}
else {
} else {
$link['real_url'] = $link['url'];
}
@ -403,7 +404,8 @@ public function filterHash($request)
*
* @return array list of shaare found.
*/
public function filterDay($request) {
public function filterDay($request)
{
$linkFilter = new LinkFilter($this->links);
return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
}
@ -420,8 +422,12 @@ public function filterDay($request) {
*
* @return array filtered links, all links if no suitable filter was provided.
*/
public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all', $untaggedonly = false)
{
public function filterSearch(
$filterRequest = array(),
$casesensitive = false,
$visibility = 'all',
$untaggedonly = false
) {
// Filter link database according to parameters.
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
@ -492,8 +498,7 @@ public function renameTag($from, $to)
$delete = empty($to);
// True for case-sensitive tag search.
$linksToAlter = $this->filterSearch(['searchtags' => $from], true);
foreach($linksToAlter as $key => &$value)
{
foreach ($linksToAlter as $key => &$value) {
$tags = preg_split('/\s+/', trim($value['tags']));
if (($pos = array_search($from, $tags)) !== false) {
if ($delete) {
@ -536,7 +541,10 @@ public function reorder($order = 'DESC')
{
$order = $order === 'ASC' ? -1 : 1;
// Reorder array by dates.
usort($this->links, function($a, $b) use ($order) {
usort($this->links, function ($a, $b) use ($order) {
if (isset($a['sticky']) && isset($b['sticky']) && $a['sticky'] !== $b['sticky']) {
return $a['sticky'] ? -1 : 1;
}
return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
});

View file

@ -62,7 +62,7 @@ public function filter($type, $request, $casesensitive = false, $visibility = 'a
$visibility = 'all';
}
switch($type) {
switch ($type) {
case self::$FILTER_HASH:
return $this->filterSmallHash($request);
case self::$FILTER_TAG | self::$FILTER_TEXT: // == "vuotext"
@ -205,7 +205,6 @@ private function filterFulltext($searchterms, $visibility = 'all')
// Iterate over every stored link.
foreach ($this->links as $id => $link) {
// ignore non private links when 'privatonly' is on.
if ($visibility !== 'all') {
if (! $link['private'] && $visibility === 'private') {
@ -257,11 +256,11 @@ private function filterFulltext($searchterms, $visibility = 'all')
private static function tag2regex($tag)
{
$len = strlen($tag);
if(!$len || $tag === "-" || $tag === "*"){
if (!$len || $tag === "-" || $tag === "*") {
// nothing to search, return empty regex
return '';
}
if($tag[0] === "-") {
if ($tag[0] === "-") {
// query is negated
$i = 1; // use offset to start after '-' character
$regex = '(?!'; // create negative lookahead
@ -271,14 +270,14 @@ private static function tag2regex($tag)
}
$regex .= '.*(?:^| )'; // before tag may only be a space or the beginning
// iterate over string, separating it into placeholder and content
for(; $i < $len; $i++){
if($tag[$i] === '*'){
for (; $i < $len; $i++) {
if ($tag[$i] === '*') {
// placeholder found
$regex .= '[^ ]*?';
} else {
// regular characters
$offset = strpos($tag, '*', $i);
if($offset === false){
if ($offset === false) {
// no placeholder found, set offset to end of string
$offset = $len;
}
@ -310,19 +309,19 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
{
// get single tags (we may get passed an array, even though the docs say different)
$inputTags = $tags;
if(!is_array($tags)) {
if (!is_array($tags)) {
// we got an input string, split tags
$inputTags = preg_split('/(?:\s+)|,/', $inputTags, -1, PREG_SPLIT_NO_EMPTY);
}
if(!count($inputTags)){
if (!count($inputTags)) {
// no input tags
return $this->noFilter($visibility);
}
// build regex from all tags
$re = '/^' . implode(array_map("self::tag2regex", $inputTags)) . '.*$/';
if(!$casesensitive) {
if (!$casesensitive) {
// make regex case insensitive
$re .= 'i';
}
@ -342,7 +341,7 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
}
}
$search = $link['tags']; // build search string, start with tags of current link
if(strlen(trim($link['description'])) && strpos($link['description'], '#') !== false){
if (strlen(trim($link['description'])) && strpos($link['description'], '#') !== false) {
// description given and at least one possible tag found
$descTags = array();
// find all tags in the form of #tag in the description
@ -351,13 +350,13 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
$link['description'],
$descTags
);
if(count($descTags[1])){
if (count($descTags[1])) {
// there were some tags in the description, add them to the search string
$search .= ' ' . implode(' ', $descTags[1]);
}
};
// match regular expression with search string
if(!preg_match($re, $search)){
if (!preg_match($re, $search)) {
// this entry does _not_ match our regex
continue;
}

View file

@ -23,7 +23,7 @@ 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 ($curlGetInfo, &$charset, &$title, &$isRedirected) {
$responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
$isRedirected = true;
@ -201,7 +201,8 @@ function space2nbsp($text)
* @return string formatted description.
*/
function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '') {
function format_description($description, $redirector = '', $urlEncode = true, $indexUrl = '')
{
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector, $urlEncode), $indexUrl)));
}

View file

@ -72,18 +72,20 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
private static function importStatus(
$filename,
$filesize,
$importCount=0,
$overwriteCount=0,
$skipCount=0,
$duration=0
)
{
$importCount = 0,
$overwriteCount = 0,
$skipCount = 0,
$duration = 0
) {
$status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
$status .= t('has an unknown file format. Nothing was imported.');
} else {
$status .= vsprintf(
t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'),
t(
'was successfully processed in %d seconds: '
.'%d links imported, %d links overwritten, %d links skipped.'
),
[$duration, $importCount, $overwriteCount, $skipCount]
);
}

View file

@ -78,7 +78,6 @@ private function initialize()
);
$this->tpl->assign('newVersion', escape($version));
$this->tpl->assign('versionError', '');
} catch (Exception $exc) {
logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
$this->tpl->assign('newVersion', '');
@ -101,7 +100,7 @@ private function initialize()
'version_hash',
ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
);
$this->tpl->assign('scripturl', index_url($_SERVER));
$this->tpl->assign('index_url', index_url($_SERVER));
$visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
$this->tpl->assign('visibility', $visibility);
$this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
@ -163,7 +162,7 @@ public function assignAll($data)
$this->initialize();
}
if (empty($data) || !is_array($data)){
if (empty($data) || !is_array($data)) {
return false;
}

View file

@ -75,8 +75,7 @@ public function load($authorizedPlugins)
try {
$this->loadPlugin($dirs[$index], $plugin);
}
catch (PluginFileNotFoundException $e) {
} catch (PluginFileNotFoundException $e) {
error_log($e->getMessage());
}
}

View file

@ -37,6 +37,8 @@ class Router
public static $PAGE_DELETELINK = 'delete_link';
public static $PAGE_PINLINK = 'pin';
public static $PAGE_EXPORT = 'export';
public static $PAGE_IMPORT = 'import';
@ -146,6 +148,10 @@ public static function findPage($query, $get, $loggedIn)
return self::$PAGE_DELETELINK;
}
if (startsWith($query, 'do='. self::$PAGE_PINLINK)) {
return self::$PAGE_PINLINK;
}
if (startsWith($query, 'do='. self::$PAGE_EXPORT)) {
return self::$PAGE_EXPORT;
}

View file

@ -58,7 +58,10 @@ public function __construct($conf)
$this->conf->set('thumbnails.enabled', false);
$this->conf->write(true);
// TODO: create a proper error handling system able to catch exceptions...
die(t('php-gd extension must be loaded to use thumbnails. Thumbnails are now disabled. Please reload the page.'));
die(t(
'php-gd extension must be loaded to use thumbnails. '
.'Thumbnails are now disabled. Please reload the page.'
));
}
$this->wt = new WebThumbnailer();

View file

@ -183,7 +183,7 @@ public function updateMethodConfigToJson()
}
}
try{
try {
$this->conf->write($this->isLoggedIn);
return true;
} catch (IOException $e) {
@ -517,6 +517,26 @@ public function updateMethodWebThumbnailer()
return true;
}
/**
* Set sticky = false on all links
*
* @return bool true if the update is successful, false otherwise.
*/
public function updateMethodSetSticky()
{
foreach ($this->linkDB as $key => $link) {
if (isset($link['sticky'])) {
return true;
}
$link['sticky'] = false;
$this->linkDB[$key] = $link;
}
$this->linkDB->save($this->conf->get('resource.page_cache'));
return true;
}
}
/**

View file

@ -34,8 +34,8 @@ function unparse_url($parsedUrl)
*/
function cleanup_url($url)
{
$obj_url = new Url($url);
return $obj_url->cleanup();
$obj_url = new Url($url);
return $obj_url->cleanup();
}
/**
@ -47,8 +47,8 @@ function cleanup_url($url)
*/
function get_url_scheme($url)
{
$obj_url = new Url($url);
return $obj_url->getScheme();
$obj_url = new Url($url);
return $obj_url->getScheme();
}
/**
@ -217,7 +217,7 @@ protected function cleanupQuery()
}
$this->parts['query'] = implode('&', $queryParams);
}
}
/**
* Removes undesired fragments
@ -269,7 +269,8 @@ public function idnToAscii()
*
* @return string the URL scheme or false if none is provided.
*/
public function getScheme() {
public function getScheme()
{
if (!isset($this->parts['scheme'])) {
return false;
}
@ -281,7 +282,8 @@ public function getScheme() {
*
* @return string the URL host or false if none is provided.
*/
public function getHost() {
public function getHost()
{
if (empty($this->parts['host'])) {
return false;
}
@ -293,7 +295,8 @@ public function getHost() {
*
* @return true is HTTP, false otherwise.
*/
public function isHttp() {
public function isHttp()
{
return strpos(strtolower($this->parts['scheme']), 'http') !== false;
}
}

View file

@ -97,7 +97,7 @@ function escape($input)
if (is_array($input)) {
$out = array();
foreach($input as $key => $value) {
foreach ($input as $key => $value) {
$out[$key] = escape($value);
}
return $out;
@ -355,10 +355,13 @@ function return_bytes($val)
$val = trim($val);
$last = strtolower($val[strlen($val)-1]);
$val = intval(substr($val, 0, -1));
switch($last) {
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
switch ($last) {
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
@ -452,6 +455,7 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
*
* @return string Text translated.
*/
function t($text, $nText = '', $nb = 1, $domain = 'shaarli') {
function t($text, $nText = '', $nb = 1, $domain = 'shaarli')
{
return dn__($domain, $text, $nText, $nb);
}

View file

@ -65,7 +65,7 @@ public function __invoke($request, $response, $next)
try {
$this->checkRequest($request);
$response = $next($request, $response);
} catch(ApiException $e) {
} catch (ApiException $e) {
$e->setResponse($response);
$e->setDebug($this->conf->get('dev.debug', false));
$response = $e->getApiResponse();
@ -98,7 +98,8 @@ protected function checkRequest($request)
*
* @throws ApiAuthorizationException The token couldn't be validated.
*/
protected function checkToken($request) {
protected function checkToken($request)
{
if (! $request->hasHeader('Authorization')) {
throw new ApiAuthorizationException('JWT token not provided');
}

View file

@ -41,7 +41,7 @@ abstract class ApiController
/**
* ApiController constructor.
*
*
* Note: enabling debug mode displays JSON with readable formatting.
*
* @param Container $ci Slim container.

View file

@ -35,8 +35,7 @@ public function getHistory($request, $response)
$offset = $request->getParam('offset');
if (empty($offset)) {
$offset = 0;
}
elseif (ctype_digit($offset)) {
} elseif (ctype_digit($offset)) {
$offset = (int) $offset;
} else {
throw new ApiBadParametersException('Invalid offset');

View file

@ -7,7 +7,7 @@
/**
* Class Info
*
*
* REST API Controller: /info
*
* @package Api\Controllers
@ -17,7 +17,7 @@ class Info extends ApiController
{
/**
* Service providing various information about Shaarli instance.
*
*
* @param Request $request Slim request.
* @param Response $response Slim response.
*

View file

@ -10,7 +10,8 @@
* Parent Exception related to the API, able to generate a valid Response (ResponseInterface).
* Also can include various information in debug mode.
*/
abstract class ApiException extends \Exception {
abstract class ApiException extends \Exception
{
/**
* @var Response instance from Slim.
@ -27,7 +28,7 @@ abstract class ApiException extends \Exception {
*
* @return Response Final response to give.
*/
public abstract function getApiResponse();
abstract public function getApiResponse();
/**
* Creates ApiResponse body.
@ -36,7 +37,8 @@ public abstract function getApiResponse();
*
* @return array|string response body
*/
protected function getApiResponseBody() {
protected function getApiResponseBody()
{
if ($this->debug !== true) {
return $this->getMessage();
}

View file

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

View file

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

View file

@ -104,12 +104,20 @@ public function write($filepath, $conf)
// Store all $conf['config']
foreach ($conf['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'config\'][\''
. $key
.'\'] = '
.var_export($conf['config'][$key], true).';'
. PHP_EOL;
}
if (isset($conf['plugins'])) {
foreach ($conf['plugins'] as $key => $value) {
$configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'plugins\'][\''
. $key
.'\'] = '
.var_export($conf['plugins'][$key], true).';'
. PHP_EOL;
}
}

View file

@ -34,8 +34,7 @@ function save_plugin_config($formData)
// If there is no order, it means a disabled plugin has been enabled.
if (isset($formData['order_' . $key])) {
$plugins[(int) $formData['order_' . $key]] = $key;
}
else {
} else {
$newEnabledPlugins[] = $key;
}
}

View file

@ -95,7 +95,6 @@ public function checkLoginState($cookie, $clientIpId)
// The user client has a valid stay-signed-in cookie
// Session information is updated with the current client information
$this->sessionManager->storeLoginInfo($clientIpId);
} elseif ($this->sessionManager->hasSessionExpired()
|| $this->sessionManager->hasClientIpChanged($clientIpId)
) {

View file

@ -422,12 +422,12 @@ function init(description) {
/**
* Bulk actions
*/
const linkCheckboxes = document.querySelectorAll('.delete-checkbox');
const linkCheckboxes = document.querySelectorAll('.link-checkbox');
const bar = document.getElementById('actions');
[...linkCheckboxes].forEach((checkbox) => {
checkbox.style.display = 'inline-block';
checkbox.addEventListener('click', () => {
const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
checkbox.addEventListener('change', () => {
const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
const count = [...linkCheckedCheckboxes].length;
if (count === 0 && bar.classList.contains('open')) {
bar.classList.toggle('open');
@ -444,7 +444,7 @@ function init(description) {
event.preventDefault();
const links = [];
const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
[...linkCheckedCheckboxes].forEach((checkbox) => {
links.push({
id: checkbox.value,
@ -466,6 +466,25 @@ function init(description) {
});
}
/**
* Select all button
*/
const selectAllButtons = document.querySelectorAll('.select-all-button');
[...selectAllButtons].forEach((selectAllButton) => {
selectAllButton.addEventListener('click', (e) => {
e.preventDefault();
const checked = selectAllButton.classList.contains('filter-off');
[...selectAllButtons].forEach((selectAllButton2) => {
selectAllButton2.classList.toggle('filter-off');
selectAllButton2.classList.toggle('filter-on');
});
[...linkCheckboxes].forEach((linkCheckbox) => {
linkCheckbox.checked = checked;
linkCheckbox.dispatchEvent(new Event('change'));
});
});
});
/**
* Tag list operations
*
@ -548,7 +567,7 @@ function init(description) {
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const tag = block.getAttribute('data-tag');
const refreshedToken = document.getElementById('token');
const refreshedToken = document.getElementById('token').value;
if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
const xhr = new XMLHttpRequest();

View file

@ -381,8 +381,6 @@ body,
box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset;
background: $almost-white;
padding: 5px 5px 3px 15px;
width: 20%;
height: 20px;
color: $dark-grey;
}
@ -742,7 +740,7 @@ body,
font-size: 1em;
}
.delete-checkbox {
.link-checkbox {
display: none;
}
}
@ -757,6 +755,14 @@ body,
font-size: 1.3em;
}
.pin-link {
font-size: 1.3em;
}
.pinned-link {
color: $blue !important;
}
.linklist-item-description {
position: relative;
padding: 0 10px;
@ -850,6 +856,10 @@ body,
margin: 0 7px;
}
.ctrl-delete {
margin: 0 7px 0 0;
}
// 64em -> lg
@media screen and (max-width: 64em) {
.linklist-item-infos-url {

View file

@ -16,7 +16,7 @@
},
"require": {
"php": ">=5.6",
"shaarli/netscape-bookmark-parser": "^2.0",
"shaarli/netscape-bookmark-parser": "^2.1",
"erusev/parsedown": "^1.6",
"slim/slim": "^3.0",
"arthurhoaro/web-thumbnailer": "^1.1",
@ -24,11 +24,9 @@
"gettext/gettext": "^4.4"
},
"require-dev": {
"phpmd/phpmd" : "@stable",
"phpunit/phpcov": "*",
"phpunit/phpunit": "^5.0",
"sebastian/phpcpd": "*",
"squizlabs/php_codesniffer": "2.*",
"phpunit/phpcov": "*"
"squizlabs/php_codesniffer": "2.*"
},
"autoload": {
"psr-4": {

495
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "da7a0c081b61d949154c5d2e5370cbab",
"content-hash": "3876b34296fedb365517b785af8384de",
"packages": [
{
"name": "arthurhoaro/web-thumbnailer",
@ -133,16 +133,16 @@
},
{
"name": "gettext/gettext",
"version": "v4.6.1",
"version": "v4.6.2",
"source": {