]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Merge remote-tracking branch 'origin/master' into 2.3
authorJeremy Benoist <jeremy.benoist@gmail.com>
Fri, 19 May 2017 09:25:19 +0000 (11:25 +0200)
committerJeremy Benoist <jeremy.benoist@gmail.com>
Fri, 19 May 2017 09:25:19 +0000 (11:25 +0200)
18 files changed:
1  2 
app/config/config.yml
composer.json
src/Wallabag/ApiBundle/Controller/EntryRestController.php
src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php

diff --combined app/config/config.yml
index 28abe7340ad8e50d97803b14b254d7f923056778,584b0da2f5c9d56d24f195bd20aeb5604fda818a..73bf0a0d2cdcc3518c486c67dbc7331db7b9e543
@@@ -3,11 -3,6 +3,11 @@@ imports
      - { resource: security.yml }
      - { resource: services.yml }
  
 +parameters:
 +    # Allows to use the live reload feature for changes in assets
 +    use_webpack_dev_server: false
 +    craue_config.cache_adapter.class: Craue\ConfigBundle\CacheAdapter\SymfonyCacheComponentAdapter
 +
  framework:
      #esi:             ~
      translator:
@@@ -35,7 -30,7 +35,7 @@@
      assets: ~
  
  wallabag_core:
-     version: 2.2.2
+     version: 2.2.3
      paypal_url: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9UBA65LG3FX9Y&lc=gb"
      languages:
          en: 'English'
      reading_speed: 1
      cache_lifetime: 10
      action_mark_as_read: 1
 -    list_mode: 1
 +    list_mode: 0
      fetching_error_message: |
          wallabag can't retrieve contents for this article. Please <a href="http://doc.wallabag.org/en/master/user/errors_during_fetching.html#how-can-i-help-to-fix-that">troubleshoot this issue</a>.
 +    api_limit_mass_actions: 10
  
  wallabag_user:
      registration_enabled: "%fosuser_registration%"
diff --combined composer.json
index 02a79853f8c8b8bb2ee85c9696b06b543439d3eb,9336e801b4147482f32fcd32a17975c3830afd8b..1c58516ebad74728cf5b10ea507c023d3eeea033
@@@ -28,7 -28,7 +28,7 @@@
          "issues": "https://github.com/wallabag/wallabag/issues"
      },
      "require": {
 -        "php": ">=5.5.9",
 +        "php": ">=5.6.0",
          "ext-pcre": "*",
          "ext-dom": "*",
          "ext-curl": "*",
@@@ -65,7 -65,7 +65,7 @@@
          "liip/theme-bundle": "~1.1",
          "lexik/form-filter-bundle": "~5.0",
          "j0k3r/graby": "~1.0",
 -        "friendsofsymfony/user-bundle": "2.0.x-dev",
 +        "friendsofsymfony/user-bundle": "^2.0",
          "friendsofsymfony/oauth-server-bundle": "^1.5",
          "stof/doctrine-extensions-bundle": "^1.2",
          "scheb/two-factor-bundle": "~2.0",
@@@ -75,7 -75,7 +75,7 @@@
          "guzzlehttp/guzzle": "^5.3.1",
          "doctrine/doctrine-migrations-bundle": "^1.0",
          "paragonie/random_compat": "~1.0",
 -        "craue/config-bundle": "~1.4",
 +        "craue/config-bundle": "~2.0",
          "mnapoli/piwik-twig-extension": "^1.0",
          "ocramius/proxy-manager": "1.*",
          "white-october/pagerfanta-bundle": "^1.0",
@@@ -84,7 -84,7 +84,7 @@@
          "javibravo/simpleue": "^1.0",
          "symfony/dom-crawler": "^3.1",
          "friendsofsymfony/jsrouting-bundle": "^1.6",
-         "bdunogier/guzzle-site-authenticator": "dev-master"
+         "bdunogier/guzzle-site-authenticator": "1.0.0-beta1"
      },
      "require-dev": {
          "doctrine/doctrine-fixtures-bundle": "~2.2",
      "config": {
          "bin-dir": "bin",
          "platform": {
 -            "php": "5.5.9"
 +            "php": "5.6.0"
          }
      },
      "minimum-stability": "dev",
index 632b16d904d898a59b1421d60c713ee75f3f7c96,54c1747c5a9fb9412f3101892df651b12e34d07d..4801811d63354a659c814f1b41299a9e8821184c
@@@ -5,7 -5,6 +5,7 @@@ namespace Wallabag\ApiBundle\Controller
  use Hateoas\Configuration\Route;
  use Hateoas\Representation\Factory\PagerfantaFactory;
  use Nelmio\ApiDocBundle\Annotation\ApiDoc;
 +use Symfony\Component\HttpKernel\Exception\HttpException;
  use Symfony\Component\HttpFoundation\Request;
  use Symfony\Component\HttpFoundation\JsonResponse;
  use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@@ -42,10 -41,12 +42,10 @@@ class EntryRestController extends Walla
                      ->getRepository('WallabagCoreBundle:Entry')
                      ->findByUrlAndUserId($url, $this->getUser()->getId());
  
 -                $results[$url] = false === $res ? false : true;
 +                $results[$url] = $res instanceof Entry ? $res->getId() : false;
              }
  
 -            $json = $this->get('serializer')->serialize($results, 'json');
 -
 -            return (new JsonResponse())->setJson($json);
 +            return $this->sendResponse($results);
          }
  
          // let's see if it is a simple url?
              ->getRepository('WallabagCoreBundle:Entry')
              ->findByUrlAndUserId($url, $this->getUser()->getId());
  
 -        $exists = false === $res ? false : true;
 -
 -        $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
 +        $exists = $res instanceof Entry ? $res->getId() : false;
  
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse(['exists' => $exists]);
      }
  
      /**
              )
          );
  
 -        $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($paginatedCollection);
      }
  
      /**
          $this->validateAuthentication();
          $this->validateUserAccess($entry->getUser()->getId());
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
              ->exportAs($request->attributes->get('_format'));
      }
  
 +    /**
 +     * Handles an entries list and delete URL.
 +     *
 +     * @ApiDoc(
 +     *       parameters={
 +     *          {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."}
 +     *       }
 +     * )
 +     *
 +     * @return JsonResponse
 +     */
 +    public function deleteEntriesListAction(Request $request)
 +    {
 +        $this->validateAuthentication();
 +
 +        $urls = json_decode($request->query->get('urls', []));
 +
 +        if (empty($urls)) {
 +            return $this->sendResponse([]);
 +        }
 +
 +        $results = [];
 +
 +        // handle multiple urls
 +        foreach ($urls as $key => $url) {
 +            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
 +                $url,
 +                $this->getUser()->getId()
 +            );
 +
 +            $results[$key]['url'] = $url;
 +
 +            if (false !== $entry) {
 +                $em = $this->getDoctrine()->getManager();
 +                $em->remove($entry);
 +                $em->flush();
 +
 +                // entry deleted, dispatch event about it!
 +                $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
 +            }
 +
 +            $results[$key]['entry'] = $entry instanceof Entry ? true : false;
 +        }
 +
 +        return $this->sendResponse($results);
 +    }
 +
 +    /**
 +     * Handles an entries list and create URL.
 +     *
 +     * @ApiDoc(
 +     *       parameters={
 +     *          {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."}
 +     *       }
 +     * )
 +     *
 +     * @return JsonResponse
 +     *
 +     * @throws HttpException When limit is reached
 +     */
 +    public function postEntriesListAction(Request $request)
 +    {
 +        $this->validateAuthentication();
 +
 +        $urls = json_decode($request->query->get('urls', []));
 +        $results = [];
 +
 +        $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
 +
 +        if (count($urls) > $limit) {
 +            throw new HttpException(400, 'API limit reached');
 +        }
 +
 +        // handle multiple urls
 +        if (!empty($urls)) {
 +            foreach ($urls as $key => $url) {
 +                $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
 +                    $url,
 +                    $this->getUser()->getId()
 +                );
 +
 +                $results[$key]['url'] = $url;
 +
 +                if (false === $entry) {
 +                    $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
 +                        new Entry($this->getUser()),
 +                        $url
 +                    );
 +                }
 +
 +                $em = $this->getDoctrine()->getManager();
 +                $em->persist($entry);
 +                $em->flush();
 +
 +                $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
 +
 +                // entry saved, dispatch event about it!
 +                $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
 +            }
 +        }
 +
 +        return $this->sendResponse($results);
 +    }
 +
      /**
       * Create an entry.
       *
          $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
  
          if (false === $entry) {
-             $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
-                 new Entry($this->getUser()),
-                 $url
-             );
+             $entry = new Entry($this->getUser());
+             try {
+                 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
+                     $entry,
+                     $url
+                 );
+             } catch (\Exception $e) {
+                 $this->get('logger')->error('Error while saving an entry', [
+                     'exception' => $e,
+                     'entry' => $entry,
+                 ]);
+                 $entry->setUrl($url);
+             }
          }
  
          if (!is_null($title)) {
          // entry saved, dispatch event about it!
          $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
          $em = $this->getDoctrine()->getManager();
          $em->flush();
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
          // entry saved, dispatch event about it!
          $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
          // entry deleted, dispatch event about it!
          $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
          $this->validateAuthentication();
          $this->validateUserAccess($entry->getUser()->getId());
  
 -        $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry->getTags());
      }
  
      /**
          $em->persist($entry);
          $em->flush();
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 -
 -        return (new JsonResponse())->setJson($json);
 +        return $this->sendResponse($entry);
      }
  
      /**
          $em->persist($entry);
          $em->flush();
  
 -        $json = $this->get('serializer')->serialize($entry, 'json');
 +        return $this->sendResponse($entry);
 +    }
 +
 +    /**
 +     * Handles an entries list delete tags from them.
 +     *
 +     * @ApiDoc(
 +     *       parameters={
 +     *          {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
 +     *       }
 +     * )
 +     *
 +     * @return JsonResponse
 +     */
 +    public function deleteEntriesTagsListAction(Request $request)
 +    {
 +        $this->validateAuthentication();
 +
 +        $list = json_decode($request->query->get('list', []));
 +
 +        if (empty($list)) {
 +            return $this->sendResponse([]);
 +        }
 +
 +        // handle multiple urls
 +        $results = [];
 +
 +        foreach ($list as $key => $element) {
 +            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
 +                $element->url,
 +                $this->getUser()->getId()
 +            );
 +
 +            $results[$key]['url'] = $element->url;
 +            $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
 +
 +            $tags = $element->tags;
 +
 +            if (false !== $entry && !(empty($tags))) {
 +                $tags = explode(',', $tags);
 +                foreach ($tags as $label) {
 +                    $label = trim($label);
 +
 +                    $tag = $this->getDoctrine()
 +                        ->getRepository('WallabagCoreBundle:Tag')
 +                        ->findOneByLabel($label);
 +
 +                    if (false !== $tag) {
 +                        $entry->removeTag($tag);
 +                    }
 +                }
 +
 +                $em = $this->getDoctrine()->getManager();
 +                $em->persist($entry);
 +                $em->flush();
 +            }
 +        }
 +
 +        return $this->sendResponse($results);
 +    }
 +
 +    /**
 +     * Handles an entries list and add tags to them.
 +     *
 +     * @ApiDoc(
 +     *       parameters={
 +     *          {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
 +     *       }
 +     * )
 +     *
 +     * @return JsonResponse
 +     */
 +    public function postEntriesTagsListAction(Request $request)
 +    {
 +        $this->validateAuthentication();
 +
 +        $list = json_decode($request->query->get('list', []));
 +
 +        if (empty($list)) {
 +            return $this->sendResponse([]);
 +        }
 +
 +        $results = [];
 +
 +        // handle multiple urls
 +        foreach ($list as $key => $element) {
 +            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
 +                $element->url,
 +                $this->getUser()->getId()
 +            );
 +
 +            $results[$key]['url'] = $element->url;
 +            $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
 +
 +            $tags = $element->tags;
 +
 +            if (false !== $entry && !(empty($tags))) {
 +                $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
 +
 +                $em = $this->getDoctrine()->getManager();
 +                $em->persist($entry);
 +                $em->flush();
 +            }
 +        }
 +
 +        return $this->sendResponse($results);
 +    }
 +
 +    /**
 +     * Shortcut to send data serialized in json.
 +     *
 +     * @param mixed $data
 +     *
 +     * @return JsonResponse
 +     */
 +    private function sendResponse($data)
 +    {
 +        $json = $this->get('serializer')->serialize($data, 'json');
  
          return (new JsonResponse())->setJson($json);
      }
index 57319af73d4ecc5e1f1d89097e63b6717c674fcd,67421942f22cbcd652e697630b4ac497b4791d4f..1bd0b8fdd7be155d573572250350fc09d9fb7341
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
          #         or: 'One rule OR another'
          #         and: 'One rule AND another'
          #         matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
 -
 +        #         notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  entry:
      page_titles:
          # unread: 'Unread entries'
              # share_email_label: 'Email'
              # public_link: 'public link'
              # delete_public_link: 'delete public link'
-             download: 'Download'
+             # export: 'Export'
              # print: 'Print'
              problem:
                  label: 'Problemer?'
          original_article: 'original'
          # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
          created_at: 'Oprettelsesdato'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Gem ny artikel'
          placeholder: 'http://website.com'
          # page_title: 'Edit an entry'
          # title_label: 'Title'
          url_label: 'Url'
 -        # is_public_label: 'Public'
          save_label: 'Gem'
      public:
          # shared_by_wallabag: "This article has been shared by <a href=%wallabag_instance%'>wallabag</a>"
@@@ -512,8 -510,6 +512,8 @@@ user
          # delete: Delete
          # delete_confirm: Are you sure?
          # back_to_list: Back to list
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -532,7 -528,6 +532,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              # entry_already_saved: 'Entry already saved on %date%'
index a7bcecc6f5d57539bad8b655d6eb73cf1bfd9fbb,35cb4b5b9eb260c21f967f11524d976645018953..94bb329517648e3084ce747149ae7a6a638fd91e
@@@ -110,7 -110,6 +110,7 @@@ config
          annotations: Entferne ALLE Annotationen
          tags: Entferne ALLE Tags
          entries: Entferne ALLE Einträge
 +        # archived: Remove ALL archived entries
          confirm: Bist du wirklich sicher? (DIES KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN)
      form_password:
          description: "Hier kannst du dein Kennwort ändern. Dieses sollte mindestens acht Zeichen enthalten."
                  or: 'Eine Regel ODER die andere'
                  and: 'Eine Regel UND eine andere'
                  matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'E-Mail'
              public_link: 'Öffentlicher Link'
              delete_public_link: 'Lösche öffentlichen Link'
-             download: 'Herunterladen'
+             export: 'Exportieren'
              print: 'Drucken'
              problem:
                  label: 'Probleme?'
          original_article: 'original'
          annotations_on_the_entry: '{0} Keine Anmerkungen|{1} Eine Anmerkung|]1,Inf[ %count% Anmerkungen'
          created_at: 'Erstellungsdatum'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Neuen Artikel speichern'
          placeholder: 'https://website.de'
          page_title: 'Eintrag bearbeiten'
          title_label: 'Titel'
          url_label: 'URL'
 -        is_public_label: 'Öffentlich'
          save_label: 'Speichern'
      public:
          shared_by_wallabag: "Dieser Artikel wurde mittels <a href='%wallabag_instance%'>wallabag</a> geteilt"
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: Löschen
          delete_confirm: Bist du sicher?
          back_to_list: Zurück zur Liste
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      page_title: Ein Fehler ist aufgetreten
@@@ -533,7 -528,6 +533,7 @@@ flashes
              annotations_reset: Anmerkungen zurücksetzen
              tags_reset: Tags zurücksetzen
              entries_reset: Einträge zurücksetzen
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Eintrag bereits am %date% gespeichert'
index 1ef2874d26d2750713200168192efde7da84eee9,e0ef3212e4f3f375c35a885445888f099ca9727f..3a006a0ec28c4484447e0ed1c046d89739c905d7
@@@ -110,7 -110,6 +110,7 @@@ config
          annotations: Remove ALL annotations
          tags: Remove ALL tags
          entries: Remove ALL entries
 +        archived: Remove ALL archived entries
          confirm: Are you really sure? (THIS CAN'T BE UNDONE)
      form_password:
          description: "You can change your password here. Your new password should by at least 8 characters long."
                  or: 'One rule OR another'
                  and: 'One rule AND another'
                  matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
 +                notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'Email'
              public_link: 'public link'
              delete_public_link: 'delete public link'
-             download: 'Download'
+             export: 'Export'
              print: 'Print'
              problem:
                  label: 'Problems?'
          original_article: 'original'
          annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
          created_at: 'Creation date'
 +        published_at: 'Publication date'
 +        published_by: 'Published by'
      new:
          page_title: 'Save new entry'
          placeholder: 'http://website.com'
          page_title: 'Edit an entry'
          title_label: 'Title'
          url_label: 'Url'
 -        is_public_label: 'Public'
          save_label: 'Save'
      public:
          shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: Delete
          delete_confirm: Are you sure?
          back_to_list: Back to list
 +    search:
 +        placeholder: Filter by username or email
  
  error:
      page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              annotations_reset: Annotations reset
              tags_reset: Tags reset
              entries_reset: Entries reset
 +            archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Entry already saved on %date%'
index 6cd079b077352ffe8284a990a3900f17825785d5,2f769b7e5cb3c7b53d58f0b34af995080fe7325d..ca5d9b2c0aa74b352a847159e33c899275370267
@@@ -110,7 -110,6 +110,7 @@@ config
          annotations: Eliminar TODAS las anotaciones
          tags: Eliminar TODAS las etiquetas
          entries: Eliminar TODOS los artículos
 +        # archived: Remove ALL archived entries
          confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER)
      form_password:
          description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres."
                  or: 'Una regla U otra'
                  and: 'Una regla Y la otra'
                  matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'e-mail'
              public_link: 'enlace público'
              delete_public_link: 'eliminar enlace público'
-             download: 'Descargar'
+             export: 'Exportar'
              print: 'Imprimir'
              problem:
                  label: '¿Algún problema?'
          original_article: 'original'
          annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones'
          created_at: 'Fecha de creación'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Guardar un nuevo artículo'
          placeholder: 'http://sitioweb.com'
          page_title: 'Editar un artículo'
          title_label: 'Título'
          url_label: 'URL'
 -        is_public_label: 'Es público'
          save_label: 'Guardar'
      public:
          shared_by_wallabag: "Este artículo se ha compartido con <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: Eliminar
          delete_confirm: ¿Estás seguro?
          back_to_list: Volver a la lista
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      page_title: Ha ocurrido un error
@@@ -533,7 -528,6 +533,7 @@@ flashes
              annotations_reset: Anotaciones reiniciadas
              tags_reset: Etiquetas reiniciadas
              entries_reset: Artículos reiniciados
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Artículo ya guardado el %fecha%'
index fb6e315ec6dc16a89d30743783526228c15249af,d9d76b32c2d2bb6209660e937dbe8e2c9668f04d..ecd8f64d0e8162c1eb0a7eade9829662cf6f5ca6
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
          #         or: 'One rule OR another'
          #         and: 'One rule AND another'
          #         matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
 +        #         notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'نشانی ایمیل'
              # public_link: 'public link'
              # delete_public_link: 'delete public link'
-             download: 'بارگیری'
+             export: 'بارگیری'
              print: 'چاپ'
              problem:
                  label: 'مشکلات؟'
          original_article: 'اصلی'
          annotations_on_the_entry: '{0} بدون حاشیه|{1} یک حاشیه|]1,Inf[ %nbحاشیه% annotations'
          created_at: 'زمان ساخت'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'ذخیرهٔ مقالهٔ تازه'
          placeholder: 'http://website.com'
          page_title: 'ویرایش مقاله'
          title_label: 'عنوان'
          url_label: 'نشانی'
 -        is_public_label: 'عمومی'
          save_label: 'ذخیره'
      public:
          # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          # delete: Delete
          # delete_confirm: Are you sure?
          # back_to_list: Back to list
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
index ad886363550c7650670c5bc0b5fe1389b186d323,efddc46a484ef5934f91ef89413d87c65824622f..84706459d078a52c610b9e9e6fcee5ac489ed3e6
@@@ -46,7 -46,7 +46,7 @@@ footer
          social: "Social"
          powered_by: "propulsé par"
          about: "À propos"
 -    stats: Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !
 +    stats: "Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !"
  
  config:
      page_title: "Configuration"
              300_word: "Je lis environ 300 mots par minute"
              400_word: "Je lis environ 400 mots par minute"
          action_mark_as_read:
 -            label: 'Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?'
 -            redirect_homepage: "À la page d'accueil"
 -            redirect_current_page: 'À la page courante'
 -        pocket_consumer_key_label: Clé d’authentification Pocket pour importer les données
 -        android_configuration: Configurez votre application Android
 -        help_theme: "L'affichage de wallabag est personnalisable. C'est ici que vous choisissez le thème que vous préférez."
 -        help_items_per_page: "Vous pouvez définir le nombre d'articles affichés sur chaque page."
 +            label: "Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?"
 +            redirect_homepage: "À la page daccueil"
 +            redirect_current_page: "À la page courante"
 +        pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données"
 +        android_configuration: "Configurez votre application Android"
 +        help_theme: "L’affichage de wallabag est personnalisable. C’est ici que vous choisissez le thème que vous préférez."
 +        help_items_per_page: "Vous pouvez définir le nombre darticles affichés sur chaque page."
          help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article."
 -        help_language: "Vous pouvez définir la langue de l'interface de wallabag."
 -        help_pocket_consumer_key: "Nécessaire pour l'import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
 +        help_language: "Vous pouvez définir la langue de linterface de wallabag."
 +        help_pocket_consumer_key: "Nécessaire pour limport depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
      form_rss:
          description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d’abord créer un jeton."
          token_label: "Jeton RSS"
          twoFactorAuthentication_label: "Double authentification"
          help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email."
          delete:
 -            title: Supprimer mon compte (attention danger !)
 -            description: Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c'est IRRÉVERSIBLE). Vous serez ensuite déconnecté.
 -            confirm: Vous êtes vraiment sûr ? (C'EST IRRÉVERSIBLE)
 -            button: 'Supprimer mon compte'
 +            title: "Supprimer mon compte (attention danger !)"
 +            description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté."
 +            confirm: "Vous êtes vraiment sûr ? (C’EST IRRÉVERSIBLE)"
 +            button: "Supprimer mon compte"
      reset:
 -        title: Réinitialisation (attention danger !)
 -        description: En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !
 -        annotations: Supprimer TOUTES les annotations
 -        tags: Supprimer TOUS les tags
 -        entries: Supprimer TOUS les articles
 -        confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE)
 +        title: "Réinitialisation (attention danger !)"
 +        description: "En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !"
 +        annotations: "Supprimer TOUTES les annotations"
 +        tags: "Supprimer TOUS les tags"
 +        entries: "Supprimer TOUS les articles"
 +        archived: "Supprimer TOUS les articles archivés"
 +        confirm: "Êtes-vous vraiment vraiment sûr ? (C’EST IRRÉVERSIBLE)"
      form_password:
          description: "Vous pouvez changer ici votre mot de passe. Le mot de passe doit contenir au moins 8 caractères."
          old_password_label: "Mot de passe actuel"
                  or: "Une règle OU l’autre"
                  and: "Une règle ET l’autre"
                  matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
 +                notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>"
  
  entry:
      page_titles:
          archived: "Articles lus"
          filtered: "Articles filtrés"
          filtered_tags: "Articles filtrés par tags :"
 -        filtered_search: 'Articles filtrés par recherche :'
 +        filtered_search: "Articles filtrés par recherche :"
          untagged: "Article sans tag"
      list:
          number_on_the_page: "{0} Il n’y a pas d’article.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
          preview_picture_label: "A une photo"
          preview_picture_help: "Photo"
          language_label: "Langue"
 -        http_status_label: 'Statut HTTP'
 +        http_status_label: "Statut HTTP"
          reading_time:
              label: "Durée de lecture en minutes"
              from: "de"
              share_email_label: "Courriel"
              public_link: "Lien public"
              delete_public_link: "Supprimer le lien public"
-             download: "Télécharger"
+             export: "Exporter"
              print: "Imprimer"
              problem:
                  label: "Un problème ?"
          original_article: "original"
          annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations"
          created_at: "Date de création"
 +        published_at: "Date de publication"
 +        published_by: "Publié par"
      new:
          page_title: "Sauvegarder un nouvel article"
          placeholder: "http://website.com"
          page_title: "Éditer un article"
          title_label: "Titre"
          url_label: "Adresse"
 -        is_public_label: "Public"
          save_label: "Enregistrer"
      public:
          shared_by_wallabag: "Cet article a été partagé par <a href=\"%wallabag_instance%\">wallabag</a>"
@@@ -298,32 -295,32 +298,32 @@@ howto
      bookmarklet:
          description: "Glissez et déposez ce lien dans votre barre de favoris :"
      shortcuts:
 -        page_description: Voici les raccourcis disponibles dans wallabag.
 -        shortcut: Raccourci
 -        action: Action
 -        all_pages_title: Raccourcis disponibles dans toutes les pages
 -        go_unread: Afficher les articles non lus
 -        go_starred: Afficher les articles favoris
 -        go_archive:  Afficher les articles lus
 -        go_all: Afficher tous les articles
 -        go_tags: Afficher les tags
 -        go_config: Aller à la configuration
 -        go_import: Aller aux imports
 -        go_developers: Aller à la section Développeurs
 -        go_howto: Afficher l'aide (cette page !)
 -        go_logout: Se déconnecter
 -        list_title: Raccourcis disponibles dans les pages de liste
 -        search: Afficher le formulaire de recherche
 -        article_title: Raccourcis disponibles quand on affiche un article
 -        open_original: Ouvrir l'URL originale de l'article
 -        toggle_favorite: Changer le statut Favori de l'article
 -        toggle_archive: Changer le status Lu de l'article
 -        delete: Supprimer l'article
 -        material_title: Raccourcis disponibles avec le thème Material uniquement
 -        add_link: Ajouter un nouvel article
 -        hide_form: Masquer le formulaire courant (recherche ou nouvel article)
 -        arrows_navigation: Naviguer à travers les articles
 -        open_article: Afficher l'article sélectionné
 +        page_description: "Voici les raccourcis disponibles dans wallabag."
 +        shortcut: "Raccourci"
 +        action: "Action"
 +        all_pages_title: "Raccourcis disponibles dans toutes les pages"
 +        go_unread: "Afficher les articles non lus"
 +        go_starred: "Afficher les articles favoris"
 +        go_archive:  "Afficher les articles lus"
 +        go_all: "Afficher tous les articles"
 +        go_tags: "Afficher les tags"
 +        go_config: "Aller à la configuration"
 +        go_import: "Aller aux imports"
 +        go_developers: "Aller à la section Développeurs"
 +        go_howto: "Afficher l’aide (cette page !)"
 +        go_logout: "Se déconnecter"
 +        list_title: "Raccourcis disponibles dans les pages de liste"
 +        search: "Afficher le formulaire de recherche"
 +        article_title: "Raccourcis disponibles quand on affiche un article"
 +        open_original: "Ouvrir l’URL originale de l’article"
 +        toggle_favorite: "Changer le statut Favori de l’article"
 +        toggle_archive: "Changer le status Lu de l’article"
 +        delete: "Supprimer l’article"
 +        material_title: "Raccourcis disponibles avec le thème Material uniquement"
 +        add_link: "Ajouter un nouvel article"
 +        hide_form: "Masquer le formulaire courant (recherche ou nouvel article)"
 +        arrows_navigation: "Naviguer à travers les articles"
 +        open_article: "Afficher l’article sélectionné"
  
  quickstart:
      page_title: "Pour bien débuter"
@@@ -385,8 -382,8 +385,8 @@@ tag
          number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
          see_untagged_entries: "Voir les articles sans tag"
      new:
 -        add: 'Ajouter'
 -        placeholder: 'Vous pouvez ajouter plusieurs tags, séparés par une virgule.'
 +        add: "Ajouter"
 +        placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
  
  import:
      page_title: "Importer"
          how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l’importer."
      worker:
          enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :"
 -        download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones."
 +        download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l’import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d’activer les imports asynchrones."
      firefox:
          page_title: "Import > Firefox"
          description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde… ». Vous allez récupérer un fichier .json. </p>"
@@@ -489,16 -486,16 +489,16 @@@ developer
          back: "Retour"
  
  user:
 -    page_title: Gestion des utilisateurs
 -    new_user: Créer un nouvel utilisateur
 -    edit_user: Éditer un utilisateur existant
 -    description: Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)
 +    page_title: "Gestion des utilisateurs"
 +    new_user: "Créer un nouvel utilisateur"
 +    edit_user: "Éditer un utilisateur existant"
 +    description: "Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)"
      list:
 -        actions: Actions
 -        edit_action: Éditer
 -        yes: Oui
 -        no: Non
 -        create_new_one: Créer un nouvel utilisateur
 +        actions: "Actions"
 +        edit_action: "Éditer"
 +        yes: "Oui"
 +        no: "Non"
 +        create_new_one: "Créer un nouvel utilisateur"
      form:
          username_label: "Nom d’utilisateur"
          name_label: "Nom"
          delete: "Supprimer"
          delete_confirm: "Voulez-vous vraiment ?"
          back_to_list: "Revenir à la liste"
 +    search:
 +        placeholder: "Filtrer par nom d’utilisateur ou email"
  
  error:
 -    page_title: Une erreur est survenue
 +    page_title: "Une erreur est survenue"
  
  flashes:
      config:
              tagging_rules_updated: "Règles mises à jour"
              tagging_rules_deleted: "Règle supprimée"
              rss_token_updated: "Jeton RSS mis à jour"
 -            annotations_reset: Annotations supprimées
 -            tags_reset: Tags supprimés
 -            entries_reset: Articles supprimés
 +            annotations_reset: "Annotations supprimées"
 +            tags_reset: "Tags supprimés"
 +            entries_reset: "Articles supprimés"
 +            archived_reset: "Articles archivés supprimés"
      entry:
          notice:
              entry_already_saved: "Article déjà sauvegardé le %date%"
              client_deleted: "Client %name% supprimé"
      user:
          notice:
 -            added: 'Utilisateur "%username%" ajouté'
 -            updated: 'Utilisateur "%username%" mis à jour'
 -            deleted: 'Utilisateur "%username%" supprimé'
 +            added: "Utilisateur \"%username%\" ajouté"
 +            updated: "Utilisateur \"%username%\" mis à jour"
 +            deleted: "Utilisateur \"%username%\" supprimé"
index 5a9605ff2c4ae85d58c870f927dc1d2ac68d3230,3f7c7010b2222458074d3f7a972ef9e6fb0b9dac..a8baa96fd6fee0189eaad7b9bd7c6a5ac1b27686
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
                  or: "Una regola O un'altra"
                  and: "Una regola E un'altra"
                  matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'E-mail'
              # public_link: 'public link'
              # delete_public_link: 'delete public link'
-             download: 'Download'
+             export: 'Esporta'
              print: 'Stampa'
              problem:
                  label: 'Problemi?'
          original_article: 'originale'
          annotations_on_the_entry: '{0} Nessuna annotazione|{1} Una annotazione|]1,Inf[ %count% annotazioni'
          created_at: 'Data di creazione'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Salva un nuovo contenuto'
          placeholder: 'http://website.com'
          page_title: 'Modifica voce'
          title_label: 'Titolo'
          url_label: 'Url'
 -        is_public_label: 'Pubblico'
          save_label: 'Salva'
      public:
          # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          # delete: Delete
          # delete_confirm: Are you sure?
          # back_to_list: Back to list
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Contenuto già salvato in data %date%'
index 5e9834882bd68cfc059d0e17996e93a6abafb6b0,913e3bcb4a23b0a670dea63e7cbaad9758956220..8f39ce0569ba532b68b2bfcbf0739a17d0af8b8f
@@@ -110,7 -110,6 +110,7 @@@ config
          annotations: Levar TOTAS las anotacions
          tags: Levar TOTAS las etiquetas
          entries: Levar TOTES los articles
 +        archived: Levar TOTES los articles archivats
          confirm: Sètz vertadièrament segur ? (ES IRREVERSIBLE)
      form_password:
          description: "Podètz cambiar vòstre senhal aquí. Vòstre senhal deu èsser long d'almens 8 caractèrs."
                  not_equal_to: 'Diferent de…'
                  or: "Una règla O l'autra"
                  and: "Una règla E l'autra"
 -                matches: 'Teste se un <i>subjècte</i> correspond a una <i>recerca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>'
 +                matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>'
 +                notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example : <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
          archived_label: 'Legits'
          starred_label: 'Favorits'
          unread_label: 'Pas legits'
 -        preview_picture_label: 'A una fotò'
 -        preview_picture_help: 'Fotò'
 +        preview_picture_label: 'A un imatge'
 +        preview_picture_help: 'Imatge'
          language_label: 'Lenga'
          http_status_label: 'Estatut HTTP'
          reading_time:
              share_email_label: 'Corrièl'
              public_link: 'ligam public'
              delete_public_link: 'suprimir lo ligam public'
-             download: 'Telecargar'
+             export: 'Exportar'
              print: 'Imprimir'
              problem:
                  label: 'Un problèma ?'
          original_article: 'original'
          annotations_on_the_entry: "{0} Pas cap d'anotacion|{1} Una anotacion|]1,Inf[ %count% anotacions"
          created_at: 'Data de creacion'
 +        published_at: 'Data de publicacion'
 +        published_by: 'Publicat per'
      new:
          page_title: 'Enregistrar un novèl article'
          placeholder: 'http://website.com'
          page_title: 'Modificar un article'
          title_label: 'Títol'
          url_label: 'Url'
 -        is_public_label: 'Public'
          save_label: 'Enregistrar'
      public:
          shared_by_wallabag: "Aqueste article es estat partejat per <a href='%wallabag_instance%'>wallabag</a>"
@@@ -344,8 -341,8 +344,8 @@@ quickstart
          new_user: 'Crear un novèl utilizaire'
          analytics: 'Configurar las estadisticas'
          sharing: 'Activar de paramètres de partatge'
 -        export: 'Configurar los expòrt'
 -        import: 'Configurar los impòrt'
 +        export: 'Configurar los expòrts'
 +        import: 'Configurar los impòrts'
      first_steps:
          title: 'Primièrs passes'
          description: "Ara wallabag es ben configurat, es lo moment d'archivar lo web. Podètz clicar sul signe + a man drecha amont per ajustar un ligam."
@@@ -461,7 -458,7 +461,7 @@@ developer
          action: 'Suprimir aqueste client'
      client:
          page_title: 'Gestion dels clients API > Novèl client'
 -        page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion."
 +        page_description: "Anatz crear un novèl client. Mercés de garnir l'url de redireccion cap a vòstra aplicacion."
          form:
              name_label: "Nom del client"
              redirect_uris_label: 'URLs de redireccion'
          page_description: 'Vaquí los paramètres de vòstre client.'
          field_name: 'Nom del client'
          field_id: 'ID Client'
 -        field_secret: 'Clau secreta'
 +        field_secret: 'Clau secrèta'
          back: 'Retour'
          read_howto: 'Legir "cossí crear ma primièra aplicacion"'
      howto:
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: 'Suprimir'
          delete_confirm: 'Sètz segur ?'
          back_to_list: 'Tornar a la lista'
 +    search:
 +        placeholder: "Filtrar per nom d'utilizaire o corrièl"
  
  error:
      page_title: Una error s'es produsida
@@@ -533,7 -528,6 +533,7 @@@ flashes
              annotations_reset: Anotacions levadas
              tags_reset: Etiquetas levadas
              entries_reset: Articles levats
 +            archived_reset: Articles archivat suprimits
      entry:
          notice:
              entry_already_saved: 'Article ja salvargardat lo %date%'
index fea90440a50a432f0bd844b8f080a2d91bb1fc92,b990a6b933367f428fb77839a812f2dff5eba8f3..a6e0c10fd127c7d4a207f09c69cdc02af08a6744
@@@ -110,7 -110,6 +110,7 @@@ config
          annotations: Usuń WSZYSTKIE adnotacje
          tags: Usuń WSZYSTKIE tagi
          entries: usuń WSZYTSTKIE wpisy
 +        # archived: Remove ALL archived entries
          confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć)
      form_password:
          description: "Tutaj możesz zmienić swoje hasło. Twoje nowe hasło powinno mieć conajmniej 8 znaków."
                  or: 'Jedna reguła LUB inna'
                  and: 'Jedna reguła I inna'
                  matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'Adres email'
              public_link: 'Publiczny link'
              delete_public_link: 'Usuń publiczny link'
-             download: 'Pobierz'
+             export: 'Export'
              print: 'Drukuj'
              problem:
                  label: 'Problemy'
          original_article: 'oryginalny'
          annotations_on_the_entry: '{0} Nie ma adnotacji |{1} Jedna adnotacja |]1,Inf[ %count% adnotacji'
          created_at: 'Czas stworzenia'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Zapisz nowy wpis'
          placeholder: 'http://website.com'
          page_title: 'Edytuj wpis'
          title_label: 'Tytuł'
          url_label: 'Adres URL'
 -        is_public_label: 'Publiczny'
          save_label: 'Zapisz'
      public:
          shared_by_wallabag: "Ten artykuł został udostępniony przez <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: Usuń
          delete_confirm: Jesteś pewien?
          back_to_list: Powrót do listy
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      page_title: Wystąpił błąd
@@@ -533,7 -528,6 +533,7 @@@ flashes
              annotations_reset: Zresetuj adnotacje
              tags_reset: Zresetuj tagi
              entries_reset: Zresetuj wpisy
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Wpis już został dodany %date%'
index c59991f89931263c6ebe02cfe78683a63155ae29,fd87ca44aee6badd0f8ca415fd189f14f6b61cfc..a9473591f391139f87d843c8a7ed10126e99fa61
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
                  or: 'Uma regra OU outra'
                  and: 'Uma regra E outra'
                  matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'E-mail'
              public_link: 'link público'
              delete_public_link: 'apagar link público'
-             download: 'Download'
+             export: 'Exportar'
              print: 'Imprimir'
              problem:
                  label: 'Problemas?'
          original_article: 'original'
          annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações'
          created_at: 'Data de criação'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Salvar nova entrada'
          placeholder: 'http://website.com'
          page_title: 'Editar uma entrada'
          title_label: 'Título'
          url_label: 'Url'
 -        is_public_label: 'Público'
          save_label: 'Salvar'
      public:
          shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          delete: 'Apagar'
          delete_confirm: 'Tem certeza?'
          back_to_list: 'Voltar para a lista'
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Entrada já foi salva em %date%'
index 5846b7cc30076d3f8604b6e5a8b477c3e437cb27,14954b530f40a6f35abeb206465699e5414d14a4..80d78a01e491c60cbd02c626c280e5db7786e4a0
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
          #         or: 'One rule OR another'
          #         and: 'One rule AND another'
          #         matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
 +        #         notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'E-mail'
              # public_link: 'public link'
              # delete_public_link: 'delete public link'
-             download: 'Descarcă'
+             export: 'Descarcă'
              # print: 'Print'
              problem:
                  label: 'Probleme?'
          original_article: 'original'
          # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
          created_at: 'Data creării'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Salvează un nou articol'
          placeholder: 'http://website.com'
          # page_title: 'Edit an entry'
          # title_label: 'Title'
          url_label: 'Url'
 -        # is_public_label: 'Public'
          save_label: 'Salvează'
      public:
          # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          # delete: Delete
          # delete_confirm: Are you sure?
          # back_to_list: Back to list
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              # entry_already_saved: 'Entry already saved on %date%'
index 430fb96bcf129fe2f9c8f29f145f5413a55d8bad,778a5515d57e57532222020ab7eda216c1e39a79..2896c82344900f89465bee857ae5012ec729965d
@@@ -110,7 -110,6 +110,7 @@@ config
          # annotations: Remove ALL annotations
          # tags: Remove ALL tags
          # entries: Remove ALL entries
 +        # archived: Remove ALL archived entries
          # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
      form_password:
          # description: "You can change your password here. Your new password should by at least 8 characters long."
                  or: 'Bir kural veya birbaşkası'
                  and: 'Bir kural ve diğeri'
                  # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
 +                # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
  
  entry:
      page_titles:
              share_email_label: 'E-posta'
              # public_link: 'public link'
              # delete_public_link: 'delete public link'
-             download: 'İndir'
+             export: 'Dışa Aktar'
              # print: 'Print'
              problem:
                  label: 'Bir sorun mu var?'
          original_article: 'orijinal'
          # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
          created_at: 'Oluşturulma tarihi'
 +        # published_at: 'Publication date'
 +        # published_by: 'Published by'
      new:
          page_title: 'Yeni makaleyi kaydet'
          placeholder: 'http://website.com'
          page_title: 'Makaleyi düzenle'
          title_label: 'Başlık'
          url_label: 'Url'
 -        is_public_label: 'Herkes tarafından erişime açık olsun mu?'
          save_label: 'Kaydet'
      public:
          # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
@@@ -513,8 -510,6 +513,8 @@@ user
          # delete: Delete
          # delete_confirm: Are you sure?
          # back_to_list: Back to list
 +    search:
 +        # placeholder: Filter by username or email
  
  error:
      # page_title: An error occurred
@@@ -533,7 -528,6 +533,7 @@@ flashes
              # annotations_reset: Annotations reset
              # tags_reset: Tags reset
              # entries_reset: Entries reset
 +            # archived_reset: Archived entries deleted
      entry:
          notice:
              entry_already_saved: 'Entry already saved on %date%'
index 47e6e8c32c91ebe6d5dd1738d0fb5604c538626d,af53084f3d9b89f1c461610de1b1135619f25f56..58e08cbcbb4479793a47fe483b338204e042cf13
      <nav class="hide-on-large-only">
          <div class="nav-wrapper cyan darken-1">
              <ul>
 +                <li>
 +                    <a href="#" data-activates="slide-out" class="button-collapse">
 +                        <i class="material-icons">menu</i>
 +                    </a>
 +                </li>
                  <li>
                      <a class="waves-effect" href="{{ path('homepage') }}">
                          <i class="material-icons">exit_to_app</i>
                          <i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
                      </a>
                  </li>
 -                <li>
 -                    <a href="#" data-activates="slide-out" class="button-collapse right">
 -                        <i class="material-icons">menu</i>
 -                    </a>
 -                </li>
              </ul>
          </div>
      </nav>
                      {% endif %}
                      {% if craue_setting('share_shaarli') %}
                          <li>
 -                        <a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank">
 -                                <i class="tool icon-image icon-image--shaarli" title="shaarli"></i>
 +                        <a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" title="shaarli" class="tool icon-image shaarli">
                                  <span>shaarli</span>
                              </a>
                          </li>
                      {% endif %}
 +                    {% if craue_setting('share_scuttle') %}
 +                        <li>
 +                        <a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" title="scuttle" class="tool icon-image scuttle">
 +                                <span>scuttle</span>
 +                            </a>
 +                        </li>
 +                    {% endif %}
                      {% if craue_setting('share_diaspora') %}
                          <li>
 -                            <a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;notes=&amp;v=1&amp;noui=1&amp;jump=doclose" target="_blank">
 -                                <i class="tool icon-image icon-image--diaspora" title="diaspora"></i>
 +                            <a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;notes=&amp;v=1&amp;noui=1&amp;jump=doclose" target="_blank" class="tool icon-image diaspora" title="diaspora">
                                  <span>diaspora*</span>
                              </a>
                          </li>
                      {% endif %}
                      {% if craue_setting('share_unmark') %}
                          <li>
 -                            <a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|striptags|url_encode}}&amp;v=6" target="_blank">
 -                                <i class="tool icon-image icon-image--unmark" title="unmark"></i>
 +                            <a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|striptags|url_encode}}&amp;v=6" target="_blank" class="tool icon-image unmark" title="unmark">
                                  <span>unmark.it</span>
                              </a>
                          </li>
                      {% endif %}
                      {% if craue_setting('carrot') %}
                          <li>
 -                            <a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}" target="_blank" title="carrot">
 -                                <i class="tool icon-image icon-image--carrot"></i>
 +                            <a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}" target="_blank" title="carrot" class="tool icon-image carrot">
                                  <span>Carrot</span>
                              </a>
                          </li>
                      {% endif %}
                      {% if craue_setting('share_mail') %}
                          <li>
 -                            <a href="mailto:?subject={{ entry.title|striptags|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" title="{{ 'entry.view.left_menu.share_email_label'|trans }}" class="tool email icon icon-mail">
 +                            <a href="mailto:?subject={{ entry.title|striptags|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" title="{{ 'entry.view.left_menu.share_email_label'|trans }}" class="tool icon">
 +                                <i class="material-icons vertical-align-middle">mail</i>
                                  <span>{{ 'entry.view.left_menu.share_email_label'|trans }}</span>
                              </a>
                          </li>
          <li class="bold">
              <a class="waves-effect collapsible-header">
                  <i class="material-icons small">file_download</i>
-                 <span>{{ 'entry.view.left_menu.download'|trans }}</span>
+                 <span>{{ 'entry.view.left_menu.export'|trans }}</span>
              </a>
              <div class="collapsible-body">
                  <ul>
              <h1>{{ entry.title|striptags|raw }} <a href="{{ path('edit', { 'id': entry.id }) }}" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1>
          </header>
          <aside>
 -            <ul class="tools">
 -                <li>
 -                    {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}
 -                </li>
 -                <li>
 -                    <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
 -                    {{ entry.createdAt|date('Y-m-d') }}
 -                </li>
 -                <li>
 -                    <i class="material-icons link">link</i>
 -                    <a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
 -                        {{ entry.domainName|removeWww }}
 -                    </a>
 -                </li>
 -                <li>
 -                    <i class="material-icons link">comment</i>
 -                    {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}
 -                </li>
 -                <li id="list">
 +            <div class="tools">
 +                <ul class="stats">
 +                    <li>
 +                        {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}
 +                    </li>
 +                    <li>
 +                        <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
 +                        {{ entry.createdAt|date('Y-m-d H:i') }}
 +                    </li>
 +                    {% if entry.publishedAt is not null %}
 +                    <li>
 +                        <i class="material-icons" title="{{ 'entry.view.published_at'|trans }}">create</i>
 +                        {{ entry.publishedAt|date('Y-m-d H:i') }}
 +                    </li>
 +                    {% endif %}
 +                    {% if entry.publishedBy is not empty %}
 +                        <li>
 +                            <i class="material-icons" title="{{ 'entry.view.published_by'|trans }}">person</i>
 +                            {% for author in entry.publishedBy %}
 +                                {{ author }}{% if not loop.last %}, {% endif %}
 +                            {% endfor %}
 +                        </li>
 +                    {% endif %}
 +                    <li>
 +                        <i class="material-icons link">link</i>
 +                        <a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
 +                            {{ entry.domainName|removeWww }}
 +                        </a>
 +                    </li>
 +                    <li>
 +                        <i class="material-icons link">comment</i>
 +                        {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}
 +                    </li>
 +                </ul>
 +                <ul class="tags">
                      {% for tag in entry.tags %}
 -                        <div class="chip">
 -                        <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i class="material-icons">delete</i></a>
 -                        </div>
 +                        <li class="chip">
 +                            <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i class="material-icons vertical-align-middle">delete</i></a>
 +                        </li>
                      {% endfor %}
 -                </li>
 -            </ul>
 +                </ul>
 +            </div>
  
              <div class="input-field nav-panel-add-tag" style="display: none">
                  {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
index 4f49f040f9e6d9ccfb4db6ef0e20fa0c7740b064,0a65f9cee0628a78a38ab6b2fd7ce259c1383371..bf7d373aa94d57f8716ed59f5f6d02f37ccd3b6f
@@@ -4,6 -4,7 +4,7 @@@ namespace Tests\Wallabag\ApiBundle\Cont
  
  use Tests\Wallabag\ApiBundle\WallabagApiTestCase;
  use Wallabag\CoreBundle\Entity\Tag;
+ use Wallabag\CoreBundle\Helper\ContentProxy;
  
  class EntryRestControllerTest extends WallabagApiTestCase
  {
          $entry = $this->client->getContainer()
              ->get('doctrine.orm.entity_manager')
              ->getRepository('WallabagCoreBundle:Entry')
 -            ->findOneByUser(1);
 +            ->findOneByUser(1, ['id' => 'asc']);
  
          if (!$entry) {
              $this->markTestSkipped('No content found in db.');
          $this->assertEquals(false, $content['is_starred']);
          $this->assertEquals('New title for my article', $content['title']);
          $this->assertEquals(1, $content['user_id']);
 -        $this->assertCount(1, $content['tags']);
 +        $this->assertCount(2, $content['tags']);
      }
  
      public function testPostSameEntry()
          $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
          $this->assertEquals(true, $content['is_archived']);
          $this->assertEquals(false, $content['is_starred']);
 -        $this->assertCount(2, $content['tags']);
 +        $this->assertCount(3, $content['tags']);
      }
  
+     public function testPostEntryWhenFetchContentFails()
+     {
+         /** @var \Symfony\Component\DependencyInjection\Container $container */
+         $container = $this->client->getContainer();
+         $contentProxy = $this->getMockBuilder(ContentProxy::class)
+             ->disableOriginalConstructor()
+             ->setMethods(['updateEntry'])
+             ->getMock();
+         $contentProxy->expects($this->any())
+             ->method('updateEntry')
+             ->willThrowException(new \Exception('Test Fetch content fails'));
+         $container->set('wallabag_core.content_proxy', $contentProxy);
+         try {
+             $this->client->request('POST', '/api/entries.json', [
+                 'url' => 'http://www.example.com/',
+             ]);
+             $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
+             $content = json_decode($this->client->getResponse()->getContent(), true);
+             $this->assertGreaterThan(0, $content['id']);
+             $this->assertEquals('http://www.example.com/', $content['url']);
+         } finally {
+             // Remove the created entry to avoid side effects on other tests
+             if (isset($content['id'])) {
+                 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
+                 $entry = $em->getReference('WallabagCoreBundle:Entry', $content['id']);
+                 $em->remove($entry);
+                 $em->flush();
+             }
+         }
+     }
      public function testPostArchivedAndStarredEntry()
      {
          $this->client->request('POST', '/api/entries.json', [
  
          $content = json_decode($this->client->getResponse()->getContent(), true);
  
 -        $this->assertEquals(true, $content['exists']);
 +        $this->assertEquals(2, $content['exists']);
      }
  
      public function testGetEntriesExistsWithManyUrls()
  
          $this->assertArrayHasKey($url1, $content);
          $this->assertArrayHasKey($url2, $content);
 -        $this->assertEquals(true, $content[$url1]);
 +        $this->assertEquals(2, $content[$url1]);
          $this->assertEquals(false, $content[$url2]);
      }
  
  
          $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
      }
 +
 +    public function testPostEntriesTagsListAction()
 +    {
 +        $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager')
 +            ->getRepository('WallabagCoreBundle:Entry')
 +            ->findByUrlAndUserId('http://0.0.0.0/entry4', 1);
 +
 +        $tags = $entry->getTags();
 +
 +        $this->assertCount(2, $tags);
 +
 +        $list = [
 +            [
 +                'url' => 'http://0.0.0.0/entry4',
 +                'tags' => 'new tag 1, new tag 2',
 +            ],
 +        ];
 +
 +        $this->client->request('POST', '/api/entries/tags/lists?list='.json_encode($list));
 +
 +        $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
 +
 +        $content = json_decode($this->client->getResponse()->getContent(), true);
 +
 +        $this->assertInternalType('int', $content[0]['entry']);
 +        $this->assertEquals('http://0.0.0.0/entry4', $content[0]['url']);
 +
 +        $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager')
 +            ->getRepository('WallabagCoreBundle:Entry')
 +            ->findByUrlAndUserId('http://0.0.0.0/entry4', 1);
 +
 +        $tags = $entry->getTags();
 +        $this->assertCount(4, $tags);
 +    }
 +
 +    public function testDeleteEntriesTagsListAction()
 +    {
 +        $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager')
 +            ->getRepository('WallabagCoreBundle:Entry')
 +            ->findByUrlAndUserId('http://0.0.0.0/entry4', 1);
 +
 +        $tags = $entry->getTags();
 +
 +        $this->assertCount(4, $tags);
 +
 +        $list = [
 +            [
 +                'url' => 'http://0.0.0.0/entry4',
 +                'tags' => 'new tag 1, new tag 2',
 +            ],
 +        ];
 +
 +        $this->client->request('DELETE', '/api/entries/tags/list?list='.json_encode($list));
 +    }
 +
 +    public function testPostEntriesListAction()
 +    {
 +        $list = [
 +            'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html',
 +            'http://0.0.0.0/entry2',
 +        ];
 +
 +        $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list));
 +
 +        $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
 +
 +        $content = json_decode($this->client->getResponse()->getContent(), true);
 +
 +        $this->assertInternalType('int', $content[0]['entry']);
 +        $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']);
 +
 +        $this->assertInternalType('int', $content[1]['entry']);
 +        $this->assertEquals('http://0.0.0.0/entry2', $content[1]['url']);
 +    }
 +
 +    public function testDeleteEntriesListAction()
 +    {
 +        $list = [
 +            'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html',
 +            'http://0.0.0.0/entry3',
 +        ];
 +
 +        $this->client->request('DELETE', '/api/entries/list?urls='.json_encode($list));
 +
 +        $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
 +
 +        $content = json_decode($this->client->getResponse()->getContent(), true);
 +
 +        $this->assertTrue($content[0]['entry']);
 +        $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']);
 +
 +        $this->assertFalse($content[1]['entry']);
 +        $this->assertEquals('http://0.0.0.0/entry3', $content[1]['url']);
 +    }
 +
 +    public function testLimitBulkAction()
 +    {
 +        $list = [
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +            'http://0.0.0.0/entry1',
 +        ];
 +
 +        $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list));
 +
 +        $this->assertEquals(400, $this->client->getResponse()->getStatusCode());
 +        $this->assertContains('API limit reached', $this->client->getResponse()->getContent());
 +    }
  }
index 698e5e1309cef786e9d825f18ffcd5aabba4db6f,7db4cf1ff46aef5e7efa84bba45f32211a3c387e..19c8698edeb083b82c0831a6bed716945f8452ad
@@@ -135,45 -135,9 +135,45 @@@ class EntryControllerTest extends Walla
              ->getRepository('WallabagCoreBundle:Entry')
              ->findByUrlAndUserId($this->url, $this->getLoggedInUserId());
  
 +        $author = $content->getPublishedBy();
 +
          $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
          $this->assertEquals($this->url, $content->getUrl());
          $this->assertContains('Google', $content->getTitle());
 +        $this->assertEquals('2015-03-28 15:37:39', $content->getPublishedAt()->format('Y-m-d H:i:s'));
 +        $this->assertEquals('Morgane Tual', $author[0]);
 +        $this->assertArrayHasKey('x-varnish1', $content->getHeaders());
 +    }
 +
 +    public function testPostWithMultipleAuthors()
 +    {
 +        $url = 'http://www.liberation.fr/planete/2017/04/05/donald-trump-et-xi-jinping-tentative-de-flirt-en-floride_1560768';
 +        $this->logInAs('admin');
 +        $client = $this->getClient();
 +
 +        $crawler = $client->request('GET', '/new');
 +
 +        $this->assertEquals(200, $client->getResponse()->getStatusCode());
 +
 +        $form = $crawler->filter('form[name=entry]')->form();
 +
 +        $data = [
 +            'entry[url]' => $url,
 +        ];
 +
 +        $client->submit($form, $data);
 +
 +        $this->assertEquals(302, $client->getResponse()->getStatusCode());
 +
 +        $content = $client->getContainer()
 +            ->get('doctrine.orm.entity_manager')
 +            ->getRepository('WallabagCoreBundle:Entry')
 +            ->findByUrlAndUserId($url, $this->getLoggedInUserId());
 +
 +        $authors = $content->getPublishedBy();
 +        $this->assertEquals('2017-04-05 19:26:13', $content->getPublishedAt()->format('Y-m-d H:i:s'));
 +        $this->assertEquals('Raphaël Balenieri, correspondant à Pékin', $authors[0]);
 +        $this->assertEquals('Frédéric Autran, correspondant à New York', $authors[1]);
      }
  
      public function testPostNewOkUrlExist()
              ->findOneByUrl($url);
          $tags = $entry->getTags();
  
 -        $this->assertCount(1, $tags);
 +        $this->assertCount(2, $tags);
          $this->assertEquals('wallabag', $tags[0]->getLabel());
  
          $em->remove($entry);
  
          $tags = $entry->getTags();
  
 -        $this->assertCount(1, $tags);
 -        $this->assertEquals('wallabag', $tags[0]->getLabel());
 +        $this->assertCount(2, $tags);
 +        $this->assertEquals('wallabag', $tags[1]->getLabel());
  
          $em->remove($entry);
          $em->flush();
          $this->assertCount(1, $crawler->filter('div[class=entry]'));
      }
  
+     public function testFilterOnReadingTimeWithNegativeValue()
+     {
+         $this->logInAs('admin');
+         $client = $this->getClient();
+         $crawler = $client->request('GET', '/unread/list');
+         $form = $crawler->filter('button[id=submit-filter]')->form();
+         $data = [
+             'entry_filter[readingTime][right_number]' => -22,
+             'entry_filter[readingTime][left_number]' => -22,
+         ];
+         $crawler = $client->submit($form, $data);
+         // forcing negative value results in no entry displayed
+         $this->assertCount(0, $crawler->filter('div[class=entry]'));
+     }
      public function testFilterOnReadingTimeOnlyUpper()
      {
          $this->logInAs('admin');
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(2, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(3, $crawler->filter('div[class=entry]'));
      }
  
      public function testFilterOnReadingTimeOnlyLower()
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(4, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(5, $crawler->filter('div[class=entry]'));
      }
  
      public function testFilterOnCreationDate()
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(5, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(6, $crawler->filter('div[class=entry]'));
  
          $data = [
              'entry_filter[createdAt][left_date]' => date('d/m/Y'),
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(5, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(6, $crawler->filter('div[class=entry]'));
  
          $data = [
              'entry_filter[createdAt][left_date]' => '01/01/1970',
          $form['entry_filter[previewPicture]']->tick();
  
          $crawler = $client->submit($form);
 -        $this->assertCount(1, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(2, $crawler->filter('div[class=entry]'));
      }
  
      public function testFilterOnLanguage()
          ];
  
          $crawler = $client->submit($form, $data);
 -        $this->assertCount(2, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(3, $crawler->filter('div[class=entry]'));
  
          $form = $crawler->filter('button[id=submit-filter]')->form();
          $data = [
          $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $entry);
          $this->assertEquals($url, $entry->getUrl());
          $this->assertContains('Perpignan', $entry->getTitle());
 -        $this->assertContains('/d9bc0fcd.jpeg', $entry->getContent());
 +        $this->assertContains('/c4789a7f.jpeg', $entry->getContent());
  
          $client->getContainer()->get('craue_config')->set('download_images_enabled', 0);
      }
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(1, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(2, $crawler->filter('div[class=entry]'));
  
          $crawler = $client->request('GET', '/all/list');
          $form = $crawler->filter('button[id=submit-filter]')->form();
  
          $crawler = $client->submit($form, $data);
  
 -        $this->assertCount(7, $crawler->filter('div[class=entry]'));
 +        $this->assertCount(8, $crawler->filter('div[class=entry]'));
      }
  
      public function testSearch()