mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-22 09:01:01 +00:00
Merge remote-tracking branch 'origin/master' into 2.4
# Conflicts: # web/wallassets/baggy.js # web/wallassets/manifest.json # web/wallassets/material.css # web/wallassets/material.js
This commit is contained in:
commit
b878be4cc9
40 changed files with 802 additions and 136 deletions
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -8,9 +8,15 @@
|
|||
| Documentation | yes/no
|
||||
| Translation | yes/no
|
||||
| CHANGELOG.md | yes/no
|
||||
| Fixed tickets | #...
|
||||
| License | MIT
|
||||
|
||||
<!--
|
||||
Please list the issues your PR fixes using special keywords, see
|
||||
https://help.github.com/articles/closing-issues-using-keywords/
|
||||
|
||||
Fixes #…
|
||||
-->
|
||||
|
||||
<!--
|
||||
- Please fill in this template according to the PR you're about to submit.
|
||||
- Replace this comment by a description of what your PR is solving.
|
||||
|
|
27
.travis.yml
27
.travis.yml
|
@ -4,9 +4,6 @@ services:
|
|||
- rabbitmq
|
||||
- redis
|
||||
|
||||
# faster builds on docker-container setup
|
||||
sudo: false
|
||||
|
||||
# used for HHVM
|
||||
addons:
|
||||
apt:
|
||||
|
@ -51,6 +48,14 @@ branches:
|
|||
except:
|
||||
- legacy
|
||||
|
||||
before_install:
|
||||
- if [[ $TRAVIS_REPO_SLUG = wallabag/wallabag ]]; then cp .composer-auth.json ~/.composer/auth.json; fi;
|
||||
|
||||
install:
|
||||
- if [[ $ASSETS = build ]]; then source ~/.nvm/nvm.sh && nvm install 6.10; fi;
|
||||
- if [[ $ASSETS = build ]]; then npm install -g yarn@latest; fi;
|
||||
- if [[ $ASSETS = build ]]; then yarn install; fi;
|
||||
|
||||
before_script:
|
||||
- PHP=$TRAVIS_PHP_VERSION
|
||||
- if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
|
||||
|
@ -58,14 +63,14 @@ before_script:
|
|||
- if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
|
||||
- composer self-update --no-progress
|
||||
- if [[ $DB = pgsql ]]; then psql -c 'create database wallabag_test;' -U postgres; fi;
|
||||
|
||||
install:
|
||||
- if [[ $ASSETS = build ]]; then source ~/.nvm/nvm.sh && nvm install 6.10; fi;
|
||||
- if [[ $ASSETS = build ]]; then npm install -g yarn@latest; fi;
|
||||
- if [[ $ASSETS = build ]]; then yarn install; fi;
|
||||
|
||||
before_install:
|
||||
- if [[ $TRAVIS_REPO_SLUG = wallabag/wallabag ]]; then cp .composer-auth.json ~/.composer/auth.json; fi;
|
||||
# increase swap to avoid "proc_open(): fork failed - Cannot allocate memory"
|
||||
# this should be removed when no more PHP 5 build will be defined
|
||||
- sudo swapon -s
|
||||
- sudo fallocate -l 4G /swapfile
|
||||
- sudo chmod 600 /swapfile
|
||||
- sudo mkswap /swapfile
|
||||
- sudo swapon /swapfile
|
||||
- sudo swapon -s
|
||||
|
||||
script:
|
||||
- travis_wait bash composer install -o --no-interaction --no-progress --prefer-dist
|
||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -1,5 +1,42 @@
|
|||
# Changelog
|
||||
|
||||
## [2.3.4](https://github.com/wallabag/wallabag/tree/2.3.4)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.3.3...2.3.4)
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix image downloading on null image path [#3684](https://github.com/wallabag/wallabag/pull/3684)
|
||||
- Remove remaining deprecation notices [#3686](https://github.com/wallabag/wallabag/pull/3686)
|
||||
- Fix mobile viewport on big iframe and video elements [#3683](https://github.com/wallabag/wallabag/pull/3683)
|
||||
- Autofocus the username field on the login page [#3691](https://github.com/wallabag/wallabag/pull/3691)
|
||||
- Feature/svg logo [#3692](https://github.com/wallabag/wallabag/pull/3692)
|
||||
- Fixes a typo [#3702](https://github.com/wallabag/wallabag/pull/3702)
|
||||
- Update release script [#3705](https://github.com/wallabag/wallabag/pull/3705)
|
||||
- Removing failing test from Travis [#3707](https://github.com/wallabag/wallabag/pull/3707)
|
||||
- Replace SO url by lemonde.fr to avoid random failing test [#3685](https://github.com/wallabag/wallabag/pull/3685)
|
||||
- php-cs-fixer: native_function_invocation [#3716](https://github.com/wallabag/wallabag/pull/3716)
|
||||
- PHP 7.2 shouldn't fail [#3717](https://github.com/wallabag/wallabag/pull/3717)
|
||||
- Liberation goes https [#3726](https://github.com/wallabag/wallabag/pull/3726)
|
||||
- Bugfix: Sanitize the title of a saved webpage from invalid UTF-8 characters. [#3725](https://github.com/wallabag/wallabag/pull/3725)
|
||||
- Fix dockerfile php72 [#3734](https://github.com/wallabag/wallabag/pull/3734)
|
||||
- Fix sort parameters [#3719](https://github.com/wallabag/wallabag/pull/3719)
|
||||
- Add note on GitHub PR template to auto-close issues [#3763](https://github.com/wallabag/wallabag/pull/3763)
|
||||
- Fix link to wallabag requirements in documentation [#3766](https://github.com/wallabag/wallabag/pull/3766)
|
||||
- Update translation when marking as read [#3772](https://github.com/wallabag/wallabag/pull/3772)
|
||||
- Makefile fixes for non GNU systems [#3706](https://github.com/wallabag/wallabag/pull/3706)
|
||||
- Card no preview replaced by wallabag logo [#3774](https://github.com/wallabag/wallabag/pull/3774)
|
||||
|
||||
### Changes
|
||||
|
||||
- Propose YunoHost badge for installing [#3678](https://github.com/wallabag/wallabag/pull/3678)
|
||||
- More robust srcset image attribute handling [#3690](https://github.com/wallabag/wallabag/pull/3690)
|
||||
- Rename getBuilderByUser and refactor query for untagged entries [#3712](https://github.com/wallabag/wallabag/pull/3712)
|
||||
- Show tags on non-image gallery preview card [#3743](https://github.com/wallabag/wallabag/pull/3743)
|
||||
- add manifest.json for android pwa [#3606](https://github.com/wallabag/wallabag/pull/3606)
|
||||
- Add placeholder image to card-based gallery entries page [#3745](https://github.com/wallabag/wallabag/pull/3745)
|
||||
- Abort running install and update script if root [#3733](https://github.com/wallabag/wallabag/pull/3733)
|
||||
- Swap entry url with origin url if graby provides an updated one [#3553](https://github.com/wallabag/wallabag/pull/3553)
|
||||
|
||||
## [2.3.3](https://github.com/wallabag/wallabag/tree/2.3.3)
|
||||
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.3.2...2.3.3)
|
||||
|
||||
|
|
53
GNUmakefile
Executable file
53
GNUmakefile
Executable file
|
@ -0,0 +1,53 @@
|
|||
SHELL=bash
|
||||
TMP_FOLDER=/tmp
|
||||
RELEASE_FOLDER=wllbg-release
|
||||
|
||||
ENV ?= prod
|
||||
|
||||
help: ## Display this help menu
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
clean: ## Clear the application cache
|
||||
rm -rf var/cache/*
|
||||
|
||||
install: ## Install wallabag with the latest version
|
||||
@./scripts/install.sh $(ENV)
|
||||
|
||||
update: ## Update the wallabag installation to the latest version
|
||||
@./scripts/update.sh $(ENV)
|
||||
|
||||
dev: ## Install the latest dev version
|
||||
@./scripts/dev.sh
|
||||
|
||||
run: ## Run the wallabag built-in server
|
||||
@php bin/console server:run --env=dev
|
||||
|
||||
build: ## Run webpack
|
||||
@npm run build:$(ENV)
|
||||
|
||||
prepare: clean ## Prepare database for testsuite
|
||||
ifdef DB
|
||||
cp app/config/tests/parameters_test.$(DB).yml app/config/parameters_test.yml
|
||||
endif
|
||||
-php bin/console doctrine:database:drop --force --env=test
|
||||
php bin/console doctrine:database:create --env=test
|
||||
php bin/console doctrine:migrations:migrate --no-interaction --env=test
|
||||
|
||||
fixtures: ## Load fixtures into database
|
||||
php bin/console doctrine:fixtures:load --no-interaction --env=test
|
||||
|
||||
test: prepare fixtures ## Launch wallabag testsuite
|
||||
bin/simple-phpunit -v
|
||||
|
||||
release: ## Create a package. Need a VERSION parameter (eg: `make release VERSION=master`).
|
||||
ifndef VERSION
|
||||
$(error VERSION is not set)
|
||||
endif
|
||||
@./scripts/release.sh $(VERSION) $(TMP_FOLDER) $(RELEASE_FOLDER) $(ENV)
|
||||
|
||||
deploy: ## Deploy wallabag
|
||||
@bundle exec cap staging deploy
|
||||
|
||||
.PHONY: help clean prepare install fixtures update build test release deploy run dev
|
||||
|
||||
.DEFAULT_GOAL := install
|
54
Makefile
Executable file → Normal file
54
Makefile
Executable file → Normal file
|
@ -1,52 +1,2 @@
|
|||
TMP_FOLDER=/tmp
|
||||
RELEASE_FOLDER=wllbg-release
|
||||
|
||||
ENV ?= prod
|
||||
|
||||
help: ## Display this help menu
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
clean: ## Clear the application cache
|
||||
rm -rf var/cache/*
|
||||
|
||||
install: ## Install wallabag with the latest version
|
||||
@sh scripts/install.sh $(ENV)
|
||||
|
||||
update: ## Update the wallabag installation to the latest version
|
||||
@sh scripts/update.sh $(ENV)
|
||||
|
||||
dev: ## Install the latest dev version
|
||||
@sh scripts/dev.sh
|
||||
|
||||
run: ## Run the wallabag built-in server
|
||||
@php bin/console server:run --env=dev
|
||||
|
||||
build: ## Run webpack
|
||||
@npm run build:$(ENV)
|
||||
|
||||
prepare: clean ## Prepare database for testsuite
|
||||
ifdef DB
|
||||
cp app/config/tests/parameters_test.$(DB).yml app/config/parameters_test.yml
|
||||
endif
|
||||
-php bin/console doctrine:database:drop --force --env=test
|
||||
php bin/console doctrine:database:create --env=test
|
||||
php bin/console doctrine:migrations:migrate --no-interaction --env=test
|
||||
|
||||
fixtures: ## Load fixtures into database
|
||||
php bin/console doctrine:fixtures:load --no-interaction --env=test
|
||||
|
||||
test: prepare fixtures ## Launch wallabag testsuite
|
||||
bin/simple-phpunit -v
|
||||
|
||||
release: ## Create a package. Need a VERSION parameter (eg: `make release VERSION=master`).
|
||||
ifndef VERSION
|
||||
$(error VERSION is not set)
|
||||
endif
|
||||
@sh scripts/release.sh $(VERSION) $(TMP_FOLDER) $(RELEASE_FOLDER) $(ENV)
|
||||
|
||||
deploy: ## Deploy wallabag
|
||||
@bundle exec cap staging deploy
|
||||
|
||||
.PHONY: help clean prepare install fixtures update build test release deploy run dev
|
||||
|
||||
.DEFAULT_GOAL := install
|
||||
.DEFAULT:
|
||||
gmake $@
|
||||
|
|
|
@ -13,7 +13,7 @@ If you do not have your own server, consider [the wallabag.it hosting solution](
|
|||
![wallabag logo](https://raw.githubusercontent.com/wallabag/logo/master/_default/typo-horizontal/png/sm/logo-typo-horizontal-black-no-bg-no-border-sm.png)
|
||||
|
||||
# Install wallabag
|
||||
Please read [the documentation to see the wallabag requirements](http://doc.wallabag.org/en/master/user/installation.html#requirements).
|
||||
Please read [the documentation to see the wallabag requirements](https://doc.wallabag.org/en/admin/installation/requirements.html).
|
||||
|
||||
Then you can install wallabag by executing the following commands:
|
||||
|
||||
|
|
|
@ -118,9 +118,14 @@ main {
|
|||
|
||||
.card-image .preview,
|
||||
.card-fullimage .preview {
|
||||
height: 14em;
|
||||
height: 100%;
|
||||
background: no-repeat 50%/cover;
|
||||
background-color: #efefef;
|
||||
display: block;
|
||||
|
||||
&--default {
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&.sw {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
wallabag_core:
|
||||
version: 2.3.3
|
||||
version: 2.3.4
|
||||
paypal_url: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9UBA65LG3FX9Y&lc=gb"
|
||||
languages:
|
||||
en: 'English'
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
"require-dev": {
|
||||
"doctrine/doctrine-fixtures-bundle": "~3.0",
|
||||
"sensio/generator-bundle": "^3.0",
|
||||
"symfony/phpunit-bridge": "^3.3",
|
||||
"symfony/phpunit-bridge": "3.4.x-dev",
|
||||
"friendsofphp/php-cs-fixer": "~2.0",
|
||||
"m6web/redis-mock": "^2.0",
|
||||
"dama/doctrine-test-bundle": "^4.0"
|
||||
|
|
|
@ -4,10 +4,10 @@ FROM php:fpm
|
|||
ARG timezone='Europe/Paris'
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libmcrypt-dev libicu-dev libpq-dev libxml2-dev libpng12-dev libjpeg-dev \
|
||||
libmcrypt-dev libicu-dev libpq-dev libxml2-dev libpng-dev libjpeg-dev \
|
||||
&& /usr/local/bin/docker-php-ext-configure gd --with-jpeg-dir=/usr/include \
|
||||
&& docker-php-ext-install \
|
||||
iconv mcrypt mbstring intl pdo pdo_mysql pdo_pgsql gd
|
||||
iconv mbstring intl pdo pdo_mysql pdo_pgsql gd
|
||||
|
||||
RUN echo "date.timezone="$timezone > /usr/local/etc/php/conf.d/date_timezone.ini
|
||||
|
||||
|
|
0
scripts/dev.sh
Normal file → Executable file
0
scripts/dev.sh
Normal file → Executable file
14
scripts/install.sh
Normal file → Executable file
14
scripts/install.sh
Normal file → Executable file
|
@ -2,6 +2,20 @@
|
|||
# You can execute this file to install wallabag
|
||||
# eg: `sh install.sh prod`
|
||||
|
||||
IGNORE_ROOT_ARG="--ignore-root-warning"
|
||||
IGNORE_ROOT=0
|
||||
|
||||
if [ "$1" == "$IGNORE_ROOT_ARG" ]; then
|
||||
IGNORE_ROOT=1
|
||||
fi
|
||||
|
||||
# Abort running this script if root
|
||||
if [ "$IGNORE_ROOT" -eq 0 ] && [ "$EUID" == "0" ]; then
|
||||
echo "Do not run this script as root!" >&2
|
||||
echo "Use $IGNORE_ROOT_ARG to ignore this error." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMPOSER_COMMAND='composer'
|
||||
|
||||
DIR="${BASH_SOURCE}"
|
||||
|
|
0
scripts/release.sh
Normal file → Executable file
0
scripts/release.sh
Normal file → Executable file
0
scripts/require.sh
Normal file → Executable file
0
scripts/require.sh
Normal file → Executable file
14
scripts/update.sh
Normal file → Executable file
14
scripts/update.sh
Normal file → Executable file
|
@ -2,6 +2,20 @@
|
|||
# You can execute this file to update wallabag
|
||||
# eg: `sh update.sh prod`
|
||||
|
||||
IGNORE_ROOT_ARG="--ignore-root-warning"
|
||||
IGNORE_ROOT=0
|
||||
|
||||
if [ "$1" == "$IGNORE_ROOT_ARG" ]; then
|
||||
IGNORE_ROOT=1
|
||||
fi
|
||||
|
||||
# Abort running this script if root
|
||||
if [ "$IGNORE_ROOT" -eq 0 ] && [ "$EUID" == "0" ]; then
|
||||
echo "Do not run this script as root!" >&2
|
||||
echo "Use $IGNORE_ROOT_ARG to ignore this error." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ class ContentProxy
|
|||
|
||||
if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
|
||||
$fetchedContent = $this->graby->fetchContent($url);
|
||||
$fetchedContent['title'] = $this->sanitizeContentTitle($fetchedContent['title'], $fetchedContent['content_type']);
|
||||
|
||||
// when content is imported, we have information in $content
|
||||
// in case fetching content goes bad, we'll keep the imported information instead of overriding them
|
||||
|
@ -65,6 +66,13 @@ class ContentProxy
|
|||
// so we'll be able to refetch it in the future
|
||||
$content['url'] = !empty($content['url']) ? $content['url'] : $url;
|
||||
|
||||
// In one case (at least in tests), url is empty here
|
||||
// so we set it using $url provided in the updateEntry call.
|
||||
// Not sure what are the other possible cases where this property is empty
|
||||
if (empty($entry->getUrl()) && !empty($url)) {
|
||||
$entry->setUrl($url);
|
||||
}
|
||||
|
||||
$this->stockEntry($entry, $content);
|
||||
}
|
||||
|
||||
|
@ -176,6 +184,59 @@ class ContentProxy
|
|||
$entry->setTitle($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character.
|
||||
*
|
||||
* @param $title
|
||||
* @param $contentType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sanitizeContentTitle($title, $contentType)
|
||||
{
|
||||
if ('application/pdf' === $contentType) {
|
||||
$title = $this->convertPdfEncodingToUTF8($title);
|
||||
}
|
||||
|
||||
return $this->sanitizeUTF8Text($title);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the title from the fetched content comes from a PDF, then its very possible that the character encoding is not
|
||||
* UTF-8. This methods tries to identify the character encoding and translate the title to UTF-8.
|
||||
*
|
||||
* @param $title
|
||||
*
|
||||
* @return string (maybe contains invalid UTF-8 character)
|
||||
*/
|
||||
private function convertPdfEncodingToUTF8($title)
|
||||
{
|
||||
// first try UTF-8 because its easier to detect its present/absence
|
||||
foreach (['UTF-8', 'UTF-16BE', 'WINDOWS-1252'] as $encoding) {
|
||||
if (mb_check_encoding($title, $encoding)) {
|
||||
return mb_convert_encoding($title, 'UTF-8', $encoding);
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove invalid UTF-8 characters from the given string.
|
||||
*
|
||||
* @param string $rawText
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sanitizeUTF8Text($rawText)
|
||||
{
|
||||
if (mb_check_encoding($rawText, 'UTF-8')) {
|
||||
return $rawText;
|
||||
}
|
||||
|
||||
return iconv('UTF-8', 'UTF-8//IGNORE', $rawText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stock entry with fetched or imported content.
|
||||
* Will fall back to OpenGraph data if available.
|
||||
|
@ -185,7 +246,7 @@ class ContentProxy
|
|||
*/
|
||||
private function stockEntry(Entry $entry, array $content)
|
||||
{
|
||||
$entry->setUrl($content['url']);
|
||||
$this->updateOriginUrl($entry, $content['url']);
|
||||
|
||||
$this->setEntryDomainName($entry);
|
||||
|
||||
|
@ -251,6 +312,115 @@ class ContentProxy
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the origin_url field when a redirection occurs
|
||||
* This field is set if it is empty and new url does not match ignore list.
|
||||
*
|
||||
* @param Entry $entry
|
||||
* @param string $url
|
||||
*/
|
||||
private function updateOriginUrl(Entry $entry, $url)
|
||||
{
|
||||
if (empty($url) || $entry->getUrl() === $url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parsed_entry_url = parse_url($entry->getUrl());
|
||||
$parsed_content_url = parse_url($url);
|
||||
|
||||
/**
|
||||
* The following part computes the list of part changes between two
|
||||
* parse_url arrays.
|
||||
*
|
||||
* As array_diff_assoc only computes changes to go from the left array
|
||||
* to the right one, we make two differents arrays to have both
|
||||
* directions. We merge these two arrays and sort keys before passing
|
||||
* the result to the switch.
|
||||
*
|
||||
* The resulting array gives us all changing parts between the two
|
||||
* urls: scheme, host, path, query and/or fragment.
|
||||
*/
|
||||
$diff_ec = array_diff_assoc($parsed_entry_url, $parsed_content_url);
|
||||
$diff_ce = array_diff_assoc($parsed_content_url, $parsed_entry_url);
|
||||
|
||||
$diff = array_merge($diff_ec, $diff_ce);
|
||||
$diff_keys = array_keys($diff);
|
||||
sort($diff_keys);
|
||||
|
||||
if ($this->ignoreUrl($entry->getUrl())) {
|
||||
$entry->setUrl($url);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This switch case lets us apply different behaviors according to
|
||||
* changing parts of urls.
|
||||
*
|
||||
* As $diff_keys is an array, we provide arrays as cases. ['path'] means
|
||||
* 'only the path is different between the two urls' whereas
|
||||
* ['fragment', 'query'] means 'only fragment and query string parts are
|
||||
* different between the two urls'.
|
||||
*
|
||||
* Note that values in $diff_keys are sorted.
|
||||
*/
|
||||
switch ($diff_keys) {
|
||||
case ['path']:
|
||||
if (($parsed_entry_url['path'] . '/' === $parsed_content_url['path']) // diff is trailing slash, we only replace the url of the entry
|
||||
|| ($url === urldecode($entry->getUrl()))) { // we update entry url if new url is a decoded version of it, see EntryRepository#findByUrlAndUserId
|
||||
$entry->setUrl($url);
|
||||
}
|
||||
break;
|
||||
case ['scheme']:
|
||||
$entry->setUrl($url);
|
||||
break;
|
||||
case ['fragment']:
|
||||
// noop
|
||||
break;
|
||||
default:
|
||||
if (empty($entry->getOriginUrl())) {
|
||||
$entry->setOriginUrl($entry->getUrl());
|
||||
}
|
||||
$entry->setUrl($url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check entry url against an ignore list to replace with content url.
|
||||
*
|
||||
* XXX: move the ignore list in the database to let users handle it
|
||||
*
|
||||
* @param string $url url to test
|
||||
*
|
||||
* @return bool true if url matches ignore list otherwise false
|
||||
*/
|
||||
private function ignoreUrl($url)
|
||||
{
|
||||
$ignored_hosts = ['feedproxy.google.com', 'feeds.reuters.com'];
|
||||
$ignored_patterns = ['https?://www\.lemonde\.fr/tiny.*'];
|
||||
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
$filtered = array_filter($ignored_hosts, function ($var) use ($parsed_url) {
|
||||
return $var === $parsed_url['host'];
|
||||
});
|
||||
|
||||
if ([] !== $filtered) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$filtered = array_filter($ignored_patterns, function ($var) use ($url) {
|
||||
return preg_match("`$var`i", $url);
|
||||
});
|
||||
|
||||
if ([] !== $filtered) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given content has at least a title, an html and a url.
|
||||
*
|
||||
|
|
|
@ -157,8 +157,8 @@ config:
|
|||
# not_equal_to: 'Not equal to...'
|
||||
# or: 'One rule OR another'
|
||||
# and: 'One rule AND another'
|
||||
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
# default_title: 'Title of the entry'
|
||||
|
|
|
@ -72,9 +72,9 @@ config:
|
|||
300_word: 'I read ~300 words per minute'
|
||||
400_word: 'I read ~400 words per minute'
|
||||
action_mark_as_read:
|
||||
label: 'Where do you want to be redirected to after marking an article as read?'
|
||||
redirect_homepage: 'To the homepage'
|
||||
redirect_current_page: 'To the current page'
|
||||
label: 'What to do after removing, starring or marking as read an article?'
|
||||
redirect_homepage: 'Go to the homepage'
|
||||
redirect_current_page: 'Stay on the current page'
|
||||
pocket_consumer_key_label: Consumer key for Pocket to import contents
|
||||
android_configuration: Configure your Android application
|
||||
android_instruction: "Touch here to prefill your Android application"
|
||||
|
@ -157,8 +157,8 @@ config:
|
|||
not_equal_to: 'Not equal to...'
|
||||
or: 'One rule OR another'
|
||||
and: 'One rule AND another'
|
||||
matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
default_title: 'Title of the entry'
|
||||
|
|
|
@ -158,7 +158,7 @@ config:
|
|||
or: 'Una regla U otra'
|
||||
and: 'Una regla Y la otra'
|
||||
matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
default_title: 'Título del artículo'
|
||||
|
|
|
@ -157,8 +157,8 @@ config:
|
|||
# not_equal_to: 'Not equal to...'
|
||||
# or: 'One rule OR another'
|
||||
# and: 'One rule AND another'
|
||||
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
# default_title: 'Title of the entry'
|
||||
|
|
|
@ -72,9 +72,9 @@ config:
|
|||
300_word: "Je lis environ 300 mots par minute"
|
||||
400_word: "Je lis environ 400 mots par minute"
|
||||
action_mark_as_read:
|
||||
label: "Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?"
|
||||
redirect_homepage: "À la page d’accueil"
|
||||
redirect_current_page: "À la page courante"
|
||||
label: "Que faire lorsqu'un article est supprimé, marqué comme lu ou marqué comme favoris ?"
|
||||
redirect_homepage: "Retourner à la page d’accueil"
|
||||
redirect_current_page: "Rester sur la page actuelle"
|
||||
pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données"
|
||||
android_configuration: "Configurez votre application Android"
|
||||
android_instruction: "Appuyez ici pour préremplir votre application Android"
|
||||
|
|
|
@ -158,7 +158,7 @@ config:
|
|||
or: "Una regola O un'altra"
|
||||
and: "Una regola E un'altra"
|
||||
matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
default_title: "Titolo del contenuto"
|
||||
|
|
|
@ -158,7 +158,7 @@ config:
|
|||
or: 'Uma regra OU outra'
|
||||
and: 'Uma regra E outra'
|
||||
matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
default_title: 'Título da entrada'
|
||||
|
|
|
@ -157,8 +157,8 @@ config:
|
|||
# not_equal_to: 'Not equal to...'
|
||||
# or: 'One rule OR another'
|
||||
# and: 'One rule AND another'
|
||||
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
# default_title: 'Title of the entry'
|
||||
|
|
|
@ -157,8 +157,8 @@ config:
|
|||
not_equal_to: 'Eşit değildir…'
|
||||
or: 'Bir kural veya birbaşkası'
|
||||
and: 'Bir kural ve diğeri'
|
||||
# matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
|
||||
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
|
||||
|
||||
entry:
|
||||
default_title: 'Makalenin başlığı'
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('wallassets/themes/_global/img/appicon/favicon.ico') }}">
|
||||
|
||||
<link rel="manifest" href="{{ asset('manifest.json') }}">
|
||||
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</div>
|
||||
|
||||
<ul class="tools links">
|
||||
<li><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.list.original_article'|trans }} : {{ entry.title|e }}"><span>{{ entry.domainName|removeWww }}</span></a></li>
|
||||
<li><a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.list.original_article'|trans }} : {{ entry.title|e }}"><span>{{ entry.domainName|removeWww }}</span></a></li>
|
||||
<li><a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool icon {% if entry.isArchived == 0 %}archive-off{% else %}archive{% endif %}" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">check</i><span>{{ 'entry.list.toogle_as_read'|trans }}</span></a></li>
|
||||
<li><a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool icon {% if entry.isStarred == 0 %}fav-off{% else %}fav{% endif %}" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">star_rate</i><span>{{ 'entry.list.toogle_as_star'|trans }}</span></a></li>
|
||||
<li><a title="{{ 'entry.list.delete'|trans }}" class="tool icon" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">delete</i><span>{{ 'entry.list.delete'|trans }}</span></a></li>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div id="article_toolbar">
|
||||
<ul class="links">
|
||||
<li class="topPosF"><a href="#top" title="{{ 'entry.view.left_menu.back_to_top'|trans }}" class="tool top icon icon-arrow-up-thick"><span>{{ 'entry.view.left_menu.set_as_read'|trans }}</span></a></li>
|
||||
<li><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e }}" class="tool link icon icon-link original"><span>{{ entry.domainName|removeWww }}</span></a></li>
|
||||
<li><a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e }}" class="tool link icon icon-link original"><span>{{ entry.domainName|removeWww }}</span></a></li>
|
||||
<li><a title="{{ 'entry.view.left_menu.re_fetch_content'|trans }}" class="tool icon icon-reload" href="{{ path('reload_entry', { 'id': entry.id }) }}"><span>{{ 'entry.view.left_menu.re_fetch_content'|trans }}</span></a></li>
|
||||
|
||||
{% set markAsReadLabel = 'entry.view.left_menu.set_as_unread' %}
|
||||
|
@ -27,13 +27,13 @@
|
|||
<li><a href="{{ path('share', {'id': entry.id }) }}" target="_blank" class="tool icon icon-eye" title="{{ 'entry.view.left_menu.public_link'|trans }}"><span>{{ 'entry.view.left_menu.public_link'|trans }}</span></a></li>
|
||||
<li><a href="{{ path('delete_share', {'id': entry.id }) }}" class="tool icon icon-no-eye" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}"><span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span></a></li>
|
||||
{% endif %}
|
||||
{% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_mail') %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="Email"><span>Email</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&tags={{ entry.tags|join(',')|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_scuttle') %}<li><a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&address={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&tags={{ entry.tags|join(',')|url_encode }}" target="_blank" class="tool icon-image icon-image--scuttle" title="scuttle"><span>scuttle</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_diaspora') %}<li><a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}¬es=&v=1&noui=1&jump=doclose" target="_blank" class="tool diaspora icon-image icon-image--diaspora" title="diaspora"><span>diaspora</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_unmark') %}<li><a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&title={{entry.title|url_encode}}&v=6" target="_blank" class="tool unmark icon-image icon-image--unmark" title="unmark"><span>unmark.it</span></a></li>{% endif %}
|
||||
{% if craue_setting('carrot') %}<li><a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" class="tool carrot icon-image icon-image--carrot" target="_blank" title="carrot"><span>Carrot</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&tags={{ entry.tags|join(',')|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" rel="noopener" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_scuttle') %}<li><a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&address={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&tags={{ entry.tags|join(',')|url_encode }}" target="_blank" rel="noopener" class="tool icon-image icon-image--scuttle" title="scuttle"><span>scuttle</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_diaspora') %}<li><a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}¬es=&v=1&noui=1&jump=doclose" target="_blank" rel="noopener" class="tool diaspora icon-image icon-image--diaspora" title="diaspora"><span>diaspora</span></a></li>{% endif %}
|
||||
{% if craue_setting('share_unmark') %}<li><a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&title={{entry.title|url_encode}}&v=6" target="_blank" rel="noopener" class="tool unmark icon-image icon-image--unmark" title="unmark"><span>unmark.it</span></a></li>{% endif %}
|
||||
{% if craue_setting('carrot') %}<li><a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" class="tool carrot icon-image icon-image--carrot" target="_blank" rel="noopener" title="carrot"><span>Carrot</span></a></li>{% endif %}
|
||||
{% if craue_setting('show_printlink') %}<li><a title="{{ 'entry.view.left_menu.print'|trans }}" class="tool icon icon-print" href="javascript: window.print();"><span>{{ 'entry.view.left_menu.print'|trans }}</span></a></li>{% endif %}
|
||||
{% if craue_setting('export_epub') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'epub' }) }}" title="Generate ePub file">EPUB</a></li>{% endif %}
|
||||
{% if craue_setting('export_mobi') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'mobi' }) }}" title="Generate Mobi file">MOBI</a></li>{% endif %}
|
||||
|
@ -74,7 +74,7 @@
|
|||
|
||||
{% if entry.originUrl is not empty %}
|
||||
<i class="material-icons" title="{{ 'entry.view.provided_by'|trans }}">launch</i>
|
||||
<a href="{{ entry.originUrl|e }}" target="_blank" class="tool">
|
||||
<a href="{{ entry.originUrl|e }}" target="_blank" rel="noopener" class="tool">
|
||||
{{ entry.originUrl|striptags|removeSchemeAndWww|truncate(32) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<body>
|
||||
<header class="block">
|
||||
<h1>{{ entry.title|e|raw }}</h1>
|
||||
<a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a>
|
||||
<a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a>
|
||||
<p class="shared-by">{{ "entry.public.shared_by_wallabag"|trans({'%wallabag_instance%': url('homepage'), '%username%': entry.user.username})|raw }}.</p>
|
||||
{% if entry.previewPicture is not null %}
|
||||
<img class="preview" src="{{ entry.previewPicture }}" alt="{{ entry.title|striptags|e('html_attr') }}" />
|
||||
|
|
|
@ -28,18 +28,18 @@
|
|||
<div class="col s12">
|
||||
<h5>{{ 'howto.top_menu.browser_addons'|trans }}</h5>
|
||||
<ul>
|
||||
<li><a href="{{ addonsUrl.firefox }}" target="_blank">{{ 'howto.browser_addons.firefox'|trans }}</a></li>
|
||||
<li><a href="{{ addonsUrl.chrome }}" target="_blank">{{ 'howto.browser_addons.chrome'|trans }}</a></li>
|
||||
<li><a href="{{ addonsUrl.opera }}" target="_blank">{{ 'howto.browser_addons.opera'|trans }}</a></li>
|
||||
<li><a href="{{ addonsUrl.firefox }}" target="_blank" rel="noopener">{{ 'howto.browser_addons.firefox'|trans }}</a></li>
|
||||
<li><a href="{{ addonsUrl.chrome }}" target="_blank" rel="noopener">{{ 'howto.browser_addons.chrome'|trans }}</a></li>
|
||||
<li><a href="{{ addonsUrl.opera }}" target="_blank" rel="noopener">{{ 'howto.browser_addons.opera'|trans }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<h5>{{ 'howto.top_menu.mobile_apps'|trans }}</h5>
|
||||
<ul>
|
||||
<li>Android: <a href="{{ addonsUrl.f_droid }}" target="_blank">{{ 'howto.mobile_apps.android.via_f_droid'|trans }}</a> / <a href="{{ addonsUrl.google_play }}" target="_blank">{{ 'howto.mobile_apps.android.via_google_play'|trans }}</a></li>
|
||||
<li>Android: <a href="{{ addonsUrl.f_droid }}" target="_blank" rel="noopener">{{ 'howto.mobile_apps.android.via_f_droid'|trans }}</a> / <a href="{{ addonsUrl.google_play }}" target="_blank" rel="noopener">{{ 'howto.mobile_apps.android.via_google_play'|trans }}</a></li>
|
||||
<li>iOS: <a href="{{ addonsUrl.ios }}" target="_blank">{{ 'howto.mobile_apps.ios'|trans }}</a></li>
|
||||
<li>Windows Phone: <a href="{{ addonsUrl.windows }}" target="_blank">{{ 'howto.mobile_apps.windows'|trans }}</a></li>
|
||||
<li>Windows Phone: <a href="{{ addonsUrl.windows }}" target="_blank" rel="noopener">{{ 'howto.mobile_apps.windows'|trans }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry} only %}
|
||||
</div>
|
||||
|
||||
{% include "@WallabagCore/themes/material/Entry/_card_actions.html.twig" with {'entry': entry} only %}
|
||||
</div>
|
|
@ -7,7 +7,8 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{{ path('view', { 'id': entry.id }) }}">
|
||||
<span class="preview" style="background-image: url({{ entry.previewPicture }})"></span>
|
||||
{% set previewClassModifier = entry.previewPicture ? '' : ' preview--default' %}
|
||||
<span class="preview{{ previewClassModifier }}" style="background-image: url({{ entry.previewPicture | default(asset('wallassets/themes/_global/img/logo-square.svg')) }})"></span>
|
||||
</a>
|
||||
</div>
|
||||
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withPreview': true} only %}
|
||||
|
|
|
@ -39,11 +39,9 @@
|
|||
<li id="entry-{{ entry.id|e }}" class="col {% if listMode == 0 %}l3 m6{% else %}collection-item{% endif %} s12">
|
||||
{% if listMode == 1 %}
|
||||
{% include "@WallabagCore/themes/material/Entry/_card_list.html.twig" with {'entry': entry} only %}
|
||||
{% elseif entry.previewPicture is null %}
|
||||
{% include "@WallabagCore/themes/material/Entry/_card_no_preview.html.twig" with {'entry': entry} only %}
|
||||
{% elseif not entry.previewPicture is null and entry.mimetype starts with 'image/' %}
|
||||
{% include "@WallabagCore/themes/material/Entry/_card_full_image.html.twig" with {'entry': entry} only %}
|
||||
{% elseif not entry.previewPicture is null %}
|
||||
{% else %}
|
||||
{% include "@WallabagCore/themes/material/Entry/_card_preview.html.twig" with {'entry': entry} only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</li>
|
||||
|
||||
<li class="bold border-bottom hide-on-med-and-down">
|
||||
<a class="waves-effect collapsible-header original" href="{{ entry.url|e }}" target="_blank">
|
||||
<a class="waves-effect collapsible-header original" href="{{ entry.url|e }}" target="_blank" rel="noopener">
|
||||
<i class="material-icons small">link</i>
|
||||
<span>{{ 'entry.view.left_menu.view_original_article'|trans }}</span>
|
||||
</a>
|
||||
|
@ -127,42 +127,42 @@
|
|||
{% endif %}
|
||||
{% if craue_setting('share_twitter') %}
|
||||
<li>
|
||||
<a href="https://twitter.com/home?status={{entry.title|striptags|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool icon-twitter" title="twitter">
|
||||
<a href="https://twitter.com/home?status={{entry.title|striptags|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool icon-twitter" title="twitter">
|
||||
<span>twitter</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if craue_setting('share_shaarli') %}
|
||||
<li>
|
||||
<a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&tags={{ entry.tags|join(',')|striptags|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" title="shaarli" class="tool icon-image shaarli">
|
||||
<a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&tags={{ entry.tags|join(',')|striptags|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" rel="noopener" title="shaarli" class="tool icon-image shaarli">
|
||||
<span>shaarli</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if craue_setting('share_scuttle') %}
|
||||
<li>
|
||||
<a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&address={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" title="scuttle" class="tool icon-image scuttle">
|
||||
<a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&address={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" rel="noopener" title="scuttle" class="tool icon-image scuttle">
|
||||
<span>scuttle</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if craue_setting('share_diaspora') %}
|
||||
<li>
|
||||
<a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&notes=&v=1&noui=1&jump=doclose" target="_blank" class="tool icon-image diaspora" title="diaspora">
|
||||
<a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}&notes=&v=1&noui=1&jump=doclose" target="_blank" rel="noopener" class="tool icon-image diaspora" title="diaspora">
|
||||
<span>diaspora*</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if craue_setting('share_unmark') %}
|
||||
<li>
|
||||
<a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&title={{entry.title|striptags|url_encode}}&v=6" target="_blank" class="tool icon-image unmark" title="unmark">
|
||||
<a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&title={{entry.title|striptags|url_encode}}&v=6" target="_blank" rel="noopener" class="tool icon-image unmark" title="unmark">
|
||||
<span>unmark.it</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if craue_setting('carrot') %}
|
||||
<li>
|
||||
<a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}" target="_blank" title="carrot" class="tool icon-image carrot">
|
||||
<a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|striptags|url_encode }}" target="_blank" rel="noopener" title="carrot" class="tool icon-image carrot">
|
||||
<span>Carrot</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -251,7 +251,7 @@
|
|||
{% endif %}
|
||||
<li>
|
||||
<i class="material-icons link">link</i>
|
||||
<a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
|
||||
<a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
|
||||
{{ entry.domainName|removeWww }}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -262,7 +262,7 @@
|
|||
{% if entry.originUrl is not empty %}
|
||||
<li>
|
||||
<i class="material-icons" title="{{ 'entry.view.provided_by'|trans }}">launch</i>
|
||||
<a href="{{ entry.originUrl|e }}" target="_blank" class="tool">
|
||||
<a href="{{ entry.originUrl|e }}" target="_blank" rel="noopener" class="tool">
|
||||
{{ entry.originUrl|striptags|removeSchemeAndWww|truncate(32) }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -143,7 +143,7 @@
|
|||
</div>
|
||||
<div class="col s12 l4">
|
||||
<p class="footer-text">
|
||||
{{ 'footer.wallabag.powered_by'|trans }} <a target="_blank" href="https://wallabag.org" class="grey-text text-lighten-4">wallabag</a> –
|
||||
{{ 'footer.wallabag.powered_by'|trans }} <a target="_blank" rel="noopener" href="https://wallabag.org" class="grey-text text-lighten-4">wallabag</a> –
|
||||
<a class="grey-text text-lighten-4" href="{{ path('about') }}">{{ 'footer.wallabag.about'|trans|lower }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -166,9 +166,8 @@ class EntryControllerTest extends WallabagCoreTestCase
|
|||
$this->assertSame($this->url, $content->getUrl());
|
||||
$this->assertContains('Google', $content->getTitle());
|
||||
$this->assertSame('fr', $content->getLanguage());
|
||||
$this->assertSame('2015-03-28 11:43:19', $content->getPublishedAt()->format('Y-m-d H:i:s'));
|
||||
$this->assertSame('Morgane Tual', $author[0]);
|
||||
$this->assertArrayHasKey('x-varnish1', $content->getHeaders());
|
||||
$this->assertSame('2016-04-07 19:01:35', $content->getPublishedAt()->format('Y-m-d H:i:s'));
|
||||
$this->assertArrayHasKey('x-frame-options', $content->getHeaders());
|
||||
$client->getContainer()->get('craue_config')->set('store_article_headers', 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -531,6 +531,377 @@ class ContentProxyTest extends TestCase
|
|||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
}
|
||||
|
||||
public function testWebsiteWithValidUTF8Title_doNothing()
|
||||
{
|
||||
// You can use https://www.online-toolz.com/tools/text-hex-convertor.php to convert UTF-8 text <=> hex
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
|
||||
$actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '7A');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'text/html',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
|
||||
$expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
public function testWebsiteWithInvalidUTF8Title_removeInvalidCharacter()
|
||||
{
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// 'a€b' (61;80;62) in hexadecimal and WINDOWS-1252 - but 80 is a invalid UTF-8 character.
|
||||
// The correct UTF-8 € character (U+20AC) is E282AC
|
||||
$actualTitle = $this->hexToStr('61' . '80' . '62');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'text/html',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// 'ab' (61;62) because all invalid UTF-8 character (like 80) are removed
|
||||
$expectedTitle = '61' . '62';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
public function testPdfWithUTF16BETitle_convertToUTF8()
|
||||
{
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '😻' (U+1F63B;D83DDE3B) in hexadecimal and as UTF16BE
|
||||
$actualTitle = $this->hexToStr('D83DDE3B');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'application/pdf',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '😻' (U+1F63B or F09F98BB) in hexadecimal and UTF-8
|
||||
$expectedTitle = 'F09F98BB';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
public function testPdfWithUTF8Title_doNothing()
|
||||
{
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '😻' (U+1F63B;D83DDE3B) in hexadecimal and as UTF8
|
||||
$actualTitle = $this->hexToStr('F09F98BB');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'application/pdf',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '😻' (U+1F63B or F09F98BB) in hexadecimal and UTF-8
|
||||
$expectedTitle = 'F09F98BB';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
public function testPdfWithWINDOWS1252Title_convertToUTF8()
|
||||
{
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '€' (80) in hexadecimal and WINDOWS-1252
|
||||
$actualTitle = $this->hexToStr('80');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'application/pdf',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '€' (U+20AC or E282AC) in hexadecimal and UTF-8
|
||||
$expectedTitle = 'E282AC';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
public function testPdfWithInvalidCharacterInTitle_removeInvalidCharacter()
|
||||
{
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '😻ℤ<F09F98BB>z' (U+1F63B or F09F98BB; U+2124 or E284A4; invalid character 81; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// 0x81 is not a valid character for UTF16, UTF8 and WINDOWS-1252
|
||||
$actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '81' . '7A');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$graby = $this->getMockBuilder('Graby\Graby')
|
||||
->setMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'content_type' => 'application/pdf',
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// the 0x81 (represented by <20>) is invalid for UTF16, UTF8 and WINDOWS-1252 and is removed
|
||||
$expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testWithChangedUrl.
|
||||
*
|
||||
* Arrays contain the following values:
|
||||
* $entry_url
|
||||
* $origin_url
|
||||
* $content_url
|
||||
* $expected_entry_url
|
||||
* $expected_origin_url
|
||||
* $expected_domain
|
||||
*/
|
||||
public function dataForChangedUrl()
|
||||
{
|
||||
return [
|
||||
'normal' => [
|
||||
'http://0.0.0.0',
|
||||
null,
|
||||
'http://1.1.1.1',
|
||||
'http://1.1.1.1',
|
||||
'http://0.0.0.0',
|
||||
'1.1.1.1',
|
||||
],
|
||||
'origin already set' => [
|
||||
'http://0.0.0.0',
|
||||
'http://hello',
|
||||
'http://1.1.1.1',
|
||||
'http://1.1.1.1',
|
||||
'http://hello',
|
||||
'1.1.1.1',
|
||||
],
|
||||
'trailing slash' => [
|
||||
'https://example.com/hello-world',
|
||||
null,
|
||||
'https://example.com/hello-world/',
|
||||
'https://example.com/hello-world/',
|
||||
null,
|
||||
'example.com',
|
||||
],
|
||||
'query string in fetched content' => [
|
||||
'https://example.org/hello',
|
||||
null,
|
||||
'https://example.org/hello?world=1',
|
||||
'https://example.org/hello?world=1',
|
||||
'https://example.org/hello',
|
||||
'example.org',
|
||||
],
|
||||
'fragment in fetched content' => [
|
||||
'https://example.org/hello',
|
||||
null,
|
||||
'https://example.org/hello#world',
|
||||
'https://example.org/hello',
|
||||
null,
|
||||
'example.org',
|
||||
],
|
||||
'fragment and query string in fetched content' => [
|
||||
'https://example.org/hello',
|
||||
null,
|
||||
'https://example.org/hello?foo#world',
|
||||
'https://example.org/hello?foo#world',
|
||||
'https://example.org/hello',
|
||||
'example.org',
|
||||
],
|
||||
'different path and query string in fetch content' => [
|
||||
'https://example.org/hello',
|
||||
null,
|
||||
'https://example.org/world?foo',
|
||||
'https://example.org/world?foo',
|
||||
'https://example.org/hello',
|
||||
'example.org',
|
||||
],
|
||||
'feedproxy ignore list test' => [
|
||||
'http://feedproxy.google.com/~r/Wallabag/~3/helloworld',
|
||||
null,
|
||||
'https://example.org/hello-wallabag',
|
||||
'https://example.org/hello-wallabag',
|
||||
null,
|
||||
'example.org',
|
||||
],
|
||||
'feedproxy ignore list test with origin url already set' => [
|
||||
'http://feedproxy.google.com/~r/Wallabag/~3/helloworld',
|
||||
'https://example.org/this-is-source',
|
||||
'https://example.org/hello-wallabag',
|
||||
'https://example.org/hello-wallabag',
|
||||
'https://example.org/this-is-source',
|
||||
'example.org',
|
||||
],
|
||||
'lemonde ignore pattern test' => [
|
||||
'http://www.lemonde.fr/tiny/url',
|
||||
null,
|
||||
'http://example.com/hello-world',
|
||||
'http://example.com/hello-world',
|
||||
null,
|
||||
'example.com',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataForChangedUrl
|
||||
*/
|
||||
public function testWithChangedUrl($entry_url, $origin_url, $content_url, $expected_entry_url, $expected_origin_url, $expected_domain)
|
||||
{
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage, true);
|
||||
$entry = new Entry(new User());
|
||||
$entry->setOriginUrl($origin_url);
|
||||
$proxy->updateEntry(
|
||||
$entry,
|
||||
$entry_url,
|
||||
[
|
||||
'html' => false,
|
||||
'title' => '',
|
||||
'url' => $content_url,
|
||||
'content_type' => '',
|
||||
'language' => '',
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
$this->assertSame($expected_entry_url, $entry->getUrl());
|
||||
$this->assertSame($expected_domain, $entry->getDomainName());
|
||||
$this->assertSame($expected_origin_url, $entry->getOriginUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/a/18506801.
|
||||
*
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function strToHex($string)
|
||||
{
|
||||
$hex = '';
|
||||
for ($i = 0; $i < \strlen($string); ++$i) {
|
||||
$ord = \ord($string[$i]);
|
||||
$hexCode = dechex($ord);
|
||||
$hex .= substr('0' . $hexCode, -2);
|
||||
}
|
||||
|
||||
return strtoupper($hex);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/a/18506801.
|
||||
*
|
||||
* @param $hex
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function hexToStr($hex)
|
||||
{
|
||||
$string = '';
|
||||
for ($i = 0; $i < \strlen($hex) - 1; $i += 2) {
|
||||
$string .= \chr(hexdec($hex[$i] . $hex[$i + 1]));
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function getTaggerMock()
|
||||
{
|
||||
return $this->getMockBuilder(RuleBasedTagger::class)
|
||||
|
|
48
web/manifest.json
Normal file
48
web/manifest.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"short_name": "wallabag",
|
||||
"name": "Save and classify articles. Read them later. Freely.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-57.png",
|
||||
"type": "image/png",
|
||||
"sizes": "57x57"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-72.png",
|
||||
"type": "image/png",
|
||||
"sizes": "72x72"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-76.png",
|
||||
"type": "image/png",
|
||||
"sizes": "76x76"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-114.png",
|
||||
"type": "image/png",
|
||||
"sizes": "114x114"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-120.png",
|
||||
"type": "image/png",
|
||||
"sizes": "120x120"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-144.png",
|
||||
"type": "image/png",
|
||||
"sizes": "144x144"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-152.png",
|
||||
"type": "image/png",
|
||||
"sizes": "152x152"
|
||||
},
|
||||
{
|
||||
"src": "wallassets/themes/_global/img/appicon/apple-touch-icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"background_color": "#FFFFFF"
|
||||
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Loading…
Reference in a new issue