aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.scrutinizer.yml15
-rw-r--r--.travis.yml1
-rw-r--r--app/DoctrineMigrations/Version20170719231144.php2
-rw-r--r--docker/php/Dockerfile4
-rw-r--r--package.json4
-rwxr-xr-xscripts/release.sh8
-rw-r--r--src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php2
-rw-r--r--src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php4
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php8
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php4
-rw-r--r--src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php8
-rw-r--r--src/Wallabag/CoreBundle/Command/ExportCommand.php2
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php8
-rw-r--r--src/Wallabag/CoreBundle/Command/ListUserCommand.php2
-rw-r--r--src/Wallabag/CoreBundle/Command/ReloadEntryCommand.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php4
-rw-r--r--src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/ContentProxy.php62
-rw-r--r--src/Wallabag/CoreBundle/Helper/CryptoProxy.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/DownloadImages.php4
-rw-r--r--src/Wallabag/CoreBundle/Helper/EntriesExport.php4
-rw-r--r--src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/Redirect.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/TagsAssigner.php4
-rw-r--r--src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php2
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php71
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_no_preview.html.twig7
-rw-r--r--src/Wallabag/CoreBundle/Tools/Utils.php2
-rw-r--r--src/Wallabag/CoreBundle/Twig/WallabagExtension.php10
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/BrowserController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/InstapaperController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/PinboardController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ReadabilityController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagController.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/InstapaperImport.php4
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php2
-rw-r--r--tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php4
-rw-r--r--tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php30
-rw-r--r--tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php17
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php4
-rw-r--r--tests/Wallabag/CoreBundle/Controller/TagControllerTest.php2
-rw-r--r--tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php244
-rw-r--r--tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php2
-rw-r--r--tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php2
-rw-r--r--tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php14
-rw-r--r--tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php2
-rw-r--r--tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php2
-rw-r--r--tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php2
-rw-r--r--tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php12
-rw-r--r--tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv2
-rw-r--r--tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json2
-rw-r--r--tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php2
-rw-r--r--var/SymfonyRequirements.php13
-rw-r--r--yarn.lock4
60 files changed, 497 insertions, 143 deletions
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
index dee92922..194297f6 100644
--- a/.scrutinizer.yml
+++ b/.scrutinizer.yml
@@ -26,3 +26,18 @@ tools:
26checks: 26checks:
27 php: 27 php:
28 code_rating: true 28 code_rating: true
29
30# use the new PHP analysis engine
31# https://scrutinizer-ci.com/docs/tools/php/php-analyzer/guides/migrate_to_new_php_analysis
32build:
33 nodes:
34 analysis:
35 tests:
36 override:
37 - php-scrutinizer-run
38
39 dependencies:
40 override:
41 - npm install -g 'yarn'
42 - yarn install --force
43 - COMPOSER_MEMORY_LIMIT=-1 composer install -o --no-interaction --no-progress --prefer-dist
diff --git a/.travis.yml b/.travis.yml
index c6c03dc3..eb409533 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -44,7 +44,6 @@ matrix:
44 - php: 7.0 44 - php: 7.0
45 env: CS_FIXER=run VALIDATE_TRANSLATION_FILE=run ASSETS=build DB=sqlite 45 env: CS_FIXER=run VALIDATE_TRANSLATION_FILE=run ASSETS=build DB=sqlite
46 allow_failures: 46 allow_failures:
47 - php: 7.2
48 - php: nightly 47 - php: nightly
49 48
50# exclude v1 branches 49# exclude v1 branches
diff --git a/app/DoctrineMigrations/Version20170719231144.php b/app/DoctrineMigrations/Version20170719231144.php
index 04358c7c..93fe7f26 100644
--- a/app/DoctrineMigrations/Version20170719231144.php
+++ b/app/DoctrineMigrations/Version20170719231144.php
@@ -55,7 +55,7 @@ class Version20170719231144 extends WallabagMigration
55 } 55 }
56 56
57 // Just in case... 57 // Just in case...
58 if (count($ids) > 0) { 58 if (\count($ids) > 0) {
59 // Merge tags 59 // Merge tags
60 $this->addSql(' 60 $this->addSql('
61 UPDATE ' . $this->getTable('entry_tag') . ' 61 UPDATE ' . $this->getTable('entry_tag') . '
diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
index 1fb1f298..d0266ec7 100644
--- a/docker/php/Dockerfile
+++ b/docker/php/Dockerfile
@@ -4,10 +4,10 @@ FROM php:fpm
4ARG timezone='Europe/Paris' 4ARG timezone='Europe/Paris'
5 5
6RUN apt-get update && apt-get install -y \ 6RUN apt-get update && apt-get install -y \
7 libmcrypt-dev libicu-dev libpq-dev libxml2-dev libpng12-dev libjpeg-dev \ 7 libmcrypt-dev libicu-dev libpq-dev libxml2-dev libpng-dev libjpeg-dev \
8 && /usr/local/bin/docker-php-ext-configure gd --with-jpeg-dir=/usr/include \ 8 && /usr/local/bin/docker-php-ext-configure gd --with-jpeg-dir=/usr/include \
9 && docker-php-ext-install \ 9 && docker-php-ext-install \
10 iconv mcrypt mbstring intl pdo pdo_mysql pdo_pgsql gd 10 iconv mbstring intl pdo pdo_mysql pdo_pgsql gd
11 11
12RUN echo "date.timezone="$timezone > /usr/local/etc/php/conf.d/date_timezone.ini 12RUN echo "date.timezone="$timezone > /usr/local/etc/php/conf.d/date_timezone.ini
13 13
diff --git a/package.json b/package.json
index 8d856bbb..ac894e79 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
1{ 1{
2 "name": "wallabag", 2 "name": "wallabag",
3 "version": "2.2.2", 3 "version": "2.3.3",
4 "description": "wallabag is a self hostable application for saving web pages", 4 "description": "wallabag is a self hostable application for saving web pages",
5 "private": true, 5 "private": true,
6 "directories": { 6 "directories": {
7 "doc": "docs" 7 "doc": "docs"
8 }, 8 },
9 "engines": { 9 "engines": {
10 "node": ">4.8" 10 "node": ">=6.10"
11 }, 11 },
12 "repository": { 12 "repository": {
13 "type": "git", 13 "type": "git",
diff --git a/scripts/release.sh b/scripts/release.sh
index bfb65684..6186a486 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -1,6 +1,6 @@
1#! /usr/bin/env bash 1#! /usr/bin/env bash
2# You can execute this file to create a new package for wallabag 2# You can execute this file to create a new package for wallabag
3# eg: `sh release.sh master /tmp wllbg-release prod` 3# eg: `sh release.sh 2.3.3 /tmp wllbg-release prod`
4 4
5VERSION=$1 5VERSION=$1
6TMP_FOLDER=$2 6TMP_FOLDER=$2
@@ -10,11 +10,11 @@ ENV=$4
10rm -rf $TMP_FOLDER/$RELEASE_FOLDER 10rm -rf $TMP_FOLDER/$RELEASE_FOLDER
11mkdir $TMP_FOLDER/$RELEASE_FOLDER 11mkdir $TMP_FOLDER/$RELEASE_FOLDER
12git clone git@github.com:wallabag/wallabag.git -b $VERSION $TMP_FOLDER/$RELEASE_FOLDER/$VERSION 12git clone git@github.com:wallabag/wallabag.git -b $VERSION $TMP_FOLDER/$RELEASE_FOLDER/$VERSION
13cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && SYMFONY_ENV=$ENV composer up -n --no-dev 13cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && SYMFONY_ENV=$ENV COMPOSER_MEMORY_LIMIT=-1 composer up -n --no-dev
14cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console wallabag:install --env=$ENV 14cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console wallabag:install --env=$ENV
15cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console assets:install --env=$ENV --symlink --relative 15cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console assets:install --env=$ENV --symlink --relative
16cd $TMP_FOLDER/$RELEASE_FOLDER && tar czf wallabag-$VERSION.tar.gz --exclude="var/cache/*" --exclude="var/logs/*" --exclude="var/sessions/*" --exclude=".git" $VERSION 16cd $TMP_FOLDER/$RELEASE_FOLDER && tar czf wallabag-$VERSION.tar.gz --exclude="var/cache/*" --exclude="var/logs/*" --exclude="var/sessions/*" --exclude=".git" $VERSION
17echo "MD5 checksum of the package for wallabag $VERSION" 17echo "MD5 checksum of the package for wallabag $VERSION"
18md5 $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz 18md5 $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz
19scp $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz framasoft_bag@78.46.248.87:/var/www/framabag.org/web 19echo "Package to upload to the release server:"
20rm -rf $TMP_FOLDER/$RELEASE_FOLDER 20echo $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz
diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
index f3090e65..3a7421c7 100644
--- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
+++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
@@ -28,7 +28,7 @@ class WallabagAnnotationController extends FOSRestController
28 ->getDoctrine() 28 ->getDoctrine()
29 ->getRepository('WallabagAnnotationBundle:Annotation') 29 ->getRepository('WallabagAnnotationBundle:Annotation')
30 ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); 30 ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
31 $total = count($annotationRows); 31 $total = \count($annotationRows);
32 $annotations = ['total' => $total, 'rows' => $annotationRows]; 32 $annotations = ['total' => $total, 'rows' => $annotationRows];
33 33
34 $json = $this->get('jms_serializer')->serialize($annotations, 'json'); 34 $json = $this->get('jms_serializer')->serialize($annotations, 'json');
diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
index b44f7e64..0de5c934 100644
--- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
+++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
@@ -21,7 +21,7 @@ class AnnotationRepository extends EntityRepository
21 public function getBuilderForAllByUser($userId) 21 public function getBuilderForAllByUser($userId)
22 { 22 {
23 return $this 23 return $this
24 ->getBuilderByUser($userId) 24 ->getSortedQueryBuilderByUser($userId)
25 ; 25 ;
26 } 26 }
27 27
@@ -133,7 +133,7 @@ class AnnotationRepository extends EntityRepository
133 * 133 *
134 * @return QueryBuilder 134 * @return QueryBuilder
135 */ 135 */
136 private function getBuilderByUser($userId) 136 private function getSortedQueryBuilderByUser($userId)
137 { 137 {
138 return $this->createQueryBuilder('a') 138 return $this->createQueryBuilder('a')
139 ->leftJoin('a.user', 'u') 139 ->leftJoin('a.user', 'u')
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
index fc47c479..0b4e74a0 100644
--- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -102,7 +102,7 @@ class EntryRestController extends WallabagRestController
102 $order = $request->query->get('order', 'desc'); 102 $order = $request->query->get('order', 'desc');
103 $page = (int) $request->query->get('page', 1); 103 $page = (int) $request->query->get('page', 1);
104 $perPage = (int) $request->query->get('perPage', 30); 104 $perPage = (int) $request->query->get('perPage', 30);
105 $tags = is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', ''); 105 $tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', '');
106 $since = $request->query->get('since', 0); 106 $since = $request->query->get('since', 0);
107 107
108 /** @var \Pagerfanta\Pagerfanta $pager */ 108 /** @var \Pagerfanta\Pagerfanta $pager */
@@ -253,7 +253,7 @@ class EntryRestController extends WallabagRestController
253 253
254 $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions'); 254 $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
255 255
256 if (count($urls) > $limit) { 256 if (\count($urls) > $limit) {
257 throw new HttpException(400, 'API limit reached'); 257 throw new HttpException(400, 'API limit reached');
258 } 258 }
259 259
@@ -347,7 +347,7 @@ class EntryRestController extends WallabagRestController
347 'open_graph' => [ 347 'open_graph' => [
348 'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(), 348 'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
349 ], 349 ],
350 'authors' => is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(), 350 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
351 ] 351 ]
352 ); 352 );
353 } catch (\Exception $e) { 353 } catch (\Exception $e) {
@@ -461,7 +461,7 @@ class EntryRestController extends WallabagRestController
461 $contentProxy->updateLanguage($entry, $data['language']); 461 $contentProxy->updateLanguage($entry, $data['language']);
462 } 462 }
463 463
464 if (!empty($data['authors']) && is_string($data['authors'])) { 464 if (!empty($data['authors']) && \is_string($data['authors'])) {
465 $entry->setPublishedBy(explode(',', $data['authors'])); 465 $entry->setPublishedBy(explode(',', $data['authors']));
466 } 466 }
467 467
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
index 9d333fe4..c6d6df6a 100644
--- a/src/Wallabag/ApiBundle/Controller/TagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -138,14 +138,14 @@ class TagRestController extends WallabagRestController
138 */ 138 */
139 private function cleanOrphanTag($tags) 139 private function cleanOrphanTag($tags)
140 { 140 {
141 if (!is_array($tags)) { 141 if (!\is_array($tags)) {
142 $tags = [$tags]; 142 $tags = [$tags];
143 } 143 }
144 144
145 $em = $this->getDoctrine()->getManager(); 145 $em = $this->getDoctrine()->getManager();
146 146
147 foreach ($tags as $tag) { 147 foreach ($tags as $tag) {
148 if (0 === count($tag->getEntries())) { 148 if (0 === \count($tag->getEntries())) {
149 $em->remove($tag); 149 $em->remove($tag);
150 } 150 }
151 } 151 }
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
index b58909db..99170967 100644
--- a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
+++ b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
@@ -51,7 +51,7 @@ class CleanDuplicatesCommand extends ContainerAwareCommand
51 } else { 51 } else {
52 $users = $this->getContainer()->get('wallabag_user.user_repository')->findAll(); 52 $users = $this->getContainer()->get('wallabag_user.user_repository')->findAll();
53 53
54 $this->io->text(sprintf('Cleaning through <info>%d</info> user accounts', count($users))); 54 $this->io->text(sprintf('Cleaning through <info>%d</info> user accounts', \count($users)));
55 55
56 foreach ($users as $user) { 56 foreach ($users as $user) {
57 $this->io->text(sprintf('Processing user <info>%s</info>', $user->getUsername())); 57 $this->io->text(sprintf('Processing user <info>%s</info>', $user->getUsername()));
@@ -79,7 +79,7 @@ class CleanDuplicatesCommand extends ContainerAwareCommand
79 $url = $this->similarUrl($entry['url']); 79 $url = $this->similarUrl($entry['url']);
80 80
81 /* @var $entry Entry */ 81 /* @var $entry Entry */
82 if (in_array($url, $urls, true)) { 82 if (\in_array($url, $urls, true)) {
83 ++$duplicatesCount; 83 ++$duplicatesCount;
84 84
85 $em->remove($repo->find($entry['id'])); 85 $em->remove($repo->find($entry['id']));
@@ -96,8 +96,8 @@ class CleanDuplicatesCommand extends ContainerAwareCommand
96 96
97 private function similarUrl($url) 97 private function similarUrl($url)
98 { 98 {
99 if (in_array(substr($url, -1), ['/', '#'], true)) { // get rid of "/" and "#" and the end of urls 99 if (\in_array(substr($url, -1), ['/', '#'], true)) { // get rid of "/" and "#" and the end of urls
100 return substr($url, 0, strlen($url)); 100 return substr($url, 0, \strlen($url));
101 } 101 }
102 102
103 return $url; 103 return $url;
diff --git a/src/Wallabag/CoreBundle/Command/ExportCommand.php b/src/Wallabag/CoreBundle/Command/ExportCommand.php
index 75e9ad91..128f9d65 100644
--- a/src/Wallabag/CoreBundle/Command/ExportCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ExportCommand.php
@@ -47,7 +47,7 @@ class ExportCommand extends ContainerAwareCommand
47 ->getQuery() 47 ->getQuery()
48 ->getResult(); 48 ->getResult();
49 49
50 $io->text(sprintf('Exporting <info>%d</info> entrie(s) for user <info>%s</info>...', count($entries), $user->getUserName())); 50 $io->text(sprintf('Exporting <info>%d</info> entrie(s) for user <info>%s</info>...', \count($entries), $user->getUserName()));
51 51
52 $filePath = $input->getArgument('filepath'); 52 $filePath = $input->getArgument('filepath');
53 53
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index a56a3257..3c76545c 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -81,7 +81,7 @@ class InstallCommand extends ContainerAwareCommand
81 $status = '<info>OK!</info>'; 81 $status = '<info>OK!</info>';
82 $help = ''; 82 $help = '';
83 83
84 if (!extension_loaded($this->getContainer()->getParameter('database_driver'))) { 84 if (!\extension_loaded($this->getContainer()->getParameter('database_driver'))) {
85 $fulfilled = false; 85 $fulfilled = false;
86 $status = '<error>ERROR!</error>'; 86 $status = '<error>ERROR!</error>';
87 $help = 'Database driver "' . $this->getContainer()->getParameter('database_driver') . '" is not installed.'; 87 $help = 'Database driver "' . $this->getContainer()->getParameter('database_driver') . '" is not installed.';
@@ -146,7 +146,7 @@ class InstallCommand extends ContainerAwareCommand
146 $status = '<info>OK!</info>'; 146 $status = '<info>OK!</info>';
147 $help = ''; 147 $help = '';
148 148
149 if (!function_exists($functionRequired)) { 149 if (!\function_exists($functionRequired)) {
150 $fulfilled = false; 150 $fulfilled = false;
151 $status = '<error>ERROR!</error>'; 151 $status = '<error>ERROR!</error>';
152 $help = 'You need the ' . $functionRequired . ' function activated'; 152 $help = 'You need the ' . $functionRequired . ' function activated';
@@ -371,7 +371,7 @@ class InstallCommand extends ContainerAwareCommand
371 } 371 }
372 372
373 try { 373 try {
374 return in_array($databaseName, $schemaManager->listDatabases(), true); 374 return \in_array($databaseName, $schemaManager->listDatabases(), true);
375 } catch (\Doctrine\DBAL\Exception\DriverException $e) { 375 } catch (\Doctrine\DBAL\Exception\DriverException $e) {
376 // it means we weren't able to get database list, assume the database doesn't exist 376 // it means we weren't able to get database list, assume the database doesn't exist
377 377
@@ -389,6 +389,6 @@ class InstallCommand extends ContainerAwareCommand
389 { 389 {
390 $schemaManager = $this->getContainer()->get('doctrine')->getManager()->getConnection()->getSchemaManager(); 390 $schemaManager = $this->getContainer()->get('doctrine')->getManager()->getConnection()->getSchemaManager();
391 391
392 return count($schemaManager->listTableNames()) > 0 ? true : false; 392 return \count($schemaManager->listTableNames()) > 0 ? true : false;
393 } 393 }
394} 394}
diff --git a/src/Wallabag/CoreBundle/Command/ListUserCommand.php b/src/Wallabag/CoreBundle/Command/ListUserCommand.php
index 68e515da..a7101a02 100644
--- a/src/Wallabag/CoreBundle/Command/ListUserCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ListUserCommand.php
@@ -50,7 +50,7 @@ class ListUserCommand extends ContainerAwareCommand
50 $io->success( 50 $io->success(
51 sprintf( 51 sprintf(
52 '%s/%s%s user(s) displayed.', 52 '%s/%s%s user(s) displayed.',
53 count($users), 53 \count($users),
54 $nbUsers, 54 $nbUsers,
55 null === $input->getArgument('search') ? '' : ' (filtered)' 55 null === $input->getArgument('search') ? '' : ' (filtered)'
56 ) 56 )
diff --git a/src/Wallabag/CoreBundle/Command/ReloadEntryCommand.php b/src/Wallabag/CoreBundle/Command/ReloadEntryCommand.php
index 91998841..10918872 100644
--- a/src/Wallabag/CoreBundle/Command/ReloadEntryCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ReloadEntryCommand.php
@@ -43,7 +43,7 @@ class ReloadEntryCommand extends ContainerAwareCommand
43 $entryRepository = $this->getContainer()->get('wallabag_core.entry_repository'); 43 $entryRepository = $this->getContainer()->get('wallabag_core.entry_repository');
44 $entryIds = $entryRepository->findAllEntriesIdByUserId($userId); 44 $entryIds = $entryRepository->findAllEntriesIdByUserId($userId);
45 45
46 $nbEntries = count($entryIds); 46 $nbEntries = \count($entryIds);
47 if (!$nbEntries) { 47 if (!$nbEntries) {
48 $io->success('No entry to reload.'); 48 $io->success('No entry to reload.');
49 49
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index a89bb780..b999c539 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -348,7 +348,7 @@ class ConfigController extends Controller
348 $em = $this->getDoctrine()->getManager(); 348 $em = $this->getDoctrine()->getManager();
349 349
350 foreach ($tags as $tag) { 350 foreach ($tags as $tag) {
351 if (0 === count($tag->getEntries())) { 351 if (0 === \count($tag->getEntries())) {
352 $em->remove($tag); 352 $em->remove($tag);
353 } 353 }
354 } 354 }
diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php
index 616c37f2..b6d28e59 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -65,7 +65,7 @@ class TagController extends Controller
65 $em->flush(); 65 $em->flush();
66 66
67 // remove orphan tag in case no entries are associated to it 67 // remove orphan tag in case no entries are associated to it
68 if (0 === count($tag->getEntries())) { 68 if (0 === \count($tag->getEntries())) {
69 $em->remove($tag); 69 $em->remove($tag);
70 $em->flush(); 70 $em->flush();
71 } 71 }
diff --git a/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
index 6f8c9e27..702c7f7a 100644
--- a/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
@@ -33,7 +33,7 @@ class EntryFilterType extends AbstractType
33 33
34 $this->user = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null; 34 $this->user = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
35 35
36 if (null === $this->user || !is_object($this->user)) { 36 if (null === $this->user || !\is_object($this->user)) {
37 return; 37 return;
38 } 38 }
39 } 39 }
@@ -96,7 +96,7 @@ class EntryFilterType extends AbstractType
96 ->add('domainName', TextFilterType::class, [ 96 ->add('domainName', TextFilterType::class, [
97 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) { 97 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
98 $value = $values['value']; 98 $value = $values['value'];
99 if (strlen($value) <= 2 || empty($value)) { 99 if (\strlen($value) <= 2 || empty($value)) {
100 return; 100 return;
101 } 101 }
102 $expression = $filterQuery->getExpr()->like($field, $filterQuery->getExpr()->lower($filterQuery->getExpr()->literal('%' . $value . '%'))); 102 $expression = $filterQuery->getExpr()->like($field, $filterQuery->getExpr()->lower($filterQuery->getExpr()->literal('%' . $value . '%')));
diff --git a/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
index 2c85da62..90e00c62 100644
--- a/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
+++ b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
@@ -107,7 +107,7 @@ class GrabySiteConfigBuilder implements SiteConfigBuilder
107 */ 107 */
108 protected function processExtraFields($extraFieldsStrings) 108 protected function processExtraFields($extraFieldsStrings)
109 { 109 {
110 if (!is_array($extraFieldsStrings)) { 110 if (!\is_array($extraFieldsStrings)) {
111 return []; 111 return [];
112 } 112 }
113 113
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
index fe795d42..d4ea608f 100644
--- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php
+++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
@@ -53,6 +53,7 @@ class ContentProxy
53 53
54 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) { 54 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
55 $fetchedContent = $this->graby->fetchContent($url); 55 $fetchedContent = $this->graby->fetchContent($url);
56 $fetchedContent['title'] = $this->sanitizeContentTitle($fetchedContent['title'], $fetchedContent['content_type']);
56 57
57 // when content is imported, we have information in $content 58 // when content is imported, we have information in $content
58 // in case fetching content goes bad, we'll keep the imported information instead of overriding them 59 // in case fetching content goes bad, we'll keep the imported information instead of overriding them
@@ -85,7 +86,7 @@ class ContentProxy
85 (new LocaleConstraint()) 86 (new LocaleConstraint())
86 ); 87 );
87 88
88 if (0 === count($errors)) { 89 if (0 === \count($errors)) {
89 $entry->setLanguage($value); 90 $entry->setLanguage($value);
90 91
91 return; 92 return;
@@ -107,7 +108,7 @@ class ContentProxy
107 (new UrlConstraint()) 108 (new UrlConstraint())
108 ); 109 );
109 110
110 if (0 === count($errors)) { 111 if (0 === \count($errors)) {
111 $entry->setPreviewPicture($value); 112 $entry->setPreviewPicture($value);
112 113
113 return; 114 return;
@@ -177,6 +178,59 @@ class ContentProxy
177 } 178 }
178 179
179 /** 180 /**
181 * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character.
182 *
183 * @param $title
184 * @param $contentType
185 *
186 * @return string
187 */
188 private function sanitizeContentTitle($title, $contentType)
189 {
190 if ('application/pdf' === $contentType) {
191 $title = $this->convertPdfEncodingToUTF8($title);
192 }
193
194 return $this->sanitizeUTF8Text($title);
195 }
196
197 /**
198 * If the title from the fetched content comes from a PDF, then its very possible that the character encoding is not
199 * UTF-8. This methods tries to identify the character encoding and translate the title to UTF-8.
200 *
201 * @param $title
202 *
203 * @return string (maybe contains invalid UTF-8 character)
204 */
205 private function convertPdfEncodingToUTF8($title)
206 {
207 // first try UTF-8 because its easier to detect its present/absence
208 foreach (['UTF-8', 'UTF-16BE', 'WINDOWS-1252'] as $encoding) {
209 if (mb_check_encoding($title, $encoding)) {
210 return mb_convert_encoding($title, 'UTF-8', $encoding);
211 }
212 }
213
214 return $title;
215 }
216
217 /**
218 * Remove invalid UTF-8 characters from the given string.
219 *
220 * @param string $rawText
221 *
222 * @return string
223 */
224 private function sanitizeUTF8Text($rawText)
225 {
226 if (mb_check_encoding($rawText, 'UTF-8')) {
227 return $rawText;
228 }
229
230 return iconv('UTF-8', 'UTF-8//IGNORE', $rawText);
231 }
232
233 /**
180 * Stock entry with fetched or imported content. 234 * Stock entry with fetched or imported content.
181 * Will fall back to OpenGraph data if available. 235 * Will fall back to OpenGraph data if available.
182 * 236 *
@@ -212,7 +266,7 @@ class ContentProxy
212 $entry->setHttpStatus($content['status']); 266 $entry->setHttpStatus($content['status']);
213 } 267 }
214 268
215 if (!empty($content['authors']) && is_array($content['authors'])) { 269 if (!empty($content['authors']) && \is_array($content['authors'])) {
216 $entry->setPublishedBy($content['authors']); 270 $entry->setPublishedBy($content['authors']);
217 } 271 }
218 272
@@ -233,7 +287,7 @@ class ContentProxy
233 } 287 }
234 288
235 // if content is an image, define it as a preview too 289 // if content is an image, define it as a preview too
236 if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { 290 if (!empty($content['content_type']) && \in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
237 $this->updatePreviewPicture($entry, $content['url']); 291 $this->updatePreviewPicture($entry, $content['url']);
238 } 292 }
239 293
diff --git a/src/Wallabag/CoreBundle/Helper/CryptoProxy.php b/src/Wallabag/CoreBundle/Helper/CryptoProxy.php
index 7d8c9888..67d73915 100644
--- a/src/Wallabag/CoreBundle/Helper/CryptoProxy.php
+++ b/src/Wallabag/CoreBundle/Helper/CryptoProxy.php
@@ -81,6 +81,6 @@ class CryptoProxy
81 */ 81 */
82 private function mask($value) 82 private function mask($value)
83 { 83 {
84 return strlen($value) > 0 ? $value[0] . '*****' . $value[strlen($value) - 1] : 'Empty value'; 84 return \strlen($value) > 0 ? $value[0] . '*****' . $value[\strlen($value) - 1] : 'Empty value';
85 } 85 }
86} 86}
diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
index 487a3a23..cc3dcfce 100644
--- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php
+++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
@@ -198,7 +198,7 @@ class DownloadImages
198 // Must be one or more digits followed by w OR x 198 // Must be one or more digits followed by w OR x
199 $pattern = "/(?:[^\"'\s]+\s*(?:\d+[wx])+)/"; 199 $pattern = "/(?:[^\"'\s]+\s*(?:\d+[wx])+)/";
200 preg_match_all($pattern, $srcsetAttribute, $matches); 200 preg_match_all($pattern, $srcsetAttribute, $matches);
201 $srcset = call_user_func_array('array_merge', $matches); 201 $srcset = \call_user_func_array('array_merge', $matches);
202 $srcsetUrls = array_map(function ($src) { 202 $srcsetUrls = array_map(function ($src) {
203 return trim(explode(' ', $src, 2)[0]); 203 return trim(explode(' ', $src, 2)[0]);
204 }, $srcset); 204 }, $srcset);
@@ -308,7 +308,7 @@ class DownloadImages
308 $this->logger->debug('DownloadImages: Checking extension (alternative)', ['ext' => $ext]); 308 $this->logger->debug('DownloadImages: Checking extension (alternative)', ['ext' => $ext]);
309 } 309 }
310 310
311 if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) { 311 if (!\in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) {
312 $this->logger->error('DownloadImages: Processed image with not allowed extension. Skipping: ' . $imagePath); 312 $this->logger->error('DownloadImages: Processed image with not allowed extension. Skipping: ' . $imagePath);
313 313
314 return false; 314 return false;
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
index 136f66f5..cbf1037b 100644
--- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php
+++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
@@ -45,7 +45,7 @@ class EntriesExport
45 */ 45 */
46 public function setEntries($entries) 46 public function setEntries($entries)
47 { 47 {
48 if (!is_array($entries)) { 48 if (!\is_array($entries)) {
49 $this->language = $entries->getLanguage(); 49 $this->language = $entries->getLanguage();
50 $entries = [$entries]; 50 $entries = [$entries];
51 } 51 }
@@ -325,7 +325,7 @@ class EntriesExport
325 { 325 {
326 $delimiter = ';'; 326 $delimiter = ';';
327 $enclosure = '"'; 327 $enclosure = '"';
328 $handle = fopen('php://memory', 'rb+'); 328 $handle = fopen('php://memory', 'b+r');
329 329
330 fputcsv($handle, ['Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language', 'Creation date'], $delimiter, $enclosure); 330 fputcsv($handle, ['Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language', 'Creation date'], $delimiter, $enclosure);
331 331
diff --git a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
index 49c1ea41..1c2c5093 100644
--- a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
+++ b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
@@ -31,7 +31,7 @@ class PreparePagerForEntries
31 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 31 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
32 } 32 }
33 33
34 if (null === $user || !is_object($user)) { 34 if (null === $user || !\is_object($user)) {
35 return; 35 return;
36 } 36 }
37 37
diff --git a/src/Wallabag/CoreBundle/Helper/Redirect.php b/src/Wallabag/CoreBundle/Helper/Redirect.php
index abc84d08..9d1a6345 100644
--- a/src/Wallabag/CoreBundle/Helper/Redirect.php
+++ b/src/Wallabag/CoreBundle/Helper/Redirect.php
@@ -31,7 +31,7 @@ class Redirect
31 { 31 {
32 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 32 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
33 33
34 if (null === $user || !is_object($user)) { 34 if (null === $user || !\is_object($user)) {
35 return $url; 35 return $url;
36 } 36 }
37 37
diff --git a/src/Wallabag/CoreBundle/Helper/TagsAssigner.php b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
index 0bfe5c57..e6b4989f 100644
--- a/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
+++ b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
@@ -32,7 +32,7 @@ class TagsAssigner
32 { 32 {
33 $tagsEntities = []; 33 $tagsEntities = [];
34 34
35 if (!is_array($tags)) { 35 if (!\is_array($tags)) {
36 $tags = explode(',', $tags); 36 $tags = explode(',', $tags);
37 } 37 }
38 38
@@ -48,7 +48,7 @@ class TagsAssigner
48 $label = trim(mb_convert_case($label, MB_CASE_LOWER)); 48 $label = trim(mb_convert_case($label, MB_CASE_LOWER));
49 49
50 // avoid empty tag 50 // avoid empty tag
51 if (0 === strlen($label)) { 51 if (0 === \strlen($label)) {
52 continue; 52 continue;
53 } 53 }
54 54
diff --git a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
index 40b5673d..4a2fcab5 100644
--- a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
+++ b/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
@@ -36,7 +36,7 @@ class UsernameRssTokenConverter implements ParamConverterInterface
36 { 36 {
37 // If there is no manager, this means that only Doctrine DBAL is configured 37 // If there is no manager, this means that only Doctrine DBAL is configured
38 // In this case we can do nothing and just return 38 // In this case we can do nothing and just return
39 if (null === $this->registry || !count($this->registry->getManagers())) { 39 if (null === $this->registry || !\count($this->registry->getManagers())) {
40 return false; 40 return false;
41 } 41 }
42 42
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index b5e35eff..83379998 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -21,7 +21,7 @@ class EntryRepository extends EntityRepository
21 public function getBuilderForAllByUser($userId) 21 public function getBuilderForAllByUser($userId)
22 { 22 {
23 return $this 23 return $this
24 ->getBuilderByUser($userId) 24 ->getSortedQueryBuilderByUser($userId)
25 ; 25 ;
26 } 26 }
27 27
@@ -35,7 +35,7 @@ class EntryRepository extends EntityRepository
35 public function getBuilderForUnreadByUser($userId) 35 public function getBuilderForUnreadByUser($userId)
36 { 36 {
37 return $this 37 return $this
38 ->getBuilderByUser($userId) 38 ->getSortedQueryBuilderByUser($userId)
39 ->andWhere('e.isArchived = false') 39 ->andWhere('e.isArchived = false')
40 ; 40 ;
41 } 41 }
@@ -50,7 +50,7 @@ class EntryRepository extends EntityRepository
50 public function getBuilderForArchiveByUser($userId) 50 public function getBuilderForArchiveByUser($userId)
51 { 51 {
52 return $this 52 return $this
53 ->getBuilderByUser($userId) 53 ->getSortedQueryBuilderByUser($userId)
54 ->andWhere('e.isArchived = true') 54 ->andWhere('e.isArchived = true')
55 ; 55 ;
56 } 56 }
@@ -65,7 +65,7 @@ class EntryRepository extends EntityRepository
65 public function getBuilderForStarredByUser($userId) 65 public function getBuilderForStarredByUser($userId)
66 { 66 {
67 return $this 67 return $this
68 ->getBuilderByUser($userId, 'starredAt', 'desc') 68 ->getSortedQueryBuilderByUser($userId, 'starredAt', 'desc')
69 ->andWhere('e.isStarred = true') 69 ->andWhere('e.isStarred = true')
70 ; 70 ;
71 } 71 }
@@ -82,7 +82,7 @@ class EntryRepository extends EntityRepository
82 public function getBuilderForSearchByUser($userId, $term, $currentRoute) 82 public function getBuilderForSearchByUser($userId, $term, $currentRoute)
83 { 83 {
84 $qb = $this 84 $qb = $this
85 ->getBuilderByUser($userId); 85 ->getSortedQueryBuilderByUser($userId);
86 86
87 if ('starred' === $currentRoute) { 87 if ('starred' === $currentRoute) {
88 $qb->andWhere('e.isStarred = true'); 88 $qb->andWhere('e.isStarred = true');
@@ -102,7 +102,7 @@ class EntryRepository extends EntityRepository
102 } 102 }
103 103
104 /** 104 /**
105 * Retrieves untagged entries for a user. 105 * Retrieve a sorted list of untagged entries for a user.
106 * 106 *
107 * @param int $userId 107 * @param int $userId
108 * 108 *
@@ -111,8 +111,21 @@ class EntryRepository extends EntityRepository
111 public function getBuilderForUntaggedByUser($userId) 111 public function getBuilderForUntaggedByUser($userId)
112 { 112 {
113 return $this 113 return $this
114 ->getBuilderByUser($userId) 114 ->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
115 ->andWhere('size(e.tags) = 0'); 115 }
116
117 /**
118 * Retrieve untagged entries for a user.
119 *
120 * @param int $userId
121 *
122 * @return QueryBuilder
123 */
124 public function getRawBuilderForUntaggedByUser($userId)
125 {
126 return $this->getQueryBuilderByUser($userId)
127 ->leftJoin('e.tags', 't')
128 ->andWhere('t.id is null');
116 } 129 }
117 130
118 /** 131 /**
@@ -151,7 +164,7 @@ class EntryRepository extends EntityRepository
151 $qb->andWhere('e.updatedAt > :since')->setParameter('since', new \DateTime(date('Y-m-d H:i:s', $since))); 164 $qb->andWhere('e.updatedAt > :since')->setParameter('since', new \DateTime(date('Y-m-d H:i:s', $since)));
152 } 165 }
153 166
154 if (is_string($tags) && '' !== $tags) { 167 if (\is_string($tags) && '' !== $tags) {
155 foreach (explode(',', $tags) as $i => $tag) { 168 foreach (explode(',', $tags) as $i => $tag) {
156 $entryAlias = 'e' . $i; 169 $entryAlias = 'e' . $i;
157 $tagAlias = 't' . $i; 170 $tagAlias = 't' . $i;
@@ -260,7 +273,7 @@ class EntryRepository extends EntityRepository
260 */ 273 */
261 public function removeTag($userId, Tag $tag) 274 public function removeTag($userId, Tag $tag)
262 { 275 {
263 $entries = $this->getBuilderByUser($userId) 276 $entries = $this->getSortedQueryBuilderByUser($userId)
264 ->innerJoin('e.tags', 't') 277 ->innerJoin('e.tags', 't')
265 ->andWhere('t.id = :tagId')->setParameter('tagId', $tag->getId()) 278 ->andWhere('t.id = :tagId')->setParameter('tagId', $tag->getId())
266 ->getQuery() 279 ->getQuery()
@@ -296,7 +309,7 @@ class EntryRepository extends EntityRepository
296 */ 309 */
297 public function findAllByTagId($userId, $tagId) 310 public function findAllByTagId($userId, $tagId)
298 { 311 {
299 return $this->getBuilderByUser($userId) 312 return $this->getSortedQueryBuilderByUser($userId)
300 ->innerJoin('e.tags', 't') 313 ->innerJoin('e.tags', 't')
301 ->andWhere('t.id = :tagId')->setParameter('tagId', $tagId) 314 ->andWhere('t.id = :tagId')->setParameter('tagId', $tagId)
302 ->getQuery() 315 ->getQuery()
@@ -320,7 +333,7 @@ class EntryRepository extends EntityRepository
320 ->getQuery() 333 ->getQuery()
321 ->getResult(); 334 ->getResult();
322 335
323 if (count($res)) { 336 if (\count($res)) {
324 return current($res); 337 return current($res);
325 } 338 }
326 339
@@ -414,7 +427,20 @@ class EntryRepository extends EntityRepository
414 } 427 }
415 428
416 /** 429 /**
417 * Return a query builder to used by other getBuilderFor* method. 430 * Return a query builder to be used by other getBuilderFor* method.
431 *
432 * @param int $userId
433 *
434 * @return QueryBuilder
435 */
436 private function getQueryBuilderByUser($userId)
437 {
438 return $this->createQueryBuilder('e')
439 ->andWhere('e.user = :userId')->setParameter('userId', $userId);
440 }
441
442 /**
443 * Return a sorted query builder to be used by other getBuilderFor* method.
418 * 444 *
419 * @param int $userId 445 * @param int $userId
420 * @param string $sortBy 446 * @param string $sortBy
@@ -422,10 +448,23 @@ class EntryRepository extends EntityRepository
422 * 448 *
423 * @return QueryBuilder 449 * @return QueryBuilder
424 */ 450 */
425 private function getBuilderByUser($userId, $sortBy = 'createdAt', $direction = 'desc') 451 private function getSortedQueryBuilderByUser($userId, $sortBy = 'createdAt', $direction = 'desc')
426 { 452 {
427 return $this->createQueryBuilder('e') 453 return $this->sortQueryBuilder($this->getQueryBuilderByUser($userId), $sortBy, $direction);
428 ->andWhere('e.user = :userId')->setParameter('userId', $userId) 454 }
455
456 /**
457 * Return the given QueryBuilder with an orderBy() call.
458 *
459 * @param QueryBuilder $qb
460 * @param string $sortBy
461 * @param string $direction
462 *
463 * @return QueryBuilder
464 */
465 private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc')
466 {
467 return $qb
429 ->orderBy(sprintf('e.%s', $sortBy), $direction); 468 ->orderBy(sprintf('e.%s', $sortBy), $direction);
430 } 469 }
431} 470}
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index 5c45211f..3ae9d414 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -30,7 +30,7 @@ class TagRepository extends EntityRepository
30 $query->setResultCacheLifetime($cacheLifeTime); 30 $query->setResultCacheLifetime($cacheLifeTime);
31 } 31 }
32 32
33 return count($query->getArrayResult()); 33 return \count($query->getArrayResult());
34 } 34 }
35 35
36 /** 36 /**
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_no_preview.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_no_preview.html.twig
index 8e6bbae0..4fd4debd 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_no_preview.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_no_preview.html.twig
@@ -1,5 +1,12 @@
1<div class="card"> 1<div class="card">
2 <div class="card-body"> 2 <div class="card-body">
3 <div class="card-image waves-effect waves-block waves-light">
4 <ul class="card-entry-labels">
5 {% for tag in entry.tags | slice(0, 3) %}
6 <li><a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a></li>
7 {% endfor %}
8 </ul>
9 </div>
3 {% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry} only %} 10 {% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry} only %}
4 </div> 11 </div>
5 12
diff --git a/src/Wallabag/CoreBundle/Tools/Utils.php b/src/Wallabag/CoreBundle/Tools/Utils.php
index eba21c02..46bb1dc5 100644
--- a/src/Wallabag/CoreBundle/Tools/Utils.php
+++ b/src/Wallabag/CoreBundle/Tools/Utils.php
@@ -29,6 +29,6 @@ class Utils
29 */ 29 */
30 public static function getReadingTime($text) 30 public static function getReadingTime($text)
31 { 31 {
32 return floor(count(preg_split('~[^\p{L}\p{N}\']+~u', strip_tags($text))) / 200); 32 return floor(\count(preg_split('~[^\p{L}\p{N}\']+~u', strip_tags($text))) / 200);
33 } 33 }
34} 34}
diff --git a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
index 8992117e..00b1e595 100644
--- a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
+++ b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
@@ -64,7 +64,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
64 { 64 {
65 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 65 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
66 66
67 if (null === $user || !is_object($user)) { 67 if (null === $user || !\is_object($user)) {
68 return 0; 68 return 0;
69 } 69 }
70 70
@@ -96,7 +96,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
96 $query->useResultCache(true); 96 $query->useResultCache(true);
97 $query->setResultCacheLifetime($this->lifeTime); 97 $query->setResultCacheLifetime($this->lifeTime);
98 98
99 return count($query->getArrayResult()); 99 return \count($query->getArrayResult());
100 } 100 }
101 101
102 /** 102 /**
@@ -108,7 +108,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
108 { 108 {
109 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 109 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
110 110
111 if (null === $user || !is_object($user)) { 111 if (null === $user || !\is_object($user)) {
112 return 0; 112 return 0;
113 } 113 }
114 114
@@ -124,7 +124,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
124 { 124 {
125 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 125 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
126 126
127 if (null === $user || !is_object($user)) { 127 if (null === $user || !\is_object($user)) {
128 return 0; 128 return 0;
129 } 129 }
130 130
@@ -137,7 +137,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
137 $query->useResultCache(true); 137 $query->useResultCache(true);
138 $query->setResultCacheLifetime($this->lifeTime); 138 $query->setResultCacheLifetime($this->lifeTime);
139 139
140 $nbArchives = count($query->getArrayResult()); 140 $nbArchives = \count($query->getArrayResult());
141 141
142 $interval = $user->getCreatedAt()->diff(new \DateTime('now')); 142 $interval = $user->getCreatedAt()->diff(new \DateTime('now'));
143 $nbDays = (int) $interval->format('%a') ?: 1; 143 $nbDays = (int) $interval->format('%a') ?: 1;
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 99056c2c..f9ffe994 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -43,7 +43,7 @@ class ImportCommand extends ContainerAwareCommand
43 $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username')); 43 $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username'));
44 } 44 }
45 45
46 if (!is_object($user)) { 46 if (!\is_object($user)) {
47 throw new Exception(sprintf('User "%s" not found', $input->getArgument('username'))); 47 throw new Exception(sprintf('User "%s" not found', $input->getArgument('username')));
48 } 48 }
49 49
diff --git a/src/Wallabag/ImportBundle/Controller/BrowserController.php b/src/Wallabag/ImportBundle/Controller/BrowserController.php
index 77a7a904..6418925c 100644
--- a/src/Wallabag/ImportBundle/Controller/BrowserController.php
+++ b/src/Wallabag/ImportBundle/Controller/BrowserController.php
@@ -30,7 +30,7 @@ abstract class BrowserController extends Controller
30 $markAsRead = $form->get('mark_as_read')->getData(); 30 $markAsRead = $form->get('mark_as_read')->getData();
31 $name = $this->getUser()->getId() . '.json'; 31 $name = $this->getUser()->getId() . '.json';
32 32
33 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 33 if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
34 $res = $wallabag 34 $res = $wallabag
35 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name) 35 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
36 ->setMarkAsRead($markAsRead) 36 ->setMarkAsRead($markAsRead)
diff --git a/src/Wallabag/ImportBundle/Controller/InstapaperController.php b/src/Wallabag/ImportBundle/Controller/InstapaperController.php
index 550679c3..f184baf9 100644
--- a/src/Wallabag/ImportBundle/Controller/InstapaperController.php
+++ b/src/Wallabag/ImportBundle/Controller/InstapaperController.php
@@ -31,7 +31,7 @@ class InstapaperController extends Controller
31 $markAsRead = $form->get('mark_as_read')->getData(); 31 $markAsRead = $form->get('mark_as_read')->getData();
32 $name = 'instapaper_' . $this->getUser()->getId() . '.csv'; 32 $name = 'instapaper_' . $this->getUser()->getId() . '.csv';
33 33
34 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 34 if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
35 $res = $instapaper 35 $res = $instapaper
36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name) 36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
37 ->setMarkAsRead($markAsRead) 37 ->setMarkAsRead($markAsRead)
diff --git a/src/Wallabag/ImportBundle/Controller/PinboardController.php b/src/Wallabag/ImportBundle/Controller/PinboardController.php
index 0e57fd41..6f54c69a 100644
--- a/src/Wallabag/ImportBundle/Controller/PinboardController.php
+++ b/src/Wallabag/ImportBundle/Controller/PinboardController.php
@@ -31,7 +31,7 @@ class PinboardController extends Controller
31 $markAsRead = $form->get('mark_as_read')->getData(); 31 $markAsRead = $form->get('mark_as_read')->getData();
32 $name = 'pinboard_' . $this->getUser()->getId() . '.json'; 32 $name = 'pinboard_' . $this->getUser()->getId() . '.json';
33 33
34 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 34 if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
35 $res = $pinboard 35 $res = $pinboard
36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name) 36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
37 ->setMarkAsRead($markAsRead) 37 ->setMarkAsRead($markAsRead)
diff --git a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
index 59de24cb..729a97a3 100644
--- a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
+++ b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
@@ -31,7 +31,7 @@ class ReadabilityController extends Controller
31 $markAsRead = $form->get('mark_as_read')->getData(); 31 $markAsRead = $form->get('mark_as_read')->getData();
32 $name = 'readability_' . $this->getUser()->getId() . '.json'; 32 $name = 'readability_' . $this->getUser()->getId() . '.json';
33 33
34 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 34 if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
35 $res = $readability 35 $res = $readability
36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name) 36 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
37 ->setMarkAsRead($markAsRead) 37 ->setMarkAsRead($markAsRead)
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagController.php b/src/Wallabag/ImportBundle/Controller/WallabagController.php
index 6e6524b4..d182dd2c 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagController.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagController.php
@@ -33,7 +33,7 @@ abstract class WallabagController extends Controller
33 $markAsRead = $form->get('mark_as_read')->getData(); 33 $markAsRead = $form->get('mark_as_read')->getData();
34 $name = $this->getUser()->getId() . '.json'; 34 $name = $this->getUser()->getId() . '.json';
35 35
36 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 36 if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
37 $res = $wallabag 37 $res = $wallabag
38 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name) 38 ->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
39 ->setMarkAsRead($markAsRead) 39 ->setMarkAsRead($markAsRead)
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
index b5593180..225f1791 100644
--- a/src/Wallabag/ImportBundle/Import/BrowserImport.php
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -77,7 +77,7 @@ abstract class BrowserImport extends AbstractImport
77 */ 77 */
78 public function parseEntry(array $importedEntry) 78 public function parseEntry(array $importedEntry)
79 { 79 {
80 if ((!array_key_exists('guid', $importedEntry) || (!array_key_exists('id', $importedEntry))) && is_array(reset($importedEntry))) { 80 if ((!array_key_exists('guid', $importedEntry) || (!array_key_exists('id', $importedEntry))) && \is_array(reset($importedEntry))) {
81 if ($this->producer) { 81 if ($this->producer) {
82 $this->parseEntriesForProducer($importedEntry); 82 $this->parseEntriesForProducer($importedEntry);
83 83
diff --git a/src/Wallabag/ImportBundle/Import/InstapaperImport.php b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
index 7ab69e7a..e4f0970c 100644
--- a/src/Wallabag/ImportBundle/Import/InstapaperImport.php
+++ b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
@@ -62,7 +62,7 @@ class InstapaperImport extends AbstractImport
62 } 62 }
63 63
64 $entries = []; 64 $entries = [];
65 $handle = fopen($this->filepath, 'r'); 65 $handle = fopen($this->filepath, 'rb');
66 while (false !== ($data = fgetcsv($handle, 10240))) { 66 while (false !== ($data = fgetcsv($handle, 10240))) {
67 if ('URL' === $data[0]) { 67 if ('URL' === $data[0]) {
68 continue; 68 continue;
@@ -72,7 +72,7 @@ class InstapaperImport extends AbstractImport
72 // BUT it can also be the status (since status = folder in Instapaper) 72 // BUT it can also be the status (since status = folder in Instapaper)
73 // and we don't want archive, unread & starred to become a tag 73 // and we don't want archive, unread & starred to become a tag
74 $tags = null; 74 $tags = null;
75 if (false === in_array($data[3], ['Archive', 'Unread', 'Starred'], true)) { 75 if (false === \in_array($data[3], ['Archive', 'Unread', 'Starred'], true)) {
76 $tags = [$data[3]]; 76 $tags = [$data[3]];
77 } 77 }
78 78
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index dddb87f4..c1b35b7e 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -149,7 +149,7 @@ class PocketImport extends AbstractImport
149 // - first call get 5k offset 0 149 // - first call get 5k offset 0
150 // - second call get 5k offset 5k 150 // - second call get 5k offset 5k
151 // - and so on 151 // - and so on
152 if (self::NB_ELEMENTS === count($entries['list'])) { 152 if (self::NB_ELEMENTS === \count($entries['list'])) {
153 ++$run; 153 ++$run;
154 154
155 return $this->import(self::NB_ELEMENTS * $run); 155 return $this->import(self::NB_ELEMENTS * $run);
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
index a35c411e..b9bb525a 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -56,7 +56,7 @@ class WallabagV1Import extends WallabagImport
56 56
57 // In case of a bad fetch in v1, replace title and content with v2 error strings 57 // In case of a bad fetch in v1, replace title and content with v2 error strings
58 // If fetching fails again, they will get this instead of the v1 strings 58 // If fetching fails again, they will get this instead of the v1 strings
59 if (in_array($entry['title'], $this->untitled, true)) { 59 if (\in_array($entry['title'], $this->untitled, true)) {
60 $data['title'] = $this->fetchingErrorMessageTitle; 60 $data['title'] = $this->fetchingErrorMessageTitle;
61 $data['html'] = $this->fetchingErrorMessage; 61 $data['html'] = $this->fetchingErrorMessage;
62 } 62 }
diff --git a/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php b/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php
index 35de47f9..f58d1c12 100644
--- a/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php
@@ -28,7 +28,7 @@ class DeveloperControllerTest extends WallabagCoreTestCase
28 $this->assertSame(200, $client->getResponse()->getStatusCode()); 28 $this->assertSame(200, $client->getResponse()->getStatusCode());
29 29
30 $newNbClients = $em->getRepository('WallabagApiBundle:Client')->findAll(); 30 $newNbClients = $em->getRepository('WallabagApiBundle:Client')->findAll();
31 $this->assertGreaterThan(count($nbClients), count($newNbClients)); 31 $this->assertGreaterThan(\count($nbClients), \count($newNbClients));
32 32
33 $this->assertGreaterThan(1, $alert = $crawler->filter('.settings ul li strong')->extract(['_text'])); 33 $this->assertGreaterThan(1, $alert = $crawler->filter('.settings ul li strong')->extract(['_text']));
34 $this->assertContains('My app', $alert[0]); 34 $this->assertContains('My app', $alert[0]);
@@ -65,7 +65,7 @@ class DeveloperControllerTest extends WallabagCoreTestCase
65 65
66 $crawler = $client->request('GET', '/developer'); 66 $crawler = $client->request('GET', '/developer');
67 $this->assertSame(200, $client->getResponse()->getStatusCode()); 67 $this->assertSame(200, $client->getResponse()->getStatusCode());
68 $this->assertSame(count($nbClients), $crawler->filter('ul[class=collapsible] li')->count()); 68 $this->assertSame(\count($nbClients), $crawler->filter('ul[class=collapsible] li')->count());
69 } 69 }
70 70
71 public function testDeveloperHowto() 71 public function testDeveloperHowto()
diff --git a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
index 9722986e..58b617f3 100644
--- a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
@@ -28,7 +28,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
28 28
29 $this->assertSame($entry->getTitle(), $content['title']); 29 $this->assertSame($entry->getTitle(), $content['title']);
30 $this->assertSame($entry->getUrl(), $content['url']); 30 $this->assertSame($entry->getUrl(), $content['url']);
31 $this->assertCount(count($entry->getTags()), $content['tags']); 31 $this->assertCount(\count($entry->getTags()), $content['tags']);
32 $this->assertSame($entry->getUserName(), $content['user_name']); 32 $this->assertSame($entry->getUserName(), $content['user_name']);
33 $this->assertSame($entry->getUserEmail(), $content['user_email']); 33 $this->assertSame($entry->getUserEmail(), $content['user_email']);
34 $this->assertSame($entry->getUserId(), $content['user_id']); 34 $this->assertSame($entry->getUserId(), $content['user_id']);
@@ -127,7 +127,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
127 127
128 $content = json_decode($this->client->getResponse()->getContent(), true); 128 $content = json_decode($this->client->getResponse()->getContent(), true);
129 129
130 $this->assertGreaterThanOrEqual(1, count($content)); 130 $this->assertGreaterThanOrEqual(1, \count($content));
131 $this->assertNotEmpty($content['_embedded']['items']); 131 $this->assertNotEmpty($content['_embedded']['items']);
132 $this->assertGreaterThanOrEqual(1, $content['total']); 132 $this->assertGreaterThanOrEqual(1, $content['total']);
133 $this->assertSame(1, $content['page']); 133 $this->assertSame(1, $content['page']);
@@ -154,7 +154,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
154 154
155 $content = json_decode($this->client->getResponse()->getContent(), true); 155 $content = json_decode($this->client->getResponse()->getContent(), true);
156 156
157 $this->assertGreaterThanOrEqual(1, count($content)); 157 $this->assertGreaterThanOrEqual(1, \count($content));
158 $this->assertArrayHasKey('items', $content['_embedded']); 158 $this->assertArrayHasKey('items', $content['_embedded']);
159 $this->assertGreaterThanOrEqual(0, $content['total']); 159 $this->assertGreaterThanOrEqual(0, $content['total']);
160 $this->assertSame(1, $content['page']); 160 $this->assertSame(1, $content['page']);
@@ -206,7 +206,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
206 206
207 $content = json_decode($this->client->getResponse()->getContent(), true); 207 $content = json_decode($this->client->getResponse()->getContent(), true);
208 208
209 $this->assertGreaterThanOrEqual(1, count($content)); 209 $this->assertGreaterThanOrEqual(1, \count($content));
210 $this->assertArrayHasKey('items', $content['_embedded']); 210 $this->assertArrayHasKey('items', $content['_embedded']);
211 $this->assertGreaterThanOrEqual(1, $content['total']); 211 $this->assertGreaterThanOrEqual(1, $content['total']);
212 $this->assertSame(1, $content['page']); 212 $this->assertSame(1, $content['page']);
@@ -250,7 +250,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
250 250
251 $content = json_decode($this->client->getResponse()->getContent(), true); 251 $content = json_decode($this->client->getResponse()->getContent(), true);
252 252
253 $this->assertGreaterThanOrEqual(1, count($content)); 253 $this->assertGreaterThanOrEqual(1, \count($content));
254 $this->assertNotEmpty($content['_embedded']['items']); 254 $this->assertNotEmpty($content['_embedded']['items']);
255 $this->assertGreaterThanOrEqual(1, $content['total']); 255 $this->assertGreaterThanOrEqual(1, $content['total']);
256 $this->assertSame(1, $content['page']); 256 $this->assertSame(1, $content['page']);
@@ -278,7 +278,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
278 278
279 $content = json_decode($this->client->getResponse()->getContent(), true); 279 $content = json_decode($this->client->getResponse()->getContent(), true);
280 280
281 $this->assertGreaterThanOrEqual(1, count($content)); 281 $this->assertGreaterThanOrEqual(1, \count($content));
282 $this->assertNotEmpty($content['_embedded']['items']); 282 $this->assertNotEmpty($content['_embedded']['items']);
283 $this->assertGreaterThanOrEqual(1, $content['total']); 283 $this->assertGreaterThanOrEqual(1, $content['total']);
284 $this->assertSame(1, $content['page']); 284 $this->assertSame(1, $content['page']);
@@ -305,7 +305,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
305 305
306 $content = json_decode($this->client->getResponse()->getContent(), true); 306 $content = json_decode($this->client->getResponse()->getContent(), true);
307 307
308 $this->assertGreaterThanOrEqual(1, count($content)); 308 $this->assertGreaterThanOrEqual(1, \count($content));
309 $this->assertNotEmpty($content['_embedded']['items']); 309 $this->assertNotEmpty($content['_embedded']['items']);
310 $this->assertGreaterThanOrEqual(1, $content['total']); 310 $this->assertGreaterThanOrEqual(1, $content['total']);
311 $this->assertSame(1, $content['page']); 311 $this->assertSame(1, $content['page']);
@@ -342,7 +342,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
342 342
343 $content = json_decode($this->client->getResponse()->getContent(), true); 343 $content = json_decode($this->client->getResponse()->getContent(), true);
344 344
345 $this->assertGreaterThanOrEqual(1, count($content)); 345 $this->assertGreaterThanOrEqual(1, \count($content));
346 $this->assertNotEmpty($content['_embedded']['items']); 346 $this->assertNotEmpty($content['_embedded']['items']);
347 $this->assertGreaterThanOrEqual(1, $content['total']); 347 $this->assertGreaterThanOrEqual(1, $content['total']);
348 $this->assertSame(1, $content['page']); 348 $this->assertSame(1, $content['page']);
@@ -370,7 +370,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
370 370
371 $content = json_decode($this->client->getResponse()->getContent(), true); 371 $content = json_decode($this->client->getResponse()->getContent(), true);
372 372
373 $this->assertGreaterThanOrEqual(1, count($content)); 373 $this->assertGreaterThanOrEqual(1, \count($content));
374 $this->assertEmpty($content['_embedded']['items']); 374 $this->assertEmpty($content['_embedded']['items']);
375 $this->assertSame(0, $content['total']); 375 $this->assertSame(0, $content['total']);
376 $this->assertSame(1, $content['page']); 376 $this->assertSame(1, $content['page']);
@@ -608,7 +608,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
608 $this->assertSame($entry->getId(), $content['id']); 608 $this->assertSame($entry->getId(), $content['id']);
609 $this->assertSame($entry->getUrl(), $content['url']); 609 $this->assertSame($entry->getUrl(), $content['url']);
610 $this->assertSame('New awesome title', $content['title']); 610 $this->assertSame('New awesome title', $content['title']);
611 $this->assertGreaterThanOrEqual(1, count($content['tags']), 'We force only one tag'); 611 $this->assertGreaterThanOrEqual(1, \count($content['tags']), 'We force only one tag');
612 $this->assertSame(1, $content['user_id']); 612 $this->assertSame(1, $content['user_id']);
613 $this->assertSame('de_AT', $content['language']); 613 $this->assertSame('de_AT', $content['language']);
614 $this->assertSame('http://preview.io/picture.jpg', $content['preview_picture']); 614 $this->assertSame('http://preview.io/picture.jpg', $content['preview_picture']);
@@ -647,7 +647,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
647 647
648 $this->assertSame($entry->getId(), $content['id']); 648 $this->assertSame($entry->getId(), $content['id']);
649 $this->assertSame($entry->getUrl(), $content['url']); 649 $this->assertSame($entry->getUrl(), $content['url']);
650 $this->assertGreaterThanOrEqual(1, count($content['tags']), 'We force only one tag'); 650 $this->assertGreaterThanOrEqual(1, \count($content['tags']), 'We force only one tag');
651 $this->assertEmpty($content['published_by'], 'Authors were not saved because of an array instead of a string'); 651 $this->assertEmpty($content['published_by'], 'Authors were not saved because of an array instead of a string');
652 $this->assertSame($previousContent, $content['content'], 'Ensure content has not moved'); 652 $this->assertSame($previousContent, $content['content'], 'Ensure content has not moved');
653 $this->assertSame($previousLanguage, $content['language'], 'Ensure language has not moved'); 653 $this->assertSame($previousLanguage, $content['language'], 'Ensure language has not moved');
@@ -772,7 +772,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
772 $this->markTestSkipped('No content found in db.'); 772 $this->markTestSkipped('No content found in db.');
773 } 773 }
774 774
775 $nbTags = count($entry->getTags()); 775 $nbTags = \count($entry->getTags());
776 776
777 $newTags = 'tag1,tag2,tag3'; 777 $newTags = 'tag1,tag2,tag3';
778 778
@@ -783,7 +783,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
783 $content = json_decode($this->client->getResponse()->getContent(), true); 783 $content = json_decode($this->client->getResponse()->getContent(), true);
784 784
785 $this->assertArrayHasKey('tags', $content); 785 $this->assertArrayHasKey('tags', $content);
786 $this->assertSame($nbTags + 3, count($content['tags'])); 786 $this->assertSame($nbTags + 3, \count($content['tags']));
787 787
788 $entryDB = $this->client->getContainer() 788 $entryDB = $this->client->getContainer()
789 ->get('doctrine.orm.entity_manager') 789 ->get('doctrine.orm.entity_manager')
@@ -813,7 +813,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
813 } 813 }
814 814
815 // hydrate the tags relations 815 // hydrate the tags relations
816 $nbTags = count($entry->getTags()); 816 $nbTags = \count($entry->getTags());
817 $tag = $entry->getTags()[0]; 817 $tag = $entry->getTags()[0];
818 818
819 $this->client->request('DELETE', '/api/entries/' . $entry->getId() . '/tags/' . $tag->getId() . '.json'); 819 $this->client->request('DELETE', '/api/entries/' . $entry->getId() . '/tags/' . $tag->getId() . '.json');
@@ -823,7 +823,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
823 $content = json_decode($this->client->getResponse()->getContent(), true); 823 $content = json_decode($this->client->getResponse()->getContent(), true);
824 824
825 $this->assertArrayHasKey('tags', $content); 825 $this->assertArrayHasKey('tags', $content);
826 $this->assertSame($nbTags - 1, count($content['tags'])); 826 $this->assertSame($nbTags - 1, \count($content['tags']));
827 } 827 }
828 828
829 public function testSaveIsArchivedAfterPost() 829 public function testSaveIsArchivedAfterPost()
diff --git a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php
index a436be79..bf0068b4 100644
--- a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php
@@ -174,7 +174,7 @@ class EntryControllerTest extends WallabagCoreTestCase
174 174
175 public function testPostWithMultipleAuthors() 175 public function testPostWithMultipleAuthors()
176 { 176 {
177 $url = 'http://www.liberation.fr/planete/2017/04/05/donald-trump-et-xi-jinping-tentative-de-flirt-en-floride_1560768'; 177 $url = 'https://www.liberation.fr/planete/2017/04/05/donald-trump-et-xi-jinping-tentative-de-flirt-en-floride_1560768';
178 $this->logInAs('admin'); 178 $this->logInAs('admin');
179 $client = $this->getClient(); 179 $client = $this->getClient();
180 180
@@ -197,6 +197,7 @@ class EntryControllerTest extends WallabagCoreTestCase
197 ->getRepository('WallabagCoreBundle:Entry') 197 ->getRepository('WallabagCoreBundle:Entry')
198 ->findByUrlAndUserId($url, $this->getLoggedInUserId()); 198 ->findByUrlAndUserId($url, $this->getLoggedInUserId());
199 199
200 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
200 $authors = $content->getPublishedBy(); 201 $authors = $content->getPublishedBy();
201 $this->assertSame('2017-04-05 19:26:13', $content->getPublishedAt()->format('Y-m-d H:i:s')); 202 $this->assertSame('2017-04-05 19:26:13', $content->getPublishedAt()->format('Y-m-d H:i:s'));
202 $this->assertSame('fr', $content->getLanguage()); 203 $this->assertSame('fr', $content->getLanguage());
@@ -524,7 +525,7 @@ class EntryControllerTest extends WallabagCoreTestCase
524 525
525 $this->assertGreaterThan(1, $title = $crawler->filter('div[id=article] h1')->extract(['_text'])); 526 $this->assertGreaterThan(1, $title = $crawler->filter('div[id=article] h1')->extract(['_text']));
526 $this->assertContains('My updated title hehe :)', $title[0]); 527 $this->assertContains('My updated title hehe :)', $title[0]);
527 $this->assertSame(1, count($stats = $crawler->filter('div[class=tools] ul[class=stats] li a[class=tool]')->extract(['_text']))); 528 $this->assertSame(1, \count($stats = $crawler->filter('div[class=tools] ul[class=stats] li a[class=tool]')->extract(['_text'])));
528 $this->assertNotContains('example.io', trim($stats[0])); 529 $this->assertNotContains('example.io', trim($stats[0]));
529 } 530 }
530 531
@@ -1325,16 +1326,12 @@ class EntryControllerTest extends WallabagCoreTestCase
1325 'http://www.hao123.com/shequ?__noscript__-=1', 1326 'http://www.hao123.com/shequ?__noscript__-=1',
1326 'zh_CN', 1327 'zh_CN',
1327 ], 1328 ],
1328 'de_AT' => [ 1329 'ru' => [
1329 'https://buy.garmin.com/de-AT/AT/catalog/product/compareResult.ep?compareProduct=112885&compareProduct=36728', 1330 'https://www.kp.ru/daily/26879.7/3921982/',
1330 'de_AT', 1331 'ru',
1331 ],
1332 'ru_RU' => [
1333 'http://netler.ru/ikt/windows-error-reporting.htm',
1334 'ru_RU',
1335 ], 1332 ],
1336 'pt_BR' => [ 1333 'pt_BR' => [
1337 'http://precodoscombustiveis.com.br/postos/cidade/4121/pr/maringa', 1334 'https://politica.estadao.com.br/noticias/eleicoes,campanha-catatonica,70002491983',
1338 'pt_BR', 1335 'pt_BR',
1339 ], 1336 ],
1340 'fucked_list_of_languages' => [ 1337 'fucked_list_of_languages' => [
diff --git a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
index ab7f23cc..6f3308e5 100644
--- a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
@@ -180,7 +180,7 @@ class ExportControllerTest extends WallabagCoreTestCase
180 180
181 $this->assertGreaterThan(1, $csv); 181 $this->assertGreaterThan(1, $csv);
182 // +1 for title line 182 // +1 for title line
183 $this->assertSame(count($contentInDB) + 1, count($csv)); 183 $this->assertSame(\count($contentInDB) + 1, \count($csv));
184 $this->assertSame('Title;URL;Content;Tags;"MIME Type";Language;"Creation date"', $csv[0]); 184 $this->assertSame('Title;URL;Content;Tags;"MIME Type";Language;"Creation date"', $csv[0]);
185 $this->assertContains($contentInDB[0]['title'], $csv[1]); 185 $this->assertContains($contentInDB[0]['title'], $csv[1]);
186 $this->assertContains($contentInDB[0]['url'], $csv[1]); 186 $this->assertContains($contentInDB[0]['url'], $csv[1]);
@@ -272,7 +272,7 @@ class ExportControllerTest extends WallabagCoreTestCase
272 272
273 $content = new \SimpleXMLElement($client->getResponse()->getContent()); 273 $content = new \SimpleXMLElement($client->getResponse()->getContent());
274 $this->assertGreaterThan(0, $content->count()); 274 $this->assertGreaterThan(0, $content->count());
275 $this->assertSame(count($contentInDB), $content->count()); 275 $this->assertSame(\count($contentInDB), $content->count());
276 $this->assertNotEmpty('id', (string) $content->entry[0]->id); 276 $this->assertNotEmpty('id', (string) $content->entry[0]->id);
277 $this->assertNotEmpty('title', (string) $content->entry[0]->title); 277 $this->assertNotEmpty('title', (string) $content->entry[0]->title);
278 $this->assertNotEmpty('url', (string) $content->entry[0]->url); 278 $this->assertNotEmpty('url', (string) $content->entry[0]->url);
diff --git a/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php b/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php
index 5a973a7e..768f4c07 100644
--- a/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php
@@ -98,7 +98,7 @@ class TagControllerTest extends WallabagCoreTestCase
98 $tags[$key] = $tag->getLabel(); 98 $tags[$key] = $tag->getLabel();
99 } 99 }
100 100
101 $this->assertGreaterThanOrEqual(2, count($tags)); 101 $this->assertGreaterThanOrEqual(2, \count($tags));
102 $this->assertNotFalse(array_search('foo2', $tags, true), 'Tag foo2 is assigned to the entry'); 102 $this->assertNotFalse(array_search('foo2', $tags, true), 'Tag foo2 is assigned to the entry');
103 $this->assertNotFalse(array_search('bar2', $tags, true), 'Tag bar2 is assigned to the entry'); 103 $this->assertNotFalse(array_search('bar2', $tags, true), 'Tag bar2 is assigned to the entry');
104 } 104 }
diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
index 51df8de1..3f3c60d0 100644
--- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
+++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
@@ -531,6 +531,250 @@ class ContentProxyTest extends TestCase
531 $this->assertSame('1.1.1.1', $entry->getDomainName()); 531 $this->assertSame('1.1.1.1', $entry->getDomainName());
532 } 532 }
533 533
534 public function testWebsiteWithValidUTF8Title_doNothing()
535 {
536 // You can use https://www.online-toolz.com/tools/text-hex-convertor.php to convert UTF-8 text <=> hex
537 // See http://graphemica.com for more info about the characters
538 // '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
539 $actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '7A');
540
541 $tagger = $this->getTaggerMock();
542 $tagger->expects($this->once())
543 ->method('tag');
544
545 $graby = $this->getMockBuilder('Graby\Graby')
546 ->setMethods(['fetchContent'])
547 ->disableOriginalConstructor()
548 ->getMock();
549
550 $graby->expects($this->any())
551 ->method('fetchContent')
552 ->willReturn([
553 'html' => false,
554 'title' => $actualTitle,
555 'url' => '',
556 'content_type' => 'text/html',
557 'language' => '',
558 ]);
559
560 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
561 $entry = new Entry(new User());
562 $proxy->updateEntry($entry, 'http://0.0.0.0');
563
564 // '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
565 $expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
566 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
567 }
568
569 public function testWebsiteWithInvalidUTF8Title_removeInvalidCharacter()
570 {
571 // See http://graphemica.com for more info about the characters
572 // 'a€b' (61;80;62) in hexadecimal and WINDOWS-1252 - but 80 is a invalid UTF-8 character.
573 // The correct UTF-8 € character (U+20AC) is E282AC
574 $actualTitle = $this->hexToStr('61' . '80' . '62');
575
576 $tagger = $this->getTaggerMock();
577 $tagger->expects($this->once())
578 ->method('tag');
579
580 $graby = $this->getMockBuilder('Graby\Graby')
581 ->setMethods(['fetchContent'])
582 ->disableOriginalConstructor()
583 ->getMock();
584
585 $graby->expects($this->any())
586 ->method('fetchContent')
587 ->willReturn([
588 'html' => false,
589 'title' => $actualTitle,
590 'url' => '',
591 'content_type' => 'text/html',
592 'language' => '',
593 ]);
594
595 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
596 $entry = new Entry(new User());
597 $proxy->updateEntry($entry, 'http://0.0.0.0');
598
599 // 'ab' (61;62) because all invalid UTF-8 character (like 80) are removed
600 $expectedTitle = '61' . '62';
601 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
602 }
603
604 public function testPdfWithUTF16BETitle_convertToUTF8()
605 {
606 // See http://graphemica.com for more info about the characters
607 // '😻' (U+1F63B;D83DDE3B) in hexadecimal and as UTF16BE
608 $actualTitle = $this->hexToStr('D83DDE3B');
609
610 $tagger = $this->getTaggerMock();
611 $tagger->expects($this->once())
612 ->method('tag');
613
614 $graby = $this->getMockBuilder('Graby\Graby')
615 ->setMethods(['fetchContent'])
616 ->disableOriginalConstructor()
617 ->getMock();
618
619 $graby->expects($this->any())
620 ->method('fetchContent')
621 ->willReturn([
622 'html' => false,
623 'title' => $actualTitle,
624 'url' => '',
625 'content_type' => 'application/pdf',
626 'language' => '',
627 ]);
628
629 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
630 $entry = new Entry(new User());
631 $proxy->updateEntry($entry, 'http://0.0.0.0');
632
633 // '😻' (U+1F63B or F09F98BB) in hexadecimal and UTF-8
634 $expectedTitle = 'F09F98BB';
635 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
636 }
637
638 public function testPdfWithUTF8Title_doNothing()
639 {
640 // See http://graphemica.com for more info about the characters
641 // '😻' (U+1F63B;D83DDE3B) in hexadecimal and as UTF8
642 $actualTitle = $this->hexToStr('F09F98BB');
643
644 $tagger = $this->getTaggerMock();
645 $tagger->expects($this->once())
646 ->method('tag');
647
648 $graby = $this->getMockBuilder('Graby\Graby')
649 ->setMethods(['fetchContent'])
650 ->disableOriginalConstructor()
651 ->getMock();
652
653 $graby->expects($this->any())
654 ->method('fetchContent')
655 ->willReturn([
656 'html' => false,
657 'title' => $actualTitle,
658 'url' => '',
659 'content_type' => 'application/pdf',
660 'language' => '',
661 ]);
662
663 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
664 $entry = new Entry(new User());
665 $proxy->updateEntry($entry, 'http://0.0.0.0');
666
667 // '😻' (U+1F63B or F09F98BB) in hexadecimal and UTF-8
668 $expectedTitle = 'F09F98BB';
669 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
670 }
671
672 public function testPdfWithWINDOWS1252Title_convertToUTF8()
673 {
674 // See http://graphemica.com for more info about the characters
675 // '€' (80) in hexadecimal and WINDOWS-1252
676 $actualTitle = $this->hexToStr('80');
677
678 $tagger = $this->getTaggerMock();
679 $tagger->expects($this->once())
680 ->method('tag');
681
682 $graby = $this->getMockBuilder('Graby\Graby')
683 ->setMethods(['fetchContent'])
684 ->disableOriginalConstructor()
685 ->getMock();
686
687 $graby->expects($this->any())
688 ->method('fetchContent')
689 ->willReturn([
690 'html' => false,
691 'title' => $actualTitle,
692 'url' => '',
693 'content_type' => 'application/pdf',
694 'language' => '',
695 ]);
696
697 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
698 $entry = new Entry(new User());
699 $proxy->updateEntry($entry, 'http://0.0.0.0');
700
701 // '€' (U+20AC or E282AC) in hexadecimal and UTF-8
702 $expectedTitle = 'E282AC';
703 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
704 }
705
706 public function testPdfWithInvalidCharacterInTitle_removeInvalidCharacter()
707 {
708 // See http://graphemica.com for more info about the characters
709 // '😻ℤ�z' (U+1F63B or F09F98BB; U+2124 or E284A4; invalid character 81; U+007A or 7A) in hexadecimal and UTF-8
710 // 0x81 is not a valid character for UTF16, UTF8 and WINDOWS-1252
711 $actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '81' . '7A');
712
713 $tagger = $this->getTaggerMock();
714 $tagger->expects($this->once())
715 ->method('tag');
716
717 $graby = $this->getMockBuilder('Graby\Graby')
718 ->setMethods(['fetchContent'])
719 ->disableOriginalConstructor()
720 ->getMock();
721
722 $graby->expects($this->any())
723 ->method('fetchContent')
724 ->willReturn([
725 'html' => false,
726 'title' => $actualTitle,
727 'url' => '',
728 'content_type' => 'application/pdf',
729 'language' => '',
730 ]);
731
732 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
733 $entry = new Entry(new User());
734 $proxy->updateEntry($entry, 'http://0.0.0.0');
735
736 // '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
737 // the 0x81 (represented by �) is invalid for UTF16, UTF8 and WINDOWS-1252 and is removed
738 $expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
739 $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
740 }
741
742 /**
743 * https://stackoverflow.com/a/18506801.
744 *
745 * @param $string
746 *
747 * @return string
748 */
749 private function strToHex($string)
750 {
751 $hex = '';
752 for ($i = 0; $i < \strlen($string); ++$i) {
753 $ord = \ord($string[$i]);
754 $hexCode = dechex($ord);
755 $hex .= substr('0' . $hexCode, -2);
756 }
757
758 return strtoupper($hex);
759 }
760
761 /**
762 * https://stackoverflow.com/a/18506801.
763 *
764 * @param $hex
765 *
766 * @return string
767 */
768 private function hexToStr($hex)
769 {
770 $string = '';
771 for ($i = 0; $i < \strlen($hex) - 1; $i += 2) {
772 $string .= \chr(hexdec($hex[$i] . $hex[$i + 1]));
773 }
774
775 return $string;
776 }
777
534 private function getTaggerMock() 778 private function getTaggerMock()
535 { 779 {
536 return $this->getMockBuilder(RuleBasedTagger::class) 780 return $this->getMockBuilder(RuleBasedTagger::class)
diff --git a/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php
index ddb7a65a..cd3e41e9 100644
--- a/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php
@@ -121,7 +121,7 @@ class ChromeControllerTest extends WallabagCoreTestCase
121 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content); 121 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
122 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.usinenouvelle.com is ok'); 122 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.usinenouvelle.com is ok');
123 $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.usinenouvelle.com is ok'); 123 $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.usinenouvelle.com is ok');
124 $this->assertSame(1, count($content->getTags())); 124 $this->assertSame(1, \count($content->getTags()));
125 125
126 $createdAt = $content->getCreatedAt(); 126 $createdAt = $content->getCreatedAt();
127 $this->assertSame('2011', $createdAt->format('Y')); 127 $this->assertSame('2011', $createdAt->format('Y'));
diff --git a/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php
index fc02c813..dc5ed6d0 100644
--- a/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php
@@ -122,7 +122,7 @@ class FirefoxControllerTest extends WallabagCoreTestCase
122 $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://lexpansion.lexpress.fr is ok'); 122 $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://lexpansion.lexpress.fr is ok');
123 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://lexpansion.lexpress.fr is ok'); 123 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://lexpansion.lexpress.fr is ok');
124 $this->assertNotEmpty($content->getLanguage(), 'Language for http://lexpansion.lexpress.fr is ok'); 124 $this->assertNotEmpty($content->getLanguage(), 'Language for http://lexpansion.lexpress.fr is ok');
125 $this->assertSame(3, count($content->getTags())); 125 $this->assertSame(3, \count($content->getTags()));
126 126
127 $content = $client->getContainer() 127 $content = $client->getContainer()
128 ->get('doctrine.orm.entity_manager') 128 ->get('doctrine.orm.entity_manager')
diff --git a/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php
index dacdf488..7390fa88 100644
--- a/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php
@@ -114,15 +114,17 @@ class InstapaperControllerTest extends WallabagCoreTestCase
114 ->get('doctrine.orm.entity_manager') 114 ->get('doctrine.orm.entity_manager')
115 ->getRepository('WallabagCoreBundle:Entry') 115 ->getRepository('WallabagCoreBundle:Entry')
116 ->findByUrlAndUserId( 116 ->findByUrlAndUserId(
117 'http://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551', 117 'https://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551',
118 $this->getLoggedInUserId() 118 $this->getLoggedInUserId()
119 ); 119 );
120 120
121 $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is ok'); 121 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
122 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is ok'); 122
123 $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is ok'); 123 $this->assertNotEmpty($content->getMimetype(), 'Mimetype for https://www.liberation.fr is ok');
124 $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for https://www.liberation.fr is ok');
125 $this->assertNotEmpty($content->getLanguage(), 'Language for https://www.liberation.fr is ok');
124 $this->assertContains('foot', $content->getTags(), 'It includes the "foot" tag'); 126 $this->assertContains('foot', $content->getTags(), 'It includes the "foot" tag');
125 $this->assertSame(1, count($content->getTags())); 127 $this->assertSame(1, \count($content->getTags()));
126 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); 128 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
127 129
128 $content = $client->getContainer() 130 $content = $client->getContainer()
@@ -136,7 +138,7 @@ class InstapaperControllerTest extends WallabagCoreTestCase
136 $this->assertContains('foot', $content->getTags()); 138 $this->assertContains('foot', $content->getTags());
137 $this->assertContains('test_tag', $content->getTags()); 139 $this->assertContains('test_tag', $content->getTags());
138 140
139 $this->assertSame(2, count($content->getTags())); 141 $this->assertSame(2, \count($content->getTags()));
140 } 142 }
141 143
142 public function testImportInstapaperWithFileAndMarkAllAsRead() 144 public function testImportInstapaperWithFileAndMarkAllAsRead()
diff --git a/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php b/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php
index 1135f32e..80819f45 100644
--- a/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php
@@ -127,7 +127,7 @@ class PinboardControllerTest extends WallabagCoreTestCase
127 $this->assertContains('foot', $tags, 'It includes the "foot" tag'); 127 $this->assertContains('foot', $tags, 'It includes the "foot" tag');
128 $this->assertContains('varnish', $tags, 'It includes the "varnish" tag'); 128 $this->assertContains('varnish', $tags, 'It includes the "varnish" tag');
129 $this->assertContains('php', $tags, 'It includes the "php" tag'); 129 $this->assertContains('php', $tags, 'It includes the "php" tag');
130 $this->assertSame(3, count($tags)); 130 $this->assertSame(3, \count($tags));
131 131
132 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); 132 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
133 $this->assertSame('2016-10-26', $content->getCreatedAt()->format('Y-m-d')); 133 $this->assertSame('2016-10-26', $content->getCreatedAt()->format('Y-m-d'));
diff --git a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
index 78816ad8..5619659a 100644
--- a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
@@ -125,7 +125,7 @@ class ReadabilityControllerTest extends WallabagCoreTestCase
125 125
126 $tags = $content->getTags(); 126 $tags = $content->getTags();
127 $this->assertContains('foot', $tags, 'It includes the "foot" tag'); 127 $this->assertContains('foot', $tags, 'It includes the "foot" tag');
128 $this->assertSame(1, count($tags)); 128 $this->assertSame(1, \count($tags));
129 129
130 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); 130 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
131 $this->assertSame('2016-09-08', $content->getCreatedAt()->format('Y-m-d')); 131 $this->assertSame('2016-09-08', $content->getCreatedAt()->format('Y-m-d'));
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
index e0e309b0..c67941a7 100644
--- a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
@@ -127,7 +127,7 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase
127 $tags = $content->getTags(); 127 $tags = $content->getTags();
128 $this->assertContains('foot', $tags, 'It includes the "foot" tag'); 128 $this->assertContains('foot', $tags, 'It includes the "foot" tag');
129 $this->assertContains('framabag', $tags, 'It includes the "framabag" tag'); 129 $this->assertContains('framabag', $tags, 'It includes the "framabag" tag');
130 $this->assertSame(2, count($tags)); 130 $this->assertSame(2, \count($tags));
131 131
132 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); 132 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
133 } 133 }
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
index e52b9c85..822656ba 100644
--- a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
@@ -115,20 +115,20 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
115 ->get('doctrine.orm.entity_manager') 115 ->get('doctrine.orm.entity_manager')
116 ->getRepository('WallabagCoreBundle:Entry') 116 ->getRepository('WallabagCoreBundle:Entry')
117 ->findByUrlAndUserId( 117 ->findByUrlAndUserId(
118 'http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867', 118 'https://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867',
119 $this->getLoggedInUserId() 119 $this->getLoggedInUserId()
120 ); 120 );
121 121
122 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content); 122 $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
123 123
124 // empty because it wasn't re-imported 124 // empty because it wasn't re-imported
125 $this->assertEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is empty'); 125 $this->assertEmpty($content->getMimetype(), 'Mimetype for https://www.liberation.fr is empty');
126 $this->assertEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is empty'); 126 $this->assertEmpty($content->getPreviewPicture(), 'Preview picture for https://www.liberation.fr is empty');
127 $this->assertEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is empty'); 127 $this->assertEmpty($content->getLanguage(), 'Language for https://www.liberation.fr is empty');
128 128
129 $tags = $content->getTags(); 129 $tags = $content->getTags();
130 $this->assertContains('foot', $tags, 'It includes the "foot" tag'); 130 $this->assertContains('foot', $tags, 'It includes the "foot" tag');
131 $this->assertSame(1, count($tags)); 131 $this->assertSame(1, \count($tags));
132 132
133 $content = $client->getContainer() 133 $content = $client->getContainer()
134 ->get('doctrine.orm.entity_manager') 134 ->get('doctrine.orm.entity_manager')
@@ -147,7 +147,7 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
147 $this->assertContains('foot', $tags, 'It includes the "foot" tag'); 147 $this->assertContains('foot', $tags, 'It includes the "foot" tag');
148 $this->assertContains('mediapart', $tags, 'It includes the "mediapart" tag'); 148 $this->assertContains('mediapart', $tags, 'It includes the "mediapart" tag');
149 $this->assertContains('blog', $tags, 'It includes the "blog" tag'); 149 $this->assertContains('blog', $tags, 'It includes the "blog" tag');
150 $this->assertSame(3, count($tags)); 150 $this->assertSame(3, \count($tags));
151 151
152 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); 152 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
153 $this->assertSame('2016-09-08', $content->getCreatedAt()->format('Y-m-d')); 153 $this->assertSame('2016-09-08', $content->getCreatedAt()->format('Y-m-d'));
diff --git a/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv b/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv
index 7b692d00..5380bf2c 100644
--- a/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv
+++ b/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv
@@ -1,5 +1,5 @@
1URL,Title,Selection,Folder 1URL,Title,Selection,Folder
2http://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551,Baumettes : un tour en cellule,,Unread 2https://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551,Baumettes : un tour en cellule,,Unread
3https://redditblog.com/2016/09/20/amp-and-reactredux/,AMP and React+Redux: Why Not?,,Archive 3https://redditblog.com/2016/09/20/amp-and-reactredux/,AMP and React+Redux: Why Not?,,Archive
4https://medium.com/@the_minh/why-foursquare-swarm-is-still-my-favourite-social-network-e38228493e6c,Why Foursquare / Swarm is still my favourite social network,,Starred 4https://medium.com/@the_minh/why-foursquare-swarm-is-still-my-favourite-social-network-e38228493e6c,Why Foursquare / Swarm is still my favourite social network,,Starred
5https://www.20minutes.fr/high-tech/2077615-20170531-quoi-exactement-tweet-covfefe-donald-trump-persiste-signe,"Dis donc Donald Trump, c'est quoi exactement «covfefe»?",,test_tag 5https://www.20minutes.fr/high-tech/2077615-20170531-quoi-exactement-tweet-covfefe-donald-trump-persiste-signe,"Dis donc Donald Trump, c'est quoi exactement «covfefe»?",,test_tag
diff --git a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json
index 63c44cf9..a2142f90 100644
--- a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json
+++ b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json
@@ -21,7 +21,7 @@
21 { 21 {
22 "id": 22, 22 "id": 22,
23 "title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans", 23 "title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans",
24 "url": "http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867", 24 "url": "https://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867",
25 "is_archived": false, 25 "is_archived": false,
26 "created_at": "2016-09-08T11:55:58+0200", 26 "created_at": "2016-09-08T11:55:58+0200",
27 "updated_at": "2016-09-08T11:57:16+0200", 27 "updated_at": "2016-09-08T11:57:16+0200",
diff --git a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
index f39fa60e..aa176068 100644
--- a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
+++ b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
@@ -13,7 +13,7 @@ final class CountableMemorySpool extends \Swift_MemorySpool implements \Countabl
13{ 13{
14 public function count() 14 public function count()
15 { 15 {
16 return count($this->messages); 16 return \count($this->messages);
17 } 17 }
18 18
19 public function getMessages() 19 public function getMessages()
diff --git a/var/SymfonyRequirements.php b/var/SymfonyRequirements.php
index 3b14a402..4a1fcc62 100644
--- a/var/SymfonyRequirements.php
+++ b/var/SymfonyRequirements.php
@@ -389,7 +389,7 @@ class SymfonyRequirements extends RequirementCollection
389 { 389 {
390 /* mandatory requirements follow */ 390 /* mandatory requirements follow */
391 391
392 $installedPhpVersion = phpversion(); 392 $installedPhpVersion = PHP_VERSION;
393 $requiredPhpVersion = $this->getPhpRequiredVersion(); 393 $requiredPhpVersion = $this->getPhpRequiredVersion();
394 394
395 $this->addRecommendation( 395 $this->addRecommendation(
@@ -448,15 +448,8 @@ class SymfonyRequirements extends RequirementCollection
448 } 448 }
449 449
450 if (false !== $requiredPhpVersion && version_compare($installedPhpVersion, $requiredPhpVersion, '>=')) { 450 if (false !== $requiredPhpVersion && version_compare($installedPhpVersion, $requiredPhpVersion, '>=')) {
451 $timezones = array();
452 foreach (DateTimeZone::listAbbreviations() as $abbreviations) {
453 foreach ($abbreviations as $abbreviation) {
454 $timezones[$abbreviation['timezone_id']] = true;
455 }
456 }
457
458 $this->addRequirement( 451 $this->addRequirement(
459 isset($timezones[@date_default_timezone_get()]), 452 in_array(@date_default_timezone_get(), DateTimeZone::listIdentifiers(), true),
460 sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 453 sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()),
461 'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.' 454 'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.'
462 ); 455 );
@@ -731,7 +724,7 @@ class SymfonyRequirements extends RequirementCollection
731 'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).' 724 'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).'
732 ); 725 );
733 726
734 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 727 if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
735 $this->addRecommendation( 728 $this->addRecommendation(
736 $this->getRealpathCacheSize() >= 5 * 1024 * 1024, 729 $this->getRealpathCacheSize() >= 5 * 1024 * 1024,
737 'realpath_cache_size should be at least 5M in php.ini', 730 'realpath_cache_size should be at least 5M in php.ini',
diff --git a/yarn.lock b/yarn.lock
index 1dd45be4..7b62e0f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2178,6 +2178,10 @@ hawk@~3.1.3:
2178 hoek "2.x.x" 2178 hoek "2.x.x"
2179 sntp "1.x.x" 2179 sntp "1.x.x"
2180 2180
2181highlight.js@^9.12.0:
2182 version "9.12.0"
2183 resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
2184
2181hmac-drbg@^1.0.0: 2185hmac-drbg@^1.0.0:
2182 version "1.0.1" 2186 version "1.0.1"
2183 resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 2187 resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"