case 'sqlite':
$this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext DEFAULT NULL');
break;
+
case 'mysql':
$this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext COLLATE \'utf8_unicode_ci\' DEFAULT NULL');
break;
+
case 'postgresql':
$this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name text DEFAULT NULL');
}
--- /dev/null
+<?php
+
+namespace Application\Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class Version20161001072726 extends AbstractMigration implements ContainerAwareInterface
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ public function setContainer(ContainerInterface $container = null)
+ {
+ $this->container = $container;
+ }
+
+ private function getTable($tableName)
+ {
+ return $this->container->getParameter('database_table_prefix') . $tableName;
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function up(Schema $schema)
+ {
+ $this->skipIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.');
+
+ // remove all FK from entry_tag
+ $query = $this->connection->query("SELECT CONSTRAINT_NAME FROM information_schema.key_column_usage WHERE TABLE_NAME = '".$this->getTable('entry_tag')."' AND CONSTRAINT_NAME LIKE 'FK_%' AND TABLE_SCHEMA = '".$this->connection->getDatabase()."'");
+ $query->execute();
+
+ foreach ($query->fetchAll() as $fk) {
+ $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
+ }
+
+ $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_entry_tag_entry FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) ON DELETE CASCADE');
+ $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_entry_tag_tag FOREIGN KEY (tag_id) REFERENCES '.$this->getTable('tag').' (id) ON DELETE CASCADE');
+
+ // remove entry FK from annotation
+ $query = $this->connection->query("SELECT CONSTRAINT_NAME FROM information_schema.key_column_usage WHERE TABLE_NAME = '".$this->getTable('annotation')."' AND CONSTRAINT_NAME LIKE 'FK_%' and COLUMN_NAME = 'entry_id' AND TABLE_SCHEMA = '".$this->connection->getDatabase()."'");
+ $query->execute();
+
+ foreach ($query->fetchAll() as $fk) {
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
+ }
+
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_annotation_entry FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) ON DELETE CASCADE');
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema)
+ {
+ throw new SkipMigrationException('Too complex ...');
+ }
+}
--- /dev/null
+<?php
+
+namespace Application\Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class Version20161022134138 extends AbstractMigration implements ContainerAwareInterface
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ public function setContainer(ContainerInterface $container = null)
+ {
+ $this->container = $container;
+ }
+
+ private function getTable($tableName)
+ {
+ return $this->container->getParameter('database_table_prefix') . $tableName;
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function up(Schema $schema)
+ {
+ $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
+
+ $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema)
+ {
+ $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
+
+ $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+ $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+ $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+ }
+}
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
- charset: UTF8
+ charset: "%database_charset%"
path: "%database_path%"
unix_socket: "%database_socket%"
server_version: 5.6
fos_rest:
param_fetcher_listener: true
body_listener: true
- format_listener: true
view:
+ mime_types:
+ csv:
+ - 'text/csv'
+ - 'text/plain'
+ pdf:
+ - 'application/pdf'
+ epub:
+ - 'application/epub+zip'
+ mobi:
+ - 'application/x-mobipocket-ebook'
view_response_listener: 'force'
formats:
xml: true
- json : true
+ json: true
+ txt: true
+ csv: true
+ pdf: true
+ epub: true
+ mobi: true
templating_formats:
html: true
force_redirects:
default_engine: twig
routing_loader:
default_format: json
+ format_listener:
+ enabled: true
+ rules:
+ - { path: "^/api/entries/([0-9]+)/export.(.*)", priorities: ['epub', 'mobi', 'pdf', 'txt', 'csv'], fallback_format: false, prefer_extension: false }
+ - { path: "^/api", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false }
+ - { path: "^/annotations", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false }
+ # for an unknown reason, EACH REQUEST goes to FOS\RestBundle\EventListener\FormatListener
+ # so we need to add custom rule for custom api export but also for all other routes of the application...
+ - { path: '^/', priorities: ['text/html', '*/*'], fallback_format: html, prefer_extension: false }
nelmio_api_doc:
sandbox:
enabled: false
+ cache:
+ enabled: true
name: wallabag API documentation
nelmio_cors:
dbname: "%test_database_name%"
user: "%test_database_user%"
password: "%test_database_password%"
- charset: UTF8
+ charset: "%test_database_charset%"
path: "%test_database_path%"
orm:
metadata_cache_driver:
database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite"
database_table_prefix: wallabag_
database_socket: null
+ # with MySQL, use "utf8mb4" if you got problem with content with emojis
+ database_charset: utf8
- mailer_transport: smtp
- mailer_host: 127.0.0.1
- mailer_user: ~
- mailer_password: ~
+ mailer_transport: smtp
+ mailer_host: 127.0.0.1
+ mailer_user: ~
+ mailer_password: ~
- locale: en
+ locale: en
# A secret key that's used to generate certain security-related tokens
- secret: ovmpmAWXRCabNlMgzlzFXDYmCFfzGv
+ secret: ovmpmAWXRCabNlMgzlzFXDYmCFfzGv
# two factor stuff
twofactor_auth: true
test_database_user: null
test_database_password: null
test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite'
+ test_database_charset: utf8
Rest_Wallabag:
- type : rest
- resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"
-
+ type : rest
+ resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"
test_database_user: root
test_database_password: ~
test_database_path: ~
+ test_database_charset: utf8mb4
test_database_user: travis
test_database_password: ~
test_database_path: ~
+ test_database_charset: utf8
test_database_user: ~
test_database_password: ~
test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite"
+ test_database_charset: utf8
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "^2.0",
"nelmio/cors-bundle": "~1.4.0",
- "friendsofsymfony/rest-bundle": "~1.4",
- "jms/serializer-bundle": "~1.0",
+ "friendsofsymfony/rest-bundle": "~2.1",
+ "jms/serializer-bundle": "~1.1",
"nelmio/api-doc-bundle": "~2.7",
"mgargano/simplehtmldom": "~1.5",
- "tecnickcom/tcpdf": "~6.2",
+ "tecnickcom/tc-lib-pdf": "dev-master",
"simplepie/simplepie": "~1.3.1",
"willdurand/hateoas-bundle": "~1.0",
"htmlawed/htmlawed": "~1.1.19",
You can change your name, your email address and enable ``Two factor authentication``.
+If the wallabag instance has more than one enabled user, you can delete your account here. **Take care, we delete all your data**.
+
Two factor authentication
~~~~~~~~~~~~~~~~~~~~~~~~~
"database_path", "``""%kernel.root_dir%/../data/db/wallabag.sqlite""``", "only for SQLite, define where to put the database file. Leave it empty for other database"
"database_table_prefix", "wallabag_", "all wallabag's tables will be prefixed with that string. You can include a ``_`` for clarity"
"database_socket", "null", "If your database is using a socket instead of tcp, put the path of the socket (other connection parameters will then be ignored)"
+ "database_charset", "utf8mb4", "For PostgreSQL & SQLite you should use utf8, for MySQL use utf8mb4 which handle emoji"
.. csv-table:: Configuration to send emails from wallabag
:header: "name", "default", "description"
Vous pouvez ici modifier votre nom, votre adresse email et activer la ``Double authentification``.
+Si l'instance de wallabag compte plus d'un utilisateur actif, vous pouvez supprimer ici votre compte. **Attention, nous supprimons toutes vos données**.
+
Double authentification (2FA)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
namespace Wallabag\AnnotationBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
-use Nelmio\ApiDocBundle\Annotation\ApiDoc;
+use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Wallabag\AnnotationBundle\Entity\Annotation;
use Wallabag\CoreBundle\Entity\Entry;
/**
* Retrieve annotations for an entry.
*
- * @ApiDoc(
- * requirements={
- * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
- * }
- * )
+ * @param Entry $entry
+ *
+ * @see Wallabag\ApiBundle\Controller\WallabagRestController
*
- * @return Response
+ * @return JsonResponse
*/
public function getAnnotationsAction(Entry $entry)
{
$annotationRows = $this
- ->getDoctrine()
- ->getRepository('WallabagAnnotationBundle:Annotation')
- ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
+ ->getDoctrine()
+ ->getRepository('WallabagAnnotationBundle:Annotation')
+ ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
$total = count($annotationRows);
$annotations = ['total' => $total, 'rows' => $annotationRows];
$json = $this->get('serializer')->serialize($annotations, 'json');
- return $this->renderJsonResponse($json);
+ return (new JsonResponse())->setJson($json);
}
/**
* Creates a new annotation.
*
- * @param Entry $entry
+ * @param Request $request
+ * @param Entry $entry
*
- * @ApiDoc(
- * requirements={
- * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
- * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
- * {"name"="text", "dataType"="string", "required"=true, "description"=""},
- * }
- * )
+ * @return JsonResponse
*
- * @return Response
+ * @see Wallabag\ApiBundle\Controller\WallabagRestController
*/
public function postAnnotationAction(Request $request, Entry $entry)
{
$json = $this->get('serializer')->serialize($annotation, 'json');
- return $this->renderJsonResponse($json);
+ return (new JsonResponse())->setJson($json);
}
/**
* Updates an annotation.
*
- * @ApiDoc(
- * requirements={
- * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
- * }
- * )
+ * @see Wallabag\ApiBundle\Controller\WallabagRestController
*
* @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
*
- * @return Response
+ * @param Annotation $annotation
+ * @param Request $request
+ *
+ * @return JsonResponse
*/
public function putAnnotationAction(Annotation $annotation, Request $request)
{
$json = $this->get('serializer')->serialize($annotation, 'json');
- return $this->renderJsonResponse($json);
+ return (new JsonResponse())->setJson($json);
}
/**
* Removes an annotation.
*
- * @ApiDoc(
- * requirements={
- * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
- * }
- * )
+ * @see Wallabag\ApiBundle\Controller\WallabagRestController
*
* @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
*
- * @return Response
+ * @param Annotation $annotation
+ *
+ * @return JsonResponse
*/
public function deleteAnnotationAction(Annotation $annotation)
{
$json = $this->get('serializer')->serialize($annotation, 'json');
- return $this->renderJsonResponse($json);
- }
-
- /**
- * Send a JSON Response.
- * We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
- *
- * @param string $json
- *
- * @return Response
- */
- private function renderJsonResponse($json, $code = 200)
- {
- return new Response($json, $code, ['application/json']);
+ return (new JsonResponse())->setJson($json);
}
}
* @Exclude
*
* @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="annotations")
- * @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
+ * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
*/
private $entry;
{
return $this->createQueryBuilder('a')
->andWhere('a.id = :annotationId')->setParameter('annotationId', $annotationId)
- ->getQuery()->getSingleResult()
+ ->getQuery()
+ ->getSingleResult()
;
}
return $this->createQueryBuilder('a')
->where('a.entry = :entryId')->setParameter('entryId', $entryId)
->andwhere('a.user = :userId')->setParameter('userId', $userId)
- ->getQuery()->getResult()
+ ->getQuery()
+ ->getResult()
;
}
->getQuery()
->getSingleResult();
}
+
+ /**
+ * Remove all annotations for a user id.
+ * Used when a user want to reset all informations.
+ *
+ * @param int $userId
+ */
+ public function removeAllByUserId($userId)
+ {
+ $this->getEntityManager()
+ ->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = :userId')
+ ->setParameter('userId', $userId)
+ ->execute();
+ }
}
namespace Wallabag\ApiBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
-use Hateoas\Configuration\Route;
+use Hateoas\Configuration\Route as HateoasRoute;
use Hateoas\Representation\Factory\PagerfantaFactory;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
+use Wallabag\AnnotationBundle\Entity\Annotation;
class WallabagRestController extends FOSRestController
{
$pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
$paginatedCollection = $pagerfantaFactory->createRepresentation(
$pager,
- new Route(
+ new HateoasRoute(
'api_get_entries',
[
'archive' => $isArchived,
return (new JsonResponse())->setJson($json);
}
+ /**
+ * Retrieve a single entry as a predefined format.
+ *
+ * @ApiDoc(
+ * requirements={
+ * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
+ * }
+ * )
+ *
+ * @return Response
+ */
+ public function getEntryExportAction(Entry $entry, Request $request)
+ {
+ $this->validateAuthentication();
+ $this->validateUserAccess($entry->getUser()->getId());
+
+ return $this->get('wallabag_core.helper.entries_export')
+ ->setEntries($entry)
+ ->updateTitle('entry')
+ ->exportAs($request->attributes->get('_format'));
+ }
+
/**
* Create an entry.
*
return (new JsonResponse())->setJson($json);
}
+ /**
+ * Retrieve annotations for an entry.
+ *
+ * @ApiDoc(
+ * requirements={
+ * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
+ * }
+ * )
+ *
+ * @param Entry $entry
+ *
+ * @return JsonResponse
+ */
+ public function getAnnotationsAction(Entry $entry)
+ {
+ $this->validateAuthentication();
+
+ return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [
+ 'entry' => $entry,
+ ]);
+ }
+
+ /**
+ * Creates a new annotation.
+ *
+ * @ApiDoc(
+ * requirements={
+ * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
+ * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
+ * {"name"="text", "dataType"="string", "required"=true, "description"=""},
+ * }
+ * )
+ *
+ * @param Request $request
+ * @param Entry $entry
+ *
+ * @return JsonResponse
+ */
+ public function postAnnotationAction(Request $request, Entry $entry)
+ {
+ $this->validateAuthentication();
+
+ return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [
+ 'request' => $request,
+ 'entry' => $entry,
+ ]);
+ }
+
+ /**
+ * Updates an annotation.
+ *
+ * @ApiDoc(
+ * requirements={
+ * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
+ * }
+ * )
+ *
+ * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
+ *
+ * @param Annotation $annotation
+ * @param Request $request
+ *
+ * @return JsonResponse
+ */
+ public function putAnnotationAction(Annotation $annotation, Request $request)
+ {
+ $this->validateAuthentication();
+
+ return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [
+ 'annotation' => $annotation,
+ 'request' => $request,
+ ]);
+ }
+
+ /**
+ * Removes an annotation.
+ *
+ * @ApiDoc(
+ * requirements={
+ * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
+ * }
+ * )
+ *
+ * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
+ *
+ * @param Annotation $annotation
+ *
+ * @return JsonResponse
+ */
+ public function deleteAnnotationAction(Annotation $annotation)
+ {
+ $this->validateAuthentication();
+
+ return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [
+ 'annotation' => $annotation,
+ ]);
+ }
+
/**
* Retrieve version number.
*
-entries:
- type: rest
- resource: "WallabagApiBundle:WallabagRest"
- name_prefix: api_
+api:
+ type: rest
+ resource: "WallabagApiBundle:WallabagRest"
+ name_prefix: api_
{
$this
->setName('wallabag:install')
- ->setDescription('Wallabag installer.')
+ ->setDescription('wallabag installer.')
->addOption(
'reset',
null,
$this->defaultInput = $input;
$this->defaultOutput = $output;
- $output->writeln('<info>Installing Wallabag...</info>');
+ $output->writeln('<info>Installing wallabag...</info>');
$output->writeln('');
$this
->setupConfig()
;
- $output->writeln('<info>Wallabag has been successfully installed.</info>');
+ $output->writeln('<info>wallabag has been successfully installed.</info>');
$output->writeln('<comment>Just execute `php bin/console server:run --env=prod` for using wallabag: http://localhost:8000</comment>');
}
// testing if database driver exists
$fulfilled = true;
- $label = '<comment>PDO Driver</comment>';
+ $label = '<comment>PDO Driver (%s)</comment>';
$status = '<info>OK!</info>';
$help = '';
$help = 'Database driver "'.$this->getContainer()->getParameter('database_driver').'" is not installed.';
}
- $rows[] = [$label, $status, $help];
+ $rows[] = [sprintf($label, $this->getContainer()->getParameter('database_driver')), $status, $help];
// testing if connection to the database can be etablished
$label = '<comment>Database connection</comment>';
$help = '';
try {
- $this->getContainer()->get('doctrine')->getManager()->getConnection()->connect();
+ $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
+ $conn->connect();
} catch (\Exception $e) {
if (false === strpos($e->getMessage(), 'Unknown database')
&& false === strpos($e->getMessage(), 'database "'.$this->getContainer()->getParameter('database_name').'" does not exist')) {
$rows[] = [$label, $status, $help];
+ // now check if MySQL isn't too old to handle utf8mb4
+ if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform) {
+ $version = $conn->query('select version()')->fetchColumn();
+ $minimalVersion = '5.5.4';
+
+ if (false === version_compare($version, $minimalVersion, '>')) {
+ $fulfilled = false;
+ $rows[] = [
+ '<comment>Database version</comment>',
+ '<error>ERROR!</error>',
+ 'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).',
+ ];
+ }
+ }
+
foreach ($this->functionExists as $functionRequired) {
$label = '<comment>'.$functionRequired.'</comment>';
$status = '<info>OK!</info>';
throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.');
}
- $this->defaultOutput->writeln('<info>Success! Your system can run Wallabag properly.</info>');
+ $this->defaultOutput->writeln('<info>Success! Your system can run wallabag properly.</info>');
$this->defaultOutput->writeln('');
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Wallabag\CoreBundle\Entity\Config;
use Wallabag\CoreBundle\Entity\TaggingRule;
use Wallabag\CoreBundle\Form\Type\ConfigType;
'token' => $config->getRssToken(),
],
'twofactor_auth' => $this->getParameter('twofactor_auth'),
+ 'enabled_users' => $this->getDoctrine()
+ ->getRepository('WallabagUserBundle:User')
+ ->getSumEnabledUsers(),
]);
}
return $this->redirect($this->generateUrl('config').'?tagging-rule='.$rule->getId().'#set5');
}
+ /**
+ * Remove all annotations OR tags OR entries for the current user.
+ *
+ * @Route("/reset/{type}", requirements={"id" = "annotations|tags|entries"}, name="config_reset")
+ *
+ * @return RedirectResponse
+ */
+ public function resetAction($type)
+ {
+ $em = $this->getDoctrine()->getManager();
+
+ switch ($type) {
+ case 'annotations':
+ $this->getDoctrine()
+ ->getRepository('WallabagAnnotationBundle:Annotation')
+ ->removeAllByUserId($this->getUser()->getId());
+ break;
+
+ case 'tags':
+ $this->removeAllTagsByUserId($this->getUser()->getId());
+ break;
+
+ case 'entries':
+ // SQLite doesn't care about cascading remove, so we need to manually remove associated stuf
+ // otherwise they won't be removed ...
+ if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) {
+ $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId());
+ }
+
+ // manually remove tags to avoid orphan tag
+ $this->removeAllTagsByUserId($this->getUser()->getId());
+
+ $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->removeAllByUserId($this->getUser()->getId());
+ }
+
+ $this->get('session')->getFlashBag()->add(
+ 'notice',
+ 'flashes.config.notice.'.$type.'_reset'
+ );
+
+ return $this->redirect($this->generateUrl('config').'#set3');
+ }
+
+ /**
+ * Remove all tags for a given user and cleanup orphan tags.
+ *
+ * @param int $userId
+ */
+ private function removeAllTagsByUserId($userId)
+ {
+ $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId);
+
+ if (empty($tags)) {
+ return;
+ }
+
+ $this->getDoctrine()
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->removeTags($userId, $tags);
+
+ // cleanup orphan tags
+ $em = $this->getDoctrine()->getManager();
+
+ foreach ($tags as $tag) {
+ if (count($tag->getEntries()) === 0) {
+ $em->remove($tag);
+ }
+ }
+
+ $em->flush();
+ }
+
/**
* Validate that a rule can be edited/deleted by the current user.
*
return $config;
}
+
+ /**
+ * Delete account for current user.
+ *
+ * @Route("/account/delete", name="delete_account")
+ *
+ * @param Request $request
+ *
+ * @throws AccessDeniedHttpException
+ *
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ */
+ public function deleteAccountAction(Request $request)
+ {
+ $enabledUsers = $this->getDoctrine()
+ ->getRepository('WallabagUserBundle:User')
+ ->getSumEnabledUsers();
+
+ if ($enabledUsers <= 1) {
+ throw new AccessDeniedHttpException();
+ }
+
+ $user = $this->getUser();
+
+ // logout current user
+ $this->get('security.token_storage')->setToken(null);
+ $request->getSession()->invalidate();
+
+ $em = $this->get('fos_user.user_manager');
+ $em->deleteUser($user);
+
+ return $this->redirect($this->generateUrl('fos_user_security_login'));
+ }
}
$flatTags = [];
- foreach ($tags as $key => $tag) {
+ foreach ($tags as $tag) {
$nbEntries = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
- ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag['id']);
+ ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag->getId());
$flatTags[] = [
- 'id' => $tag['id'],
- 'label' => $tag['label'],
- 'slug' => $tag['slug'],
+ 'id' => $tag->getId(),
+ 'label' => $tag->getLabel(),
+ 'slug' => $tag->getSlug(),
'nbEntries' => $nbEntries,
];
}
*
* @XmlRoot("entry")
* @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntryRepository")
- * @ORM\Table(name="`entry`")
+ * @ORM\Table(name="`entry`", options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"})
* @ORM\HasLifecycleCallbacks()
* @Hateoas\Relation("self", href = "expr('/api/entries/' ~ object.getId())")
*/
* @ORM\JoinTable(
* name="entry_tag",
* joinColumns={
- * @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
+ * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
* },
* inverseJoinColumns={
- * @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
+ * @ORM\JoinColumn(name="tag_id", referencedColumnName="id", onDelete="cascade")
* }
* )
*/
return $qb->getQuery()->getSingleScalarResult();
}
+
+ /**
+ * Remove all entries for a user id.
+ * Used when a user want to reset all informations.
+ *
+ * @param int $userId
+ */
+ public function removeAllByUserId($userId)
+ {
+ $this->getEntityManager()
+ ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = :userId')
+ ->setParameter('userId', $userId)
+ ->execute();
+ }
}
/**
* Find all tags per user.
+ * Instead of just left joined on the Entry table, we select only id and group by id to avoid tag multiplication in results.
+ * Once we have all tags id, we can safely request them one by one.
+ * This'll still be fastest than the previous query.
*
* @param int $userId
*
*/
public function findAllTags($userId)
{
- return $this->createQueryBuilder('t')
- ->select('t.slug', 't.label', 't.id')
+ $ids = $this->createQueryBuilder('t')
+ ->select('t.id')
->leftJoin('t.entries', 'e')
->where('e.user = :userId')->setParameter('userId', $userId)
- ->groupBy('t.slug')
- ->addGroupBy('t.label')
- ->addGroupBy('t.id')
+ ->groupBy('t.id')
->getQuery()
->getArrayResult();
+
+ $tags = [];
+ foreach ($ids as $id) {
+ $tags[] = $this->find($id);
+ }
+
+ return $tags;
}
/**
arguments:
- '@twig'
- '%kernel.debug%'
+
+ wallabag_core.subscriber.sqlite_cascade_delete:
+ class: Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber
+ arguments:
+ - "@doctrine"
+ tags:
+ - { name: doctrine.event_subscriber }
name_label: 'Navn'
email_label: 'Emailadresse'
# twoFactorAuthentication_label: 'Two factor authentication'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Gammel adgangskode'
new_password_label: 'Ny adgangskode'
# tagging_rules_deleted: 'Tagging rule deleted'
# user_added: 'User "%username%" added'
# rss_token_updated: 'RSS token updated'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
# entry_already_saved: 'Entry already saved on %date%'
name_label: 'Name'
email_label: 'E-Mail-Adresse'
twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Altes Kennwort'
new_password_label: 'Neues Kennwort'
tagging_rules_deleted: 'Tagging-Regel gelöscht'
user_added: 'Benutzer "%username%" erstellt'
rss_token_updated: 'RSS-Token aktualisiert'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Eintrag bereits am %date% gespeichert'
name_label: 'Name'
email_label: 'Email'
twoFactorAuthentication_label: 'Two factor authentication'
+ delete:
+ title: Delete my account (a.k.a danger zone)
+ description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ button: Delete my account
+ reset:
+ title: Reset area (a.k.a danger zone)
+ description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ annotations: Remove ALL annotations
+ tags: Remove ALL tags
+ entries: Remove ALL entries
+ confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Current password'
new_password_label: 'New password'
tagging_rules_updated: 'Tagging rules updated'
tagging_rules_deleted: 'Tagging rule deleted'
rss_token_updated: 'RSS token updated'
+ annotations_reset: Annotations reset
+ tags_reset: Tags reset
+ entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Entry already saved on %date%'
name_label: 'Nombre'
email_label: 'Direccion e-mail'
twoFactorAuthentication_label: 'Autentificación de dos factores'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Contraseña actual'
new_password_label: 'Nueva contraseña'
tagging_rules_deleted: 'Regla de etiquetado actualizada'
user_added: 'Usuario "%username%" añadido'
rss_token_updated: 'RSS token actualizado'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Entrada ya guardada por %fecha%'
name_label: 'نام'
email_label: 'نشانی ایمیل'
twoFactorAuthentication_label: 'تأیید ۲مرحلهای'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'رمز قدیمی'
new_password_label: 'رمز تازه'
tagging_rules_deleted: 'قانون برچسبگذاری پاک شد'
user_added: 'کابر "%username%" افزوده شد'
rss_token_updated: 'کد آر-اس-اس بهروز شد'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
name_label: 'Nom'
email_label: 'Adresse e-mail'
twoFactorAuthentication_label: 'Double authentification'
+ 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'
+ 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)
form_password:
old_password_label: 'Mot de passe actuel'
new_password_label: 'Nouveau mot de passe'
field_grant_types: 'Type de privilège accordé'
no_client: 'Aucun client pour le moment'
remove:
- warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRREVERSIBLE !'
+ warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRRÉVERSIBLE !'
warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l'utilisaient ne fonctionneront plus avec votre compte wallabag."
action: 'Supprimer le client %name%'
client:
tagging_rules_deleted: 'Règle supprimée'
user_added: 'Utilisateur "%username%" ajouté'
rss_token_updated: 'Jeton RSS mis à jour'
+ annotations_reset: Annotations supprimées
+ tags_reset: Tags supprimés
+ entries_reset: Articles supprimés
entry:
notice:
- entry_already_saved: 'Article déjà sauvergardé le %date%'
+ entry_already_saved: 'Article déjà sauvegardé le %date%'
entry_saved: 'Article enregistré'
entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu'
entry_updated: 'Article mis à jour'
name_label: 'Nome'
email_label: 'E-mail'
twoFactorAuthentication_label: 'Two factor authentication'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Password corrente'
new_password_label: 'Nuova password'
tagging_rules_deleted: 'Regola di tagging aggiornate'
user_added: 'Utente "%username%" aggiunto'
rss_token_updated: 'RSS token aggiornato'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Contenuto già salvato in data %date%'
internal_settings: 'Configuracion interna'
import: 'Importar'
howto: 'Ajuda'
- developer: 'Desvolopador'
+ developer: 'Desvolopaire'
logout: 'Desconnexion'
about: 'A prepaus'
search: 'Cercar'
save_link: 'Enregistrar un novèl article'
back_to_unread: 'Tornar als articles pas legits'
- # users_management: 'Users management'
+ users_management: 'Gestion dels utilizaires'
top:
add_new_entry: 'Enregistrar un novèl article'
search: 'Cercar'
social: 'Social'
powered_by: 'propulsat per'
about: 'A prepaus'
- # stats: Since %user_creation% you read %nb_archives% articles. That is about %per_day% a day!
+ stats: "Dempuèi %user_creation% avètz legit %nb_archives% articles. Es a l'entorn de %per_day% per jorn !"
config:
page_title: 'Configuracion'
name_label: 'Nom'
email_label: 'Adreça de corrièl'
twoFactorAuthentication_label: 'Dobla autentificacion'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Senhal actual'
new_password_label: 'Senhal novèl'
if_label: 'se'
then_tag_as_label: 'alara atribuir las etiquetas'
delete_rule_label: 'suprimir'
- # edit_rule_label: 'edit'
+ edit_rule_label: 'modificar'
rule_label: 'Règla'
tags_label: 'Etiquetas'
faq:
is_public_label: 'Public'
save_label: 'Enregistrar'
public:
- # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>"
+ shared_by_wallabag: "Aqueste article es estat partejat per <a href='%wallabag_instance%'>wallabag</a>"
about:
page_title: 'A prepaus'
quickstart:
page_title: 'Per ben començar'
- # more: 'More…'
+ more: 'Mai…'
intro:
title: 'Benvenguda sus wallabag !'
paragraph_1: "Anem vos guidar per far lo torn de la proprietat e vos presentar unas fonccionalitats que vos poirián interessar per vos apropriar aquesta aisina."
paragraph_2: 'Seguètz-nos '
configure:
- title: "Configuratz l'aplicacio"
- # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
+ title: "Configuratz l'aplicacion"
+ description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag."
language: "Cambiatz la lenga e l'estil de l'aplicacion"
rss: 'Activatz los fluxes RSS'
tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
import: 'Configurar los impòrt'
first_steps:
title: 'Primièrs passes'
- # description: "Now wallabag is well configured, it's time to archive the web. You can click on the top right sign + to add a link."
+ 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."
new_article: 'Ajustatz vòstre primièr article'
unread_articles: 'E racaptatz-lo !'
migrate:
readability: 'Migrar dempuèi Readability'
instapaper: 'Migrar dempuèi Instapaper'
developer:
- title: 'Pels desvolopadors'
- # description: 'We also thought to the developers: Docker, API, translations, etc.'
+ title: 'Pels desvolopaires'
+ description: 'Avèm tanben pensat als desvolopaires : Docker, API, traduccions, etc.'
create_application: 'Crear vòstra aplicacion tèrça'
- # use_docker: 'Use Docker to install wallabag'
+ use_docker: 'Utilizar Docker per installar wallabag'
docs:
title: 'Documentacion complèta'
- # description: "There are so much features in wallabag. Don't hesitate to read the manual to know them and to learn how to use them."
- annotate: 'Anotatar vòstre article'
+ description: "I a un fum de fonccionalitats dins wallabag. Esitetz pas a legir lo manual per las conéisser e aprendre a las utilizar."
+ annotate: 'Anotar vòstre article'
export: 'Convertissètz vòstres articles en ePub o en PDF'
search_filters: "Aprenètz a utilizar lo motor de recèrca e los filtres per retrobar l'article que vos interèssa"
fetching_errors: "Qué far se mon article es pas recuperat coma cal ?"
warn_message_2: "Se suprimissètz un client, totas las aplicacions que l'emplegan foncionaràn pas mai amb vòstre compte wallabag."
action: 'Suprimir aqueste client'
client:
- page_title: 'Desvlopador > Novèl client'
+ page_title: 'Desvolopaire > Novèl client'
page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion."
form:
name_label: "Nom del client"
save_label: 'Crear un novèl client'
action_back: 'Retorn'
client_parameter:
- page_title: 'Desvolopador > Los paramètres de vòstre client'
+ page_title: 'Desvolopaire > Los paramètres de vòstre client'
page_description: 'Vaquí los paramètres de vòstre client'
field_name: 'Nom del client'
field_id: 'ID Client'
back: 'Retour'
read_howto: 'Legir "cossí crear ma primièra aplicacion"'
howto:
- page_title: 'Desvolopador > Cossí crear ma primièra aplicacion'
+ page_title: 'Desvolopaire > Cossí crear ma primièra aplicacion'
description:
paragraph_1: "Las comandas seguentas utilizan la <a href=\"https://github.com/jkbrzt/httpie\">bibliotèca HTTPie</a>. Asseguratz-vos que siasqueòu installadas abans de l'utilizar."
paragraph_2: "Vos cal un geton per escambiar entre vòstra aplicacion e l'API de wallabar."
back: 'Retorn'
user:
- # page_title: Users management
- # new_user: Create a new user
- # edit_user: Edit an existing user
- # description: "Here you can manage all users (create, edit and delete)"
- # list:
- # actions: Actions
- # edit_action: Edit
- # yes: Yes
- # no: No
- # create_new_one: Create a new user
+ page_title: 'Gestion dels utilizaires'
+ new_user: 'Crear un novèl utilizaire'
+ edit_user: 'Modificar un utilizaire existent'
+ description: "Aquí podètz gerir totes los utilizaires (crear, modificar e suprimir)"
+ list:
+ actions: 'Accions'
+ edit_action: 'Modificar'
+ yes: 'Òc'
+ no: 'Non'
+ create_new_one: 'Crear un novèl utilizaire'
form:
username_label: "Nom d'utilizaire"
- # name_label: 'Name'
+ name_label: 'Nom'
password_label: 'Senhal'
repeat_new_password_label: 'Confirmatz vòstre novèl senhal'
plain_password_label: 'Senhal en clar'
email_label: 'Adreça de corrièl'
- # enabled_label: 'Enabled'
- # locked_label: 'Locked'
- # last_login_label: 'Last login'
- # twofactor_label: Two factor authentication
- # save: Save
- # delete: Delete
- # delete_confirm: Are you sure?
- # back_to_list: Back to list
+ enabled_label: 'Actiu'
+ locked_label: 'Varrolhat'
+ last_login_label: 'Darrièra connexion'
+ twofactor_label: 'Autentificacion doble-factor'
+ save: 'Enregistrar'
+ delete: 'Suprimir'
+ delete_confirm: 'Sètz segur ?'
+ back_to_list: 'Tornar a la lista'
error:
# page_title: An error occurred
rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
tagging_rules_updated: 'Règlas misa a jorn'
tagging_rules_deleted: 'Règla suprimida'
- user_added: 'Utilizaire "%username%" apondut'
+ user_added: 'Utilizaire "%username%" ajustat'
rss_token_updated: 'Geton RSS mes a jorn'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Article ja salvargardat lo %date%'
entry_reloaded_failed: "L'article es estat cargat de nòu mai la recuperacion del contengut a fracassat"
entry_archived: 'Article marcat coma legit'
entry_unarchived: 'Article marcat coma pas legit'
- entry_starred: 'Article apondut dins los favorits'
+ entry_starred: 'Article ajustat dins los favorits'
entry_unstarred: 'Article quitat dels favorits'
entry_deleted: 'Article suprimit'
tag:
notice:
- tag_added: 'Etiqueta aponduda'
+ tag_added: 'Etiqueta ajustada'
import:
notice:
failed: "L'importacion a fracassat, mercés de tornar ensajar"
name_label: 'Nazwa'
email_label: 'Adres email'
twoFactorAuthentication_label: 'Autoryzacja dwuetapowa'
+ delete:
+ title: Usuń moje konto (niebezpieczna strefa !)
+ description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany.
+ confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć)
+ button: Usuń moje konto
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Stare hasło'
new_password_label: 'Nowe hasło'
tagging_rules_deleted: 'Reguła tagowania usunięta'
user_added: 'Użytkownik "%username%" dodany'
rss_token_updated: 'Token kanału RSS zaktualizowany'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Wpis już został dodany %date%'
name_label: 'Nume'
email_label: 'E-mail'
# twoFactorAuthentication_label: 'Two factor authentication'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Parola veche'
new_password_label: 'Parola nouă'
# tagging_rules_deleted: 'Tagging rule deleted'
# user_added: 'User "%username%" added'
# rss_token_updated: 'RSS token updated'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
# entry_already_saved: 'Entry already saved on %date%'
name_label: 'İsim'
email_label: 'E-posta'
twoFactorAuthentication_label: 'İki adımlı doğrulama'
+ delete:
+ # title: Delete my account (a.k.a danger zone)
+ # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
+ # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
+ # button: Delete my account
+ reset:
+ # title: Reset area (a.k.a danger zone)
+ # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+ # annotations: Remove ALL annotations
+ # tags: Remove ALL tags
+ # entries: Remove ALL entries
+ # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
form_password:
old_password_label: 'Eski şifre'
new_password_label: 'Yeni şifre'
tagging_rules_deleted: 'Tagging rule deleted'
user_added: 'User "%username%" added'
rss_token_updated: 'RSS token updated'
+ # annotations_reset: Annotations reset
+ # tags_reset: Tags reset
+ # entries_reset: Entries reset
entry:
notice:
entry_already_saved: 'Entry already saved on %date%'
</fieldset>
{% endif %}
+ <h2>{{ 'config.reset.title'|trans }}</h2>
+ <fieldset class="w500p inline">
+ <p>{{ 'config.reset.description'|trans }}</p>
+ <ul>
+ <li>
+ <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.annotations'|trans }}
+ </a>
+ </li>
+ <li>
+ <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.tags'|trans }}
+ </a>
+ </li>
+ <li>
+ <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.entries'|trans }}
+ </a>
+ </li>
+ </ul>
+ </fieldset>
+
{{ form_widget(form.user._token) }}
{{ form_widget(form.user.save) }}
</form>
+ {% if enabled_users > 1 %}
+ <h2>{{ 'config.form_user.delete.title'|trans }}</h2>
+
+ <p>{{ 'config.form_user.delete.description'|trans }}</p>
+ <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
+ {{ 'config.form_user.delete.button'|trans }}
+ </a>
+ {% endif %}
+
<h2>{{ 'config.tab_menu.password'|trans }}</h2>
{{ form_start(form.pwd) }}
{{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
{{ form_widget(form.user._token) }}
</form>
+
+ <br /><hr /><br />
+
+ <div class="row">
+ <h5>{{ 'config.reset.title'|trans }}</h5>
+ <p>{{ 'config.reset.description'|trans }}</p>
+ <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.annotations'|trans }}
+ </a>
+ <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.tags'|trans }}
+ </a>
+ <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+ {{ 'config.reset.entries'|trans }}
+ </a>
+ </div>
+
+ {% if enabled_users > 1 %}
+ <br /><hr /><br />
+
+ <div class="row">
+ <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
+ <p>{{ 'config.form_user.delete.description'|trans }}</p>
+ <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
+ {{ 'config.form_user.delete.button'|trans }}
+ </a>
+ </div>
+ {% endif %}
</div>
<div id="set4" class="col s12">
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Subscriber;
+
+use Doctrine\Common\EventSubscriber;
+use Doctrine\ORM\Event\LifecycleEventArgs;
+use Wallabag\CoreBundle\Entity\Entry;
+use Doctrine\Bundle\DoctrineBundle\Registry;
+
+/**
+ * SQLite doesn't care about cascading remove, so we need to manually remove associated stuf for an Entry.
+ * Foreign Key Support can be enabled by running `PRAGMA foreign_keys = ON;` at runtime (AT RUNTIME !).
+ * But it needs a compilation flag that not all SQLite instance has ...
+ *
+ * @see https://www.sqlite.org/foreignkeys.html#fk_enable
+ */
+class SQLiteCascadeDeleteSubscriber implements EventSubscriber
+{
+ private $doctrine;
+
+ /**
+ * @param \Doctrine\Bundle\DoctrineBundle\Registry $doctrine
+ */
+ public function __construct(Registry $doctrine)
+ {
+ $this->doctrine = $doctrine;
+ }
+
+ /**
+ * @return array
+ */
+ public function getSubscribedEvents()
+ {
+ return [
+ 'preRemove',
+ ];
+ }
+
+ /**
+ * We removed everything related to the upcoming removed entry because SQLite can't handle it on it own.
+ * We do it in the preRemove, because we can't retrieve tags in the postRemove (because the entry id is gone).
+ *
+ * @param LifecycleEventArgs $args
+ */
+ public function preRemove(LifecycleEventArgs $args)
+ {
+ $entity = $args->getEntity();
+
+ if (!$this->doctrine->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver ||
+ !$entity instanceof Entry) {
+ return;
+ }
+
+ $em = $this->doctrine->getManager();
+
+ if (null !== $entity->getTags()) {
+ foreach ($entity->getTags() as $tag) {
+ $entity->removeTag($tag);
+ }
+ }
+
+ if (null !== $entity->getAnnotations()) {
+ foreach ($entity->getAnnotations() as $annotation) {
+ $em->remove($annotation);
+ }
+ }
+
+ $em->flush();
+ }
+}
->getQuery()
->getSingleResult();
}
+
+ /**
+ * Count how many users are enabled.
+ *
+ * @return int
+ */
+ public function getSumEnabledUsers()
+ {
+ return $this->createQueryBuilder('u')
+ ->select('count(u)')
+ ->andWhere('u.expired = false')
+ ->getQuery()
+ ->getSingleScalarResult();
+ }
}
arguments:
- WallabagUserBundle:User
- wallabag_user.create_config:
+ wallabag_user.listener.create_config:
class: Wallabag\UserBundle\EventListener\CreateConfigListener
arguments:
- "@doctrine.orm.entity_manager"
namespace Tests\AnnotationBundle\Controller;
use Tests\Wallabag\AnnotationBundle\WallabagAnnotationTestCase;
+use Wallabag\AnnotationBundle\Entity\Annotation;
+use Wallabag\CoreBundle\Entity\Entry;
class AnnotationControllerTest extends WallabagAnnotationTestCase
{
- public function testGetAnnotations()
+ /**
+ * This data provider allow to tests annotation from the :
+ * - API POV (when user use the api to manage annotations)
+ * - and User POV (when user use the web interface - using javascript - to manage annotations)
+ */
+ public function dataForEachAnnotations()
{
- $annotation = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagAnnotationBundle:Annotation')
- ->findOneByUsername('admin');
+ return [
+ ['/api/annotations'],
+ ['annotations'],
+ ];
+ }
+
+ /**
+ * Test fetching annotations for an entry.
+ *
+ * @dataProvider dataForEachAnnotations
+ */
+ public function testGetAnnotations($prefixUrl)
+ {
+ $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUserName('admin');
+ $entry = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUsernameAndNotArchived('admin');
- if (!$annotation) {
- $this->markTestSkipped('No content found in db.');
+ $annotation = new Annotation($user);
+ $annotation->setEntry($entry);
+ $annotation->setText('This is my annotation /o/');
+ $annotation->setQuote('content');
+
+ $em->persist($annotation);
+ $em->flush();
+
+ if ('annotations' === $prefixUrl) {
+ $this->logInAs('admin');
}
- $this->logInAs('admin');
- $crawler = $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json');
+ $this->client->request('GET', $prefixUrl.'/'.$entry->getId().'.json');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
- $this->assertEquals(1, $content['total']);
+ $this->assertGreaterThanOrEqual(1, $content['total']);
$this->assertEquals($annotation->getText(), $content['rows'][0]['text']);
+
+ // we need to re-fetch the annotation becase after the flush, it has been "detached" from the entity manager
+ $annotation = $em->getRepository('WallabagAnnotationBundle:Annotation')->findAnnotationById($annotation->getId());
+ $em->remove($annotation);
+ $em->flush();
}
- public function testSetAnnotation()
+ /**
+ * Test creating an annotation for an entry.
+ *
+ * @dataProvider dataForEachAnnotations
+ */
+ public function testSetAnnotation($prefixUrl)
{
- $this->logInAs('admin');
+ $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
- $entry = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
+ if ('annotations' === $prefixUrl) {
+ $this->logInAs('admin');
+ }
+
+ /** @var Entry $entry */
+ $entry = $em
->getRepository('WallabagCoreBundle:Entry')
->findOneByUsernameAndNotArchived('admin');
'quote' => 'my quote',
'ranges' => ['start' => '', 'startOffset' => 24, 'end' => '', 'endOffset' => 31],
]);
- $crawler = $this->client->request('POST', 'annotations/'.$entry->getId().'.json', [], [], $headers, $content);
+ $this->client->request('POST', $prefixUrl.'/'.$entry->getId().'.json', [], [], $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$this->assertEquals('my annotation', $content['text']);
$this->assertEquals('my quote', $content['quote']);
+ /** @var Annotation $annotation */
$annotation = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagAnnotationBundle:Annotation')
$this->assertEquals('my annotation', $annotation->getText());
}
- public function testEditAnnotation()
+ /**
+ * Test editing an existing annotation.
+ *
+ * @dataProvider dataForEachAnnotations
+ */
+ public function testEditAnnotation($prefixUrl)
{
- $annotation = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagAnnotationBundle:Annotation')
- ->findOneByUsername('admin');
+ $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
- $this->logInAs('admin');
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUserName('admin');
+ $entry = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUsernameAndNotArchived('admin');
+
+ $annotation = new Annotation($user);
+ $annotation->setEntry($entry);
+ $annotation->setText('This is my annotation /o/');
+ $annotation->setQuote('my quote');
+
+ $em->persist($annotation);
+ $em->flush();
$headers = ['CONTENT_TYPE' => 'application/json'];
$content = json_encode([
'text' => 'a modified annotation',
]);
- $crawler = $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content);
+ $this->client->request('PUT', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals('a modified annotation', $content['text']);
$this->assertEquals('my quote', $content['quote']);
- $annotationUpdated = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
+ /** @var Annotation $annotationUpdated */
+ $annotationUpdated = $em
->getRepository('WallabagAnnotationBundle:Annotation')
->findOneById($annotation->getId());
$this->assertEquals('a modified annotation', $annotationUpdated->getText());
+
+ $em->remove($annotationUpdated);
+ $em->flush();
}
- public function testDeleteAnnotation()
+ /**
+ * Test deleting an annotation.
+ *
+ * @dataProvider dataForEachAnnotations
+ */
+ public function testDeleteAnnotation($prefixUrl)
{
- $annotation = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
- ->getRepository('WallabagAnnotationBundle:Annotation')
- ->findOneByUsername('admin');
+ $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
- $this->logInAs('admin');
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUserName('admin');
+ $entry = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneByUsernameAndNotArchived('admin');
+
+ $annotation = new Annotation($user);
+ $annotation->setEntry($entry);
+ $annotation->setText('This is my annotation /o/');
+ $annotation->setQuote('my quote');
+
+ $em->persist($annotation);
+ $em->flush();
+
+ if ('annotations' === $prefixUrl) {
+ $this->logInAs('admin');
+ }
$headers = ['CONTENT_TYPE' => 'application/json'];
$content = json_encode([
'text' => 'a modified annotation',
]);
- $crawler = $this->client->request('DELETE', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content);
+ $this->client->request('DELETE', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
- $this->assertEquals('a modified annotation', $content['text']);
+ $this->assertEquals('This is my annotation /o/', $content['text']);
- $annotationDeleted = $this->client->getContainer()
- ->get('doctrine.orm.entity_manager')
+ $annotationDeleted = $em
->getRepository('WallabagAnnotationBundle:Annotation')
->findOneById($annotation->getId());
abstract class WallabagAnnotationTestCase extends WebTestCase
{
/**
- * @var Client
+ * @var \Symfony\Bundle\FrameworkBundle\Client
*/
protected $client = null;
}
/**
- * @return Client
+ * @return \Symfony\Bundle\FrameworkBundle\Client
*/
protected function createAuthorizedClient()
{
$firewallName = $container->getParameter('fos_user.firewall_name');
$this->user = $userManager->findUserBy(['username' => 'admin']);
- $loginManager->loginUser($firewallName, $this->user);
+ $loginManager->logInUser($firewallName, $this->user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken()));
$this->assertEquals($entry->getUserEmail(), $content['user_email']);
$this->assertEquals($entry->getUserId(), $content['user_id']);
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
+ }
+
+ public function testExportEntry()
+ {
+ $entry = $this->client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findOneBy(['user' => 1, 'isArchived' => false]);
+
+ if (!$entry) {
+ $this->markTestSkipped('No content found in db.');
+ }
+
+ $this->client->request('GET', '/api/entries/'.$entry->getId().'/export.epub');
+ $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
+
+ // epub format got the content type in the content
+ $this->assertContains('application/epub', $this->client->getResponse()->getContent());
+ $this->assertEquals('application/epub+zip', $this->client->getResponse()->headers->get('Content-Type'));
+
+ // re-auth client for mobi
+ $client = $this->createAuthorizedClient();
+ $client->request('GET', '/api/entries/'.$entry->getId().'/export.mobi');
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $this->assertEquals('application/x-mobipocket-ebook', $client->getResponse()->headers->get('Content-Type'));
+
+ // re-auth client for pdf
+ $client = $this->createAuthorizedClient();
+ $client->request('GET', '/api/entries/'.$entry->getId().'/export.pdf');
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $this->assertContains('PDF-', $client->getResponse()->getContent());
+ $this->assertEquals('application/pdf', $client->getResponse()->headers->get('Content-Type'));
+
+ // re-auth client for pdf
+ $client = $this->createAuthorizedClient();
+ $client->request('GET', '/api/entries/'.$entry->getId().'/export.txt');
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $this->assertContains('text/plain', $client->getResponse()->headers->get('Content-Type'));
+
+ // re-auth client for pdf
+ $client = $this->createAuthorizedClient();
+ $client->request('GET', '/api/entries/'.$entry->getId().'/export.csv');
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $this->assertContains('application/csv', $client->getResponse()->headers->get('Content-Type'));
}
public function testGetOneEntryWrongUser()
$this->assertEquals(1, $content['page']);
$this->assertGreaterThanOrEqual(1, $content['pages']);
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetEntriesWithFullOptions()
$this->assertContains('since=1443274283', $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetStarredEntries()
$this->assertContains('sort=updated', $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetArchiveEntries()
$this->assertContains('archive=1', $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetTaggedEntries()
$this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetDatedEntries()
$this->assertContains('since=1443274283', $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testGetDatedSupEntries()
$this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
}
- $this->assertTrue(
- $this->client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
+ $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
}
public function testDeleteEntry()
namespace Tests\Wallabag\CoreBundle\Controller;
use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
+use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\UserBundle\Entity\User;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\CoreBundle\Entity\Tag;
+use Wallabag\AnnotationBundle\Entity\Annotation;
class ConfigControllerTest extends WallabagCoreTestCase
{
$config->set('demo_mode_enabled', 0);
$config->set('demo_mode_username', 'wallabag');
}
+
+ public function testDeleteUserButtonVisibility()
+ {
+ $this->logInAs('admin');
+ $client = $this->getClient();
+
+ $crawler = $client->request('GET', '/config');
+
+ $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+ $this->assertContains('config.form_user.delete.button', $body[0]);
+
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUsername('empty');
+ $user->setExpired(1);
+ $em->persist($user);
+
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUsername('bob');
+ $user->setExpired(1);
+ $em->persist($user);
+
+ $em->flush();
+
+ $crawler = $client->request('GET', '/config');
+
+ $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+ $this->assertNotContains('config.form_user.delete.button', $body[0]);
+
+ $client->request('GET', '/account/delete');
+ $this->assertEquals(403, $client->getResponse()->getStatusCode());
+
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUsername('empty');
+ $user->setExpired(0);
+ $em->persist($user);
+
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->findOneByUsername('bob');
+ $user->setExpired(0);
+ $em->persist($user);
+
+ $em->flush();
+ }
+
+ public function testDeleteAccount()
+ {
+ $client = $this->getClient();
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $user = new User();
+ $user->setName('Wallace');
+ $user->setEmail('wallace@wallabag.org');
+ $user->setUsername('wallace');
+ $user->setPlainPassword('wallace');
+ $user->setEnabled(true);
+ $user->addRole('ROLE_SUPER_ADMIN');
+
+ $em->persist($user);
+
+ $config = new Config($user);
+
+ $config->setTheme('material');
+ $config->setItemsPerPage(30);
+ $config->setReadingSpeed(1);
+ $config->setLanguage('en');
+ $config->setPocketConsumerKey('xxxxx');
+
+ $em->persist($config);
+ $em->flush();
+
+ $this->logInAs('wallace');
+ $loggedInUserId = $this->getLoggedInUserId();
+
+ // create entry to check after user deletion
+ // that this entry is also deleted
+ $crawler = $client->request('GET', '/new');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $form = $crawler->filter('form[name=entry]')->form();
+ $data = [
+ 'entry[url]' => $url = 'https://github.com/wallabag/wallabag',
+ ];
+
+ $client->submit($form, $data);
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->request('GET', '/config');
+
+ $deleteLink = $crawler->filter('.delete-account')->last()->link();
+
+ $client->click($deleteLink);
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+ $user = $em
+ ->getRepository('WallabagUserBundle:User')
+ ->createQueryBuilder('u')
+ ->where('u.username = :username')->setParameter('username', 'wallace')
+ ->getQuery()
+ ->getOneOrNullResult()
+ ;
+
+ $this->assertNull($user);
+
+ $entries = $client->getContainer()
+ ->get('doctrine.orm.entity_manager')
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->findByUser($loggedInUserId);
+
+ $this->assertEmpty($entries);
+ }
+
+ public function testReset()
+ {
+ $this->logInAs('empty');
+ $client = $this->getClient();
+
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
+
+ $tag = new Tag();
+ $tag->setLabel('super');
+ $em->persist($tag);
+
+ $entry = new Entry($user);
+ $entry->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
+ $entry->setContent('Youhou');
+ $entry->setTitle('Youhou');
+ $entry->addTag($tag);
+ $em->persist($entry);
+
+ $entry2 = new Entry($user);
+ $entry2->setUrl('http://www.lemonde.de/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
+ $entry2->setContent('Youhou');
+ $entry2->setTitle('Youhou');
+ $entry2->addTag($tag);
+ $em->persist($entry2);
+
+ $annotation = new Annotation($user);
+ $annotation->setText('annotated');
+ $annotation->setQuote('annotated');
+ $annotation->setRanges([]);
+ $annotation->setEntry($entry);
+ $em->persist($annotation);
+
+ $em->flush();
+
+ // reset annotations
+ $crawler = $client->request('GET', '/config#set3');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->click($crawler->selectLink('config.reset.annotations')->link());
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+ $this->assertContains('flashes.config.notice.annotations_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+ $annotationsReset = $em
+ ->getRepository('WallabagAnnotationBundle:Annotation')
+ ->findAnnotationsByPageId($entry->getId(), $user->getId());
+
+ $this->assertEmpty($annotationsReset, 'Annotations were reset');
+
+ // reset tags
+ $crawler = $client->request('GET', '/config#set3');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->click($crawler->selectLink('config.reset.tags')->link());
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+ $this->assertContains('flashes.config.notice.tags_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+ $tagReset = $em
+ ->getRepository('WallabagCoreBundle:Tag')
+ ->countAllTags($user->getId());
+
+ $this->assertEquals(0, $tagReset, 'Tags were reset');
+
+ // reset entries
+ $crawler = $client->request('GET', '/config#set3');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+ $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+ $entryReset = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->countAllEntriesByUsername($user->getId());
+
+ $this->assertEquals(0, $entryReset, 'Entries were reset');
+ }
+
+ public function testResetEntriesCascade()
+ {
+ $this->logInAs('empty');
+ $client = $this->getClient();
+
+ $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+ $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
+
+ $tag = new Tag();
+ $tag->setLabel('super');
+ $em->persist($tag);
+
+ $entry = new Entry($user);
+ $entry->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
+ $entry->setContent('Youhou');
+ $entry->setTitle('Youhou');
+ $entry->addTag($tag);
+ $em->persist($entry);
+
+ $annotation = new Annotation($user);
+ $annotation->setText('annotated');
+ $annotation->setQuote('annotated');
+ $annotation->setRanges([]);
+ $annotation->setEntry($entry);
+ $em->persist($annotation);
+
+ $em->flush();
+
+ $crawler = $client->request('GET', '/config#set3');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
+
+ $this->assertEquals(302, $client->getResponse()->getStatusCode());
+ $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+ $entryReset = $em
+ ->getRepository('WallabagCoreBundle:Entry')
+ ->countAllEntriesByUsername($user->getId());
+
+ $this->assertEquals(0, $entryReset, 'Entries were reset');
+
+ $tagReset = $em
+ ->getRepository('WallabagCoreBundle:Tag')
+ ->countAllTags($user->getId());
+
+ $this->assertEquals(0, $tagReset, 'Tags were reset');
+
+ $annotationsReset = $em
+ ->getRepository('WallabagAnnotationBundle:Annotation')
+ ->findAnnotationsByPageId($entry->getId(), $user->getId());
+
+ $this->assertEmpty($annotationsReset, 'Annotations were reset');
+ }
}