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%"
--- /dev/null
+Console Commands
+================
+
+wallabag has a number of CLI commands to manage a number of tasks. You can list all the commands by executing `bin/console` in the wallabag folder.
+
+Each command has a help accessible through `bin/console help %command%`.
+
+.. note::
+
+ If you're in a production environment, remember to add `-e prod` to each command.
+
+Notable commands
+----------------
+
+* `assets:install`: May be helpful if assets are missing.
+* `cache:clear`: should be run after each update (included in `make update`).
+* `doctrine:migrations:status`: Output the status of your database migrations.
+* `fos:user:activate`: Manually activate an user.
+* `fos:user:change-password`: Change a password for an user.
+* `fos:user:create`: Create an user.
+* `fos:user:deactivate`: Deactivate an user (not deleted).
+* `fos:user:demote`: Removes a role from an user, typically admin rights.
+* `fos:user:promote`: Adds a role to an user, typically admin rights.
+* `rabbitmq:*`: May be useful if you're using RabbitMQ.
+* `wallabag:clean-duplicates`: Removes all entry duplicates for one user or all users
+* `wallabag:export`: Exports all entries for an user. You can choose the output path of the file.
+* `wallabag:import`: Import entries to different formats to an user account.
+* `wallabag:import:redis-worker`: Useful if you use Redis.
+* `wallabag:install`: (re)Install wallabag
+* `wallabag:tag:all`: Tag all entries for an user using his/her tagging rules.
---------------
Export your Instapaper data
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
On the settings (`https://www.instapaper.com/user <https://www.instapaper.com/user>`_) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like ``instapaper-export.csv``).
::
- bin/console wallabag:import 1 ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod
+ bin/console wallabag:import username ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod
Please replace values:
-* ``1`` is the user identifier in database (The ID of the first user created on wallabag is 1)
+* ``username`` is the user's username
* ``~/Downloads/wallabag-export-1-2016-04-05.json`` is the path of your wallabag v1 export
-If you want to mark all these entries as read, you can add the ``--markAsRead`` option.
+.. note::
+ If you want to mark all these entries as read, you can add the ``--markAsRead`` option.
-To import a wallabag v2 file, you need to add the option ``--importer=v2``.
+.. note::
+ To import a wallabag v2 file, you need to add the option ``--importer=v2``.
+
+.. note::
+ If you want to pass the user id of the user instead of it's username, add the option ``--useUserId=true``.
You'll have this in return:
--- /dev/null
+Actions en ligne de commande
+============================
+
+wallabag a un certain nombre de commandes CLI pour effectuer des tâches. Vous pouvez lister toutes les commandes en exécutant `bin/console` dans le dossier d'installation de wallabag.
+
+Chaque commande a une aide correspondante accessible via `bin/console help %command%`.
+
+.. note::
+
+ Si vous êtes dans un environnement de production, souvenez-vous d'ajouter `-e prod` à chaque commande.
+
+Commandes notables
+------------------
+
+* `assets:install`: Peut-être utile si les *assets* sont manquants.
+* `cache:clear`: doit être exécuté après chaque mise à jour (appelé dans `make update`).
+* `doctrine:migrations:status`: Montre le statut de vos migrations de vos bases de données.
+* `fos:user:activate`: Activer manuellement un utilisateur.
+* `fos:user:change-password`: Changer le mot de passe pour un utilisateur.
+* `fos:user:create`: Créer un utilisateur.
+* `fos:user:deactivate`: Désactiver un utilisateur (non supprimé).
+* `fos:user:demote`: Supprimer un rôle d'un utilisateur, typiquement les droits d'administration.
+* `fos:user:promote`: Ajoute un rôle à un utilisateur, typiquement les droits d'administration.
+* `rabbitmq:*`: Peut-être utile si vous utilisez RabbitMQ.
+* `wallabag:clean-duplicates`: Supprime tous les articles dupliqués pour un utilisateur ou bien tous.
+* `wallabag:export`: Exporte tous les articles pour un utilisateur. Vous pouvez choisir le chemin du fichier exporté.
+* `wallabag:import`: Importe les articles en différents formats dans un compte utilisateur.
+* `wallabag:import:redis-worker`: Utile si vous utilisez Redis.
+* `wallabag:install`: (ré)Installer wallabag
+* `wallabag:tag:all`: Tagger tous les articles pour un utilisateur ou une utilisatrice en utilisant ses règles de tags automatiques.
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;
$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?
$exists = $res instanceof Entry ? $res->getId() : false;
- $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
-
- 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 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 (new JsonResponse())->setJson($json);
+ return $this->sendResponse($entry);
}
/**
$this->validateAuthentication();
$list = json_decode($request->query->get('list', []));
- $results = [];
+
+ if (empty($list)) {
+ return $this->sendResponse([]);
+ }
// handle multiple urls
- if (!empty($list)) {
- foreach ($list as $key => $element) {
- $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
- $element->url,
- $this->getUser()->getId()
- );
+ $results = [];
- $results[$key]['url'] = $element->url;
- $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
+ foreach ($list as $key => $element) {
+ $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
+ $element->url,
+ $this->getUser()->getId()
+ );
- $tags = $element->tags;
+ $results[$key]['url'] = $element->url;
+ $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
- if (false !== $entry && !(empty($tags))) {
- $tags = explode(',', $tags);
- foreach ($tags as $label) {
- $label = trim($label);
+ $tags = $element->tags;
- $tag = $this->getDoctrine()
- ->getRepository('WallabagCoreBundle:Tag')
- ->findOneByLabel($label);
+ if (false !== $entry && !(empty($tags))) {
+ $tags = explode(',', $tags);
+ foreach ($tags as $label) {
+ $label = trim($label);
- if (false !== $tag) {
- $entry->removeTag($tag);
- }
- }
+ $tag = $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Tag')
+ ->findOneByLabel($label);
- $em = $this->getDoctrine()->getManager();
- $em->persist($entry);
- $em->flush();
+ if (false !== $tag) {
+ $entry->removeTag($tag);
+ }
}
+
+ $em = $this->getDoctrine()->getManager();
+ $em->persist($entry);
+ $em->flush();
}
}
- $json = $this->get('serializer')->serialize($results, 'json');
-
- return (new JsonResponse())->setJson($json);
+ return $this->sendResponse($results);
}
/**
$this->validateAuthentication();
$list = json_decode($request->query->get('list', []));
+
+ if (empty($list)) {
+ return $this->sendResponse([]);
+ }
+
$results = [];
// handle multiple urls
- if (!empty($list)) {
- foreach ($list as $key => $element) {
- $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
- $element->url,
- $this->getUser()->getId()
- );
+ 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;
+ $results[$key]['url'] = $element->url;
+ $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
- $tags = $element->tags;
+ $tags = $element->tags;
- if (false !== $entry && !(empty($tags))) {
- $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
+ if (false !== $entry && !(empty($tags))) {
+ $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
- $em = $this->getDoctrine()->getManager();
- $em->persist($entry);
- $em->flush();
- }
+ $em = $this->getDoctrine()->getManager();
+ $em->persist($entry);
+ $em->flush();
}
}
- $json = $this->get('serializer')->serialize($results, 'json');
+ 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);
}
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Command;
+
+use Doctrine\ORM\NoResultException;
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\UserBundle\Entity\User;
+
+class CleanDuplicatesCommand extends ContainerAwareCommand
+{
+ /** @var OutputInterface */
+ protected $output;
+
+ protected $duplicates = 0;
+
+ protected function configure()
+ {
+ $this
+ ->setName('wallabag:clean-duplicates')
+ ->setDescription('Cleans the database for duplicates')
+ ->setHelp('This command helps you to clean your articles list in case of duplicates')
+ ->addArgument(
+ 'username',
+ InputArgument::OPTIONAL,
+ 'User to clean'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->output = $output;
+
+ $username = $input->getArgument('username');
+
+ if ($username) {
+ try {
+ $user = $this->getUser($username);
+ $this->cleanDuplicates($user);
+ } catch (NoResultException $e) {
+ $output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
+
+ return 1;
+ }
+ } else {
+ $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll();
+
+ $output->writeln(sprintf('Cleaning through %d user accounts', count($users)));
+
+ foreach ($users as $user) {
+ $output->writeln(sprintf('Processing user %s', $user->getUsername()));
+ $this->cleanDuplicates($user);
+ }
+ $output->writeln(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates));
+ }
+
+ return 0;
+ }
+
+ /**
+ * @param User $user
+ */
+ private function cleanDuplicates(User $user)
+ {
+ $em = $this->getContainer()->get('doctrine.orm.entity_manager');
+ $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
+
+ $entries = $repo->getAllEntriesIdAndUrl($user->getId());
+
+ $duplicatesCount = 0;
+ $urls = [];
+ foreach ($entries as $entry) {
+ $url = $this->similarUrl($entry['url']);
+
+ /* @var $entry Entry */
+ if (in_array($url, $urls)) {
+ ++$duplicatesCount;
+
+ $em->remove($repo->find($entry['id']));
+ $em->flush(); // Flushing at the end of the loop would require the instance not being online
+ } else {
+ $urls[] = $entry['url'];
+ }
+ }
+
+ $this->duplicates += $duplicatesCount;
+
+ $this->output->writeln(sprintf('Cleaned %d duplicates for user %s', $duplicatesCount, $user->getUserName()));
+ }
+
+ private function similarUrl($url)
+ {
+ if (in_array(substr($url, -1), ['/', '#'])) { // get rid of "/" and "#" and the end of urls
+ return substr($url, 0, strlen($url));
+ }
+
+ return $url;
+ }
+
+ /**
+ * Fetches a user from its username.
+ *
+ * @param string $username
+ *
+ * @return \Wallabag\UserBundle\Entity\User
+ */
+ private function getUser($username)
+ {
+ return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
+ }
+
+ private function getDoctrine()
+ {
+ return $this->getContainer()->get('doctrine');
+ }
+}
->setupDatabase()
->setupAdmin()
->setupConfig()
+ ->runMigrations()
;
$output->writeln('<info>wallabag has been successfully installed.</info>');
protected function checkRequirements()
{
- $this->defaultOutput->writeln('<info><comment>Step 1 of 4.</comment> Checking system requirements.</info>');
+ $this->defaultOutput->writeln('<info><comment>Step 1 of 5.</comment> Checking system requirements.</info>');
$doctrineManager = $this->getContainer()->get('doctrine')->getManager();
$rows = [];
protected function setupDatabase()
{
- $this->defaultOutput->writeln('<info><comment>Step 2 of 4.</comment> Setting up database.</info>');
+ $this->defaultOutput->writeln('<info><comment>Step 2 of 5.</comment> Setting up database.</info>');
// user want to reset everything? Don't care about what is already here
if (true === $this->defaultInput->getOption('reset')) {
- $this->defaultOutput->writeln('Droping database, creating database and schema, clearing the cache');
+ $this->defaultOutput->writeln('Dropping database, creating database and schema, clearing the cache');
$this
->runCommand('doctrine:database:drop', ['--force' => true])
$question = new ConfirmationQuestion('It appears that your database already exists. Would you like to reset it? (y/N)', false);
if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
- $this->defaultOutput->writeln('Droping database, creating database and schema');
+ $this->defaultOutput->writeln('Dropping database, creating database and schema');
$this
->runCommand('doctrine:database:drop', ['--force' => true])
} elseif ($this->isSchemaPresent()) {
$question = new ConfirmationQuestion('Seems like your database contains schema. Do you want to reset it? (y/N)', false);
if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
- $this->defaultOutput->writeln('Droping schema and creating schema');
+ $this->defaultOutput->writeln('Dropping schema and creating schema');
$this
->runCommand('doctrine:schema:drop', ['--force' => true])
protected function setupAdmin()
{
- $this->defaultOutput->writeln('<info><comment>Step 3 of 4.</comment> Administration setup.</info>');
+ $this->defaultOutput->writeln('<info><comment>Step 3 of 5.</comment> Administration setup.</info>');
$questionHelper = $this->getHelperSet()->get('question');
$question = new ConfirmationQuestion('Would you like to create a new admin user (recommended) ? (Y/n)', true);
protected function setupConfig()
{
- $this->defaultOutput->writeln('<info><comment>Step 4 of 4.</comment> Config setup.</info>');
+ $this->defaultOutput->writeln('<info><comment>Step 4 of 5.</comment> Config setup.</info>');
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
// cleanup before insert new stuff
return $this;
}
+ protected function runMigrations()
+ {
+ $this->defaultOutput->writeln('<info><comment>Step 5 of 5.</comment> Run migrations.</info>');
+
+ $this
+ ->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
+ }
+
/**
* Run a command.
*
->scalarNode('list_mode')
->defaultValue(1)
->end()
+ ->scalarNode('api_limit_mass_actions')
+ ->defaultValue(10)
+ ->end()
->end()
;
$container->setParameter('wallabag_core.action_mark_as_read', $config['action_mark_as_read']);
$container->setParameter('wallabag_core.list_mode', $config['list_mode']);
$container->setParameter('wallabag_core.fetching_error_message', $config['fetching_error_message']);
+ $container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
->setParameter('userId', $userId)
->execute();
}
+
+ /**
+ * Get id and url from all entries
+ * Used for the clean-duplicates command.
+ */
+ public function getAllEntriesIdAndUrl($userId)
+ {
+ $qb = $this->createQueryBuilder('e')
+ ->select('e.id, e.url')
+ ->where('e.user = :userid')->setParameter(':userid', $userId);
+
+ return $qb->getQuery()->getArrayResult();
+ }
+
+ /**
+ * Find all entries by url and owner.
+ *
+ * @param $url
+ * @param $userId
+ *
+ * @return array
+ */
+ public function findAllByUrlAndUserId($url, $userId)
+ {
+ return $this->createQueryBuilder('e')
+ ->where('e.url = :url')->setParameter('url', urldecode($url))
+ ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
+ ->getQuery()
+ ->getResult();
+ }
}
# 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
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
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
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
# 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
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 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."
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 l’interface de wallabag."
+ help_pocket_consumer_key: "Nécessaire pour l’import 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
- archived: Supprimer TOUS les articles archivés
- 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"
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"
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"
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>"
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
- archived_reset: Articles archivés 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é"
# 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
delete: 'Suprimir'
delete_confirm: 'Sètz segur ?'
back_to_list: 'Tornar a la lista'
+ search:
+ # placeholder: Filter by username or email
error:
page_title: Una error s'es produsida
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
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
# 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
# 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
$this
->setName('wallabag:import')
->setDescription('Import entries from a JSON export')
- ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate')
+ ->addArgument('username', InputArgument::REQUIRED, 'User to populate')
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1')
->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false)
+ ->addOption('useUserId', null, InputArgument::OPTIONAL, 'Use user id instead of username to find account', false)
;
}
// Turning off doctrine default logs queries for saving memory
$em->getConnection()->getConfiguration()->setSQLLogger(null);
- $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId'));
+ if ($input->getOption('useUserId')) {
+ $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('username'));
+ } else {
+ $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username'));
+ }
if (!is_object($user)) {
- throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
+ throw new Exception(sprintf('User "%s" not found', $input->getArgument('username')));
}
switch ($input->getOption('importer')) {
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
+use Pagerfanta\Adapter\DoctrineORMAdapter;
+use Pagerfanta\Exception\OutOfRangeCurrentPageException;
+use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Wallabag\UserBundle\Entity\User;
-use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\UserBundle\Form\SearchUserType;
/**
* User controller.
*/
class ManageController extends Controller
{
- /**
- * Lists all User entities.
- *
- * @Route("/", name="user_index")
- * @Method("GET")
- */
- public function indexAction()
- {
- $em = $this->getDoctrine()->getManager();
-
- $users = $em->getRepository('WallabagUserBundle:User')->findAll();
-
- return $this->render('WallabagUserBundle:Manage:index.html.twig', array(
- 'users' => $users,
- ));
- }
-
/**
* Creates a new User entity.
*
->getForm()
;
}
+
+ /**
+ * @param Request $request
+ * @param int $page
+ *
+ * @Route("/list/{page}", name="user_index", defaults={"page" = 1})
+ *
+ * Default parameter for page is hardcoded (in duplication of the defaults from the Route)
+ * because this controller is also called inside the layout template without any page as argument
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function searchFormAction(Request $request, $page = 1)
+ {
+ $em = $this->getDoctrine()->getManager();
+ $qb = $em->getRepository('WallabagUserBundle:User')->createQueryBuilder('u');
+
+ $form = $this->createForm(SearchUserType::class);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $this->get('logger')->info('searching users');
+
+ $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
+
+ $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
+ }
+
+ $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
+ $pagerFanta = new Pagerfanta($pagerAdapter);
+ $pagerFanta->setMaxPerPage(50);
+
+ try {
+ $pagerFanta->setCurrentPage($page);
+ } catch (OutOfRangeCurrentPageException $e) {
+ if ($page > 1) {
+ return $this->redirect($this->generateUrl('user_index', ['page' => $pagerFanta->getNbPages()]), 302);
+ }
+ }
+
+ return $this->render('WallabagUserBundle:Manage:index.html.twig', [
+ 'searchForm' => $form->createView(),
+ 'users' => $pagerFanta,
+ ]);
+ }
}
--- /dev/null
+<?php
+
+namespace Wallabag\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class SearchUserType extends AbstractType
+{
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->setMethod('GET')
+ ->add('term', TextType::class, [
+ 'required' => true,
+ 'label' => 'user.new.form_search.term_label',
+ ])
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'csrf_protection' => false,
+ ]);
+ }
+}
->getQuery()
->getSingleScalarResult();
}
+
+ /**
+ * Retrieves users filtered with a search term.
+ *
+ * @param string $term
+ *
+ * @return QueryBuilder
+ */
+ public function getQueryBuilderForSearch($term)
+ {
+ return $this->createQueryBuilder('u')
+ ->andWhere('lower(u.username) LIKE lower(:term) OR lower(u.email) LIKE lower(:term) OR lower(u.name) LIKE lower(:term)')->setParameter('term', '%'.$term.'%');
+ }
}
<div class="row">
<div class="col s12">
<div class="card-panel">
+ {% if users.getNbPages > 1 %}
+ {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
+ {% endif %}
<div class="row">
- <div class="input-field col s12">
+ <div class="col s6">
<p class="help">{{ 'user.description'|trans|raw }}</p>
+ </div>
+ <div class="col s6">
+ <div class="input-field">
+ <form name="search_users" method="GET" action="{{ path('user_index')}}">
+ {% if form_errors(searchForm) %}
+ <span class="black-text">{{ form_errors(searchForm) }}</span>
+ {% endif %}
+
+ {% if form_errors(searchForm.term) %}
+ <span class="black-text">{{ form_errors(searchForm.term) }}</span>
+ {% endif %}
- <table class="bordered">
- <thead>
- <tr>
- <th>{{ 'user.form.username_label'|trans }}</th>
- <th>{{ 'user.form.email_label'|trans }}</th>
- <th>{{ 'user.form.last_login_label'|trans }}</th>
- <th>{{ 'user.list.actions'|trans }}</th>
- </tr>
- </thead>
- <tbody>
- {% for user in users %}
- <tr>
- <td>{{ user.username }}</td>
- <td>{{ user.email }}</td>
- <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
- <td>
- <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
- </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- <br />
- <p>
- <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
- </p>
+ {{ form_widget(searchForm.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'user.search.placeholder'} }) }}
+
+ {{ form_rest(searchForm) }}
+ </form>
+ </div>
</div>
+
+ <table class="bordered">
+ <thead>
+ <tr>
+ <th>{{ 'user.form.username_label'|trans }}</th>
+ <th>{{ 'user.form.email_label'|trans }}</th>
+ <th>{{ 'user.form.last_login_label'|trans }}</th>
+ <th>{{ 'user.list.actions'|trans }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for user in users %}
+ <tr>
+ <td>{{ user.username }}</td>
+ <td>{{ user.email }}</td>
+ <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
+ <td>
+ <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ <br />
+ <p>
+ <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
+ </p>
+ {% if users.getNbPages > 1 %}
+ {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
+ {% endif %}
</div>
</div>
</div>
];
$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://0.0.0.0/entry4', $content[0]['url']);
+ $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']);
- $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagCoreBundle:Entry')
- ->findByUrlAndUserId('http://0.0.0.0/entry4', 1);
+ $this->assertInternalType('int', $content[1]['entry']);
+ $this->assertEquals('http://0.0.0.0/entry2', $content[1]['url']);
+ }
- $tags = $entry->getTags();
- $this->assertCount(2, $tags);
+ 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());
}
}
--- /dev/null
+<?php
+
+namespace Tests\Wallabag\CoreBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Wallabag\CoreBundle\Command\CleanDuplicatesCommand;
+use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
+use Wallabag\CoreBundle\Entity\Entry;
+
+class CleanDuplicatesCommandTest extends WallabagCoreTestCase
+{
+ public function testRunCleanDuplicates()
+ {
+ $application = new Application($this->getClient()->getKernel());
+ $application->add(new CleanDuplicatesCommand());
+
+ $command = $application->find('wallabag:clean-duplicates');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'command' => $command->getName(),
+ ]);
+
+ $this->assertContains('Cleaning through 3 user accounts', $tester->getDisplay());
+ $this->assertContains('Finished cleaning. 0 duplicates found in total', $tester->getDisplay());
+ }
+
+ public function testRunCleanDuplicatesCommandWithBadUsername()
+ {
+ $application = new Application($this->getClient()->getKernel());
+ $application->add(new CleanDuplicatesCommand());
+
+ $command = $application->find('wallabag:clean-duplicates');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'command' => $command->getName(),
+ 'username' => 'unknown',
+ ]);
+
+ $this->assertContains('User "unknown" not found', $tester->getDisplay());
+ }
+
+ public function testRunCleanDuplicatesCommandForUser()
+ {
+ $application = new Application($this->getClient()->getKernel());
+ $application->add(new CleanDuplicatesCommand());
+
+ $command = $application->find('wallabag:clean-duplicates');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'command' => $command->getName(),
+ 'username' => 'admin',
+ ]);
+
+ $this->assertContains('Cleaned 0 duplicates for user admin', $tester->getDisplay());
+ }
+
+ public function testDuplicate()
+ {
+ $url = 'http://www.lemonde.fr/sport/visuel/2017/05/05/rondelle-prison-blanchissage-comprendre-le-hockey-sur-glace_5122587_3242.html';
+ $client = $this->getClient();
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $this->logInAs('admin');
+
+ $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+ $this->assertCount(0, $nbEntries);
+
+ $user = $em->getRepository('WallabagUserBundle:User')->findOneById($this->getLoggedInUserId());
+
+ $entry1 = new Entry($user);
+ $entry1->setUrl($url);
+
+ $entry2 = new Entry($user);
+ $entry2->setUrl($url);
+
+ $em->persist($entry1);
+ $em->persist($entry2);
+
+ $em->flush();
+
+ $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+ $this->assertCount(2, $nbEntries);
+
+ $application = new Application($this->getClient()->getKernel());
+ $application->add(new CleanDuplicatesCommand());
+
+ $command = $application->find('wallabag:clean-duplicates');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'command' => $command->getName(),
+ 'username' => 'admin',
+ ]);
+
+ $this->assertContains('Cleaned 1 duplicates for user admin', $tester->getDisplay());
+
+ $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+ $this->assertCount(1, $nbEntries);
+
+ $query = $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.url = :url');
+ $query->setParameter('url', $url);
+ $query->execute();
+ }
+}
$tester->execute([
'command' => $command->getName(),
'username' => 'admin',
- 'filepath' => 'specialexport.json'
+ 'filepath' => 'specialexport.json',
]);
$this->assertFileExists('specialexport.json');
$this->assertContains('Setting up database.', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
}
public function testRunInstallCommandWithReset()
$this->assertContains('Checking system requirements.', $tester->getDisplay());
$this->assertContains('Setting up database.', $tester->getDisplay());
- $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay());
+ $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
// we force to reset everything
- $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay());
+ $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay());
}
public function testRunInstallCommandWithDatabaseRemoved()
$this->assertContains('Setting up database.', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
// the current database doesn't already exist
$this->assertContains('Creating database and schema, clearing the cache', $tester->getDisplay());
$this->assertContains('Setting up database.', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
- $this->assertContains('Droping schema and creating schema', $tester->getDisplay());
+ $this->assertContains('Dropping schema and creating schema', $tester->getDisplay());
}
public function testRunInstallCommandChooseNothing()
$this->assertContains('Setting up database.', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
$this->assertContains('Creating schema', $tester->getDisplay());
}
$this->assertContains('Setting up database.', $tester->getDisplay());
$this->assertContains('Administration setup.', $tester->getDisplay());
$this->assertContains('Config setup.', $tester->getDisplay());
+ $this->assertContains('Run migrations.', $tester->getDisplay());
}
}
$this->assertEquals('http://domain.io', $entry->getUrl());
$this->assertEquals('my title', $entry->getTitle());
- $this->assertEquals($this->fetchingErrorMessage . '<p><i>But we found a short description: </i></p>desc', $entry->getContent());
+ $this->assertEquals($this->fetchingErrorMessage.'<p><i>But we found a short description: </i></p>desc', $entry->getContent());
$this->assertEmpty($entry->getPreviewPicture());
$this->assertEmpty($entry->getLanguage());
$this->assertEmpty($entry->getHttpStatus());
class ImportCommandTest extends WallabagCoreTestCase
{
/**
- * @expectedException Symfony\Component\Console\Exception\RuntimeException
+ * @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Not enough arguments
*/
public function testRunImportCommandWithoutArguments()
}
/**
- * @expectedException Symfony\Component\Config\Definition\Exception\Exception
+ * @expectedException \Symfony\Component\Config\Definition\Exception\Exception
* @expectedExceptionMessage not found
*/
public function testRunImportCommandWithoutFilepath()
$tester = new CommandTester($command);
$tester->execute([
'command' => $command->getName(),
- 'userId' => 1,
+ 'username' => 'admin',
'filepath' => 1,
]);
}
/**
- * @expectedException Symfony\Component\Config\Definition\Exception\Exception
- * @expectedExceptionMessage User with id
+ * @expectedException \Doctrine\ORM\NoResultException
*/
- public function testRunImportCommandWithoutUserId()
+ public function testRunImportCommandWithWrongUsername()
{
$application = new Application($this->getClient()->getKernel());
$application->add(new ImportCommand());
$tester = new CommandTester($command);
$tester->execute([
'command' => $command->getName(),
- 'userId' => 0,
+ 'username' => 'random',
'filepath' => './',
]);
}
$tester = new CommandTester($command);
$tester->execute([
'command' => $command->getName(),
- 'userId' => 1,
+ 'username' => 'admin',
'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json',
'--importer' => 'v2',
]);
$this->assertContains('imported', $tester->getDisplay());
$this->assertContains('already saved', $tester->getDisplay());
}
+
+ public function testRunImportCommandWithUserId()
+ {
+ $application = new Application($this->getClient()->getKernel());
+ $application->add(new ImportCommand());
+
+ $command = $application->find('wallabag:import');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'command' => $command->getName(),
+ 'username' => 1,
+ 'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json',
+ '--useUserId' => true,
+ ]);
+ }
}
{
$client = $this->getClient();
- $client->request('GET', '/users/');
+ $client->request('GET', '/users/list');
$this->assertEquals(302, $client->getResponse()->getStatusCode());
$this->assertContains('login', $client->getResponse()->headers->get('location'));
$client = $this->getClient();
// Create a new user in the database
- $crawler = $client->request('GET', '/users/');
+ $crawler = $client->request('GET', '/users/list');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), 'Unexpected HTTP status code for GET /users/');
$crawler = $client->click($crawler->selectLink('user.list.create_new_one')->link());
$client->submit($form);
$client->followRedirect();
- $crawler = $client->request('GET', '/users/');
+ $crawler = $client->request('GET', '/users/list');
// Check data in the show view
$this->assertGreaterThan(0, $crawler->filter('td:contains("test_user")')->count(), 'Missing element td:contains("test_user")');
// Check the element contains an attribute with value equals "Foo User"
$this->assertGreaterThan(0, $crawler->filter('[value="Foo User"]')->count(), 'Missing element [value="Foo User"]');
- $crawler = $client->request('GET', '/users/');
+ $crawler = $client->request('GET', '/users/list');
$crawler = $client->click($crawler->selectLink('user.list.edit_action')->last()->link());
// Delete the user
$this->assertEquals('disabled', $disabled[0]);
}
+
+ public function testUserSearch()
+ {
+ $this->logInAs('admin');
+ $client = $this->getClient();
+
+ // Search on unread list
+ $crawler = $client->request('GET', '/users/list');
+
+ $form = $crawler->filter('form[name=search_users]')->form();
+ $data = [
+ 'search_user[term]' => 'admin',
+ ];
+
+ $crawler = $client->submit($form, $data);
+
+ $this->assertCount(2, $crawler->filter('tr')); // 1 result + table header
+ }
}