aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php13
-rw-r--r--src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php18
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml242
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml14
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.es.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/base.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Static/quickstart.html.twig25
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Static/quickstart.html.twig15
-rw-r--r--src/Wallabag/ImportBundle/Controller/PocketController.php18
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php3
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php2
-rw-r--r--src/Wallabag/ImportBundle/Form/Type/UploadImportType.php5
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php23
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php15
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV2Import.php2
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig7
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig5
-rw-r--r--src/Wallabag/ImportBundle/Tests/Controller/WallabagV1ControllerTest.php44
-rw-r--r--src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php94
-rw-r--r--src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php33
-rw-r--r--src/Wallabag/ImportBundle/Tests/Import/WallabagV2ImportTest.php33
-rw-r--r--src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1-read.json53
-rw-r--r--src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v2-read.json28
23 files changed, 677 insertions, 20 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 03990088..35a90edd 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -97,6 +97,8 @@ class WallabagRestController extends FOSRestController
97 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, 97 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
98 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."}, 98 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
99 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, 99 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
100 * {"name"="starred", "dataType"="boolean", "required"=false, "format"="true or false", "description"="entry already starred"},
101 * {"name"="archive", "dataType"="boolean", "required"=false, "format"="true or false", "description"="entry already archived"},
100 * } 102 * }
101 * ) 103 * )
102 * 104 *
@@ -107,6 +109,8 @@ class WallabagRestController extends FOSRestController
107 $this->validateAuthentication(); 109 $this->validateAuthentication();
108 110
109 $url = $request->request->get('url'); 111 $url = $request->request->get('url');
112 $isArchived = $request->request->get('archive');
113 $isStarred = $request->request->get('starred');
110 114
111 $entry = $this->get('wallabag_core.content_proxy')->updateEntry( 115 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
112 new Entry($this->getUser()), 116 new Entry($this->getUser()),
@@ -118,8 +122,17 @@ class WallabagRestController extends FOSRestController
118 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); 122 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
119 } 123 }
120 124
125 if (true === (bool) $isStarred) {
126 $entry->setStarred(true);
127 }
128
129 if (true === (bool) $isArchived) {
130 $entry->setArchived(true);
131 }
132
121 $em = $this->getDoctrine()->getManager(); 133 $em = $this->getDoctrine()->getManager();
122 $em->persist($entry); 134 $em->persist($entry);
135
123 $em->flush(); 136 $em->flush();
124 137
125 $json = $this->get('serializer')->serialize($entry, 'json'); 138 $json = $this->get('serializer')->serialize($entry, 'json');
diff --git a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
index 22894a77..630b75bf 100644
--- a/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
+++ b/src/Wallabag/ApiBundle/Tests/Controller/WallabagRestControllerTest.php
@@ -162,6 +162,24 @@ class WallabagRestControllerTest extends WallabagApiTestCase
162 $this->assertCount(1, $content['tags']); 162 $this->assertCount(1, $content['tags']);
163 } 163 }
164 164
165 public function testPostArchivedEntry()
166 {
167 $this->client->request('POST', '/api/entries.json', array(
168 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
169 'archive' => true,
170 'starred' => false,
171 ));
172
173 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
174
175 $content = json_decode($this->client->getResponse()->getContent(), true);
176
177 $this->assertGreaterThan(0, $content['id']);
178 $this->assertEquals('http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html', $content['url']);
179 $this->assertEquals(true, $content['is_archived']);
180 $this->assertEquals(false, $content['is_starred']);
181 }
182
165 public function testPatchEntry() 183 public function testPatchEntry()
166 { 184 {
167 $entry = $this->client->getContainer() 185 $entry = $this->client->getContainer()
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
new file mode 100644
index 00000000..b666f2df
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -0,0 +1,242 @@
1#Login
2Keep me logged in: 'Mantenme conectado'
3Forgot your password?: '¿Ha olvidado su contraseña?'
4Login: 'Conectarse'
5Back to login: 'Revenir au formulaire de connexion'
6Send: 'Envíar'
7"Enter your email address below and we'll send you password reset instructions.": "Introduce tu dirección de email y le enviaremos las instrucciones para resetear la contraseña"
8Register: 'Registrarse'
9
10# Menu
11unread: 'Sin leer'
12starred: 'Favoritos'
13archive: 'Archivo'
14all: 'Todos los artículos'
15tags: 'Tags'
16config: 'Configuración'
17internal settings: 'Configuración interna'
18import: 'Importar'
19howto: 'Ayuda'
20logout: 'Desconectarse'
21Filtered: 'Articulos filtrados'
22About: 'Acerca de'
23
24# Header
25Back to unread articles: 'Volver a los artículos sin leer'
26Add a new entry: 'Añadir un nuevo artículo'
27Search: 'Buscar'
28Filter entries: 'Filtrar los artículos'
29Enter your search here: 'Introduce tu búsqueda aquí'
30Save new entry: 'Guardar un nuevo artículo'
31Export: 'Exportar'
32
33# Config screen
34Settings: 'Configuración'
35User information: 'Información de usuario'
36Password: 'Contraseña'
37RSS: 'RSS'
38Add a user: 'Añadir un usuario'
39Theme: 'Tema'
40Items per page: "Número de artículos por página"
41Language: 'Idioma'
42Save: 'Enregistrer'
43RSS token: 'RSS token'
44RSS token updated: 'RSS token actualizado '
45Name: 'Nombre'
46Email: 'Direccion e-mail'
47No token: 'Aucun jeton généré'
48Reset your token: 'Resetear token'
49Create your token: 'Crear token'
50Rss limit: "Límite de artículos en feed RSS"
51RSS links: 'URL de su feed RSS'
52"RSS feeds provided by wallabag allow you to read your saved articles with your favourite RSS reader. You need to generate a token first.": "Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Necesita generar un token primero"
53Old password: 'Contraseña actual'
54New password: 'Nueva contraseña'
55Repeat new password: 'Confirmar la nueva contraseña'
56Username: "Nombre de usuario"
57Two factor authentication: "Autentificación de dos factores"
58"Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion": "Con la autentificación de dos factores recibirá código mediante email en cada nueva conexión que no sea de confianza"
59
60# Tagging rules
61Tagging rules: "Reglas de etiquetado automáticas"
62What does « tagging rules » mean?: "¿Qué significa reglas de etiquetado autómaticas?"
63"They are rules used by Wallabag to automatically tag new entries.<br />Each time a new entry is added, all the tagging rules will be used to add the tags you configured, thus saving you the trouble to manually classify your entries.": "Son las reglas usadas por Wallabag para etiquetar automáticamente los nuevos artículos.<br />Cáda vez que un nuevo artículo es añadido, todas las reglas de etiquetado automáticas serán usadas para etiquetarlo, ayudandote a clasificar automáticamente los artículos."
64How do I use them?: "¿Cómo se utilizan?"
65"Let assume you want to tag new entries as « <i>short reading</i> » when the reading time is inferior to 3 minutes.<br />In that case, you should put « readingTime &lt;= 3 » in the <i>Rule</i> field and « <i>short reading</i> » in the <i>Tags</i> field.<br />Several tags can added simultaneously by separating them by a comma: « <i>short reading, must read</i> »<br />Complex rules can be written by using predefined operators: if « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » then tag as « <i>long reading, github </i> »": "Imaginons que voulez attribuer aux nouveaux articles le tag « <i>lecture courte</i> » lorsque le temps de lecture est inférieur à 3 minutes.<br />Dans ce cas, vous devriez mettre « readingTime &lt;= 3 » dans le champ <i>Règle</i> et « <i>lecture courte</i> » dans le champ <i>Tag</i>.<br />Plusieurs tags peuvent être ajoutés simultanément en les séparant par des virgules : « <i>lecture courte, à lire</i> »<br />Des règles complexes peuvent être créées en utilisant des opérateurs prédéfinis: si « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » alors attribuer les tags « <i>lecteur longue, github </i> »"
66Which variables and operators can I use to write rules?: "¿Qué variables y operadores se pueden utilizar para escribir las reglas?"
67The following variables and operators can be used to create tagging rules:: "Las siguientes variables y operadores se pueden utilizar para crear las reglas de etiquetado automáticas:"
68Variable: "Variable"
69Meaning: "Significado"
70Operator: "Operador"
71Title of the entry: "Titúlo del artículo"
72Less than…: "Menos que…"
73URL of the entry: "URL del artículo"
74Strictly less than…: "Estrictámente menos que…"
75Whether the entry is archived or not: "El artículo está guardado o no"
76Greater than…: "Más que…"
77Whether the entry is starred or not: "Si el artículo es un favorito o no"
78Strictly greater than…: "Estrictámente mas que…"
79The entry's content: "El contenido del artículo"
80Equal to…: "Egual a…"
81The entry's language: "El idoma del artículo"
82Not equal to…: "Diferente de…"
83The entry's mime-type: "Tipo MIME del artículo"
84One rule or another: "Una regla o otra"
85The estimated entry's reading time, in minutes: "El tiempo estimado de lectura del artículo, en minutos"
86One rule and another: "Una regla y la otra"
87The domain name of the entry: "El dominio del artículo"
88"Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches \"football\"</code>": "Prueba si un <i>sujeto</i> corresponde a una <i>busqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>título coincide \"football\"</code>"
89Rule: "Regla"
90FAQ: "FAQ"
91
92# Entries
93"estimated reading time: %readingTime% min": "tiempo estimado de lectura: %readingTime% min"
94"estimated reading time: %inferior% 1 min": "tiempo estimado de lectura: %inferior% 1 min"
95original: "original"
96Toggle mark as read: 'Marcar cómo leído/ no leído'
97Toggle favorite: 'Marcar cómo favorito/ no favorito'
98Delete: 'Suprimir'
99"{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.": "{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos."
100http://website: "http://website"
101"{0} No annotations|{1} One annotation|]1,Inf[ %nbAnnotations% annotations": "{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %nbAnnotations% anotaciones"
102
103# Edit entry
104Edit an entry: "Editar una artículo"
105Title: "Título"
106Is public: "Es Público"
107
108# tag
109Tags: Tags
110"{0} There is no tag.|{1} There is one tag.|]1,Inf[ There are %count% tags.": "{0} No hay ningun tag.|{1} Hay un tag.|]1,Inf[ Hay %count% tags."
111
112# Filters
113Filters: 'Filtros'
114Status: 'Estatus'
115Archived: 'Archivado'
116Starred: 'Favorito'
117Preview picture: 'Foto de preview'
118Has a preview picture: 'Hay una foto'
119Reading time in minutes: 'Duración de lectura en minutos'
120from: 'de'
121to: 'a'
122website.com: 'website.com'
123Domain name: 'Nombre de dominio'
124Creation date: 'Fecha de creación'
125dd/mm/yyyy: 'dd/mm/aaaa'
126Clear: 'Limpiar'
127Filter: 'Filtrar'
128website.com: "website.com"
129
130# About
131About: "Acerca de"
132Who is behind wallabag: "Equipo de desarrollo de wallabag"
133Getting help: "Conseguir ayuda"
134Helping wallabag: "Ayudar a wallabag"
135Developed by: "Desarrollado por"
136website: "Sitio web"
137And many others contributors ♥: "Y muchos otros contribuidores ♥"
138on GitHub: "en GitHub"
139Project website: "Web del proyecto"
140License: "Licencia"
141Version: "Versión"
142Documentation: "Documentación"
143Bug reports: "Reporte de errores"
144On our support website: "En nuestra web de soporte"
145or: "o"
146"wallabag is free and opensource. You can help us:": "wallabag es libre y gratuito. Usted puede ayudarnos :"
147"by contributing to the project:": "contribuyendo al proyecto :"
148an issue lists all our needs: "nuestras necesidades están en un ticket"
149via Paypal: "via Paypal"
150Take wallabag with you: "Llevate wallabag contigo"
151Social: "Social"
152powered by: "propulsé par"
153Contributors: "Contribuidores"
154Thank you to contributors on wallabag web application: "Gradias a los contribuidores de la aplicación web de wallabag"
155Third-party libraries: "Librerías de terceeros"
156"Here are the list of third-party libraries used in wallabag (with their licenses):": "Aquí está la lista de las dependencias utilizadas por wallabag (con sus licencias):"
157Package: Paquete
158License: Licencia
159
160# Howto
161Form: "Formulario"
162Thanks to this form: "Gracias a este formulario"
163Browser addons: "Extensiones de navigador"
164Mobile apps: "Applicaciones para smartphone"
165Bookmarklet: "Bookmarklet"
166Standard Firefox Add-On: "Extensión Firefox"
167Chrome Extension: "Extensión Chrome"
168download the application: "descargar la aplicación"
169"Drag &amp; drop this link to your bookmarks bar:": "Desplazar y soltar este link en la barra de marcadores :"
170
171# Flash messages
172Information updated: "Su información personal ha sido actualizada"
173"Config saved. Some parameters will be considered after disconnection.": "Configuración guardada. Algunos parámetros serán recargados cuando se vuelva a conectar."
174RSS information updated: "La configuración de los feeds RSS ha sido actualizada"
175Password updated: "Contraseña actualizada"
176Entry starred: "Artículo guardado en los favoritos"
177Entry unstarred: "Artículo retirado de los favoritos"
178Entry archived: "Artículo archivado"
179Entry unarchived: "Artículo desarchivado"
180Entry deleted: "Artículo suprimido"
181Tagging rule deleted: "Regla de etiquetado borrada"
182Tagging rules updated: "Regla de etiquetado actualizada"
183User "%username%" added: 'Usuario "%username%" añadido'
184In demonstration mode, you can't change password for this user.: 'En modo demo, no puedes cambiar la contraseña del usuario.'
185
186# Entry
187Mark as read: 'Marcar como leído'
188Favorite: 'Marcar cómo favorito'
189back: 'Volver'
190original article: 'Artículo original'
191Add a tag: 'Añadir una etiqueta'
192Share: 'Compartir'
193Download: 'Descargar'
194Does this article appear wrong?: "¿Este artículo no se muestra bien?"
195Problems?: '¿Algún problema?'
196Edit title: "Modificar el título"
197Re-fetch content: "Redescargar el contenido"
198Tag added: "Etiqueta añadida"
199
200# Import
201Welcome to wallabag importer. Please select your previous service that you want to migrate.: "Bienvenido al útil de migración de wallabag. Seleccione el servicio previo del que usted quiera migrar."
202"This importer will import all your Pocket data. Pocket doesn't allow us to retrieve content from their service, so the readable content of each article will be re-fetched by wallabag.": "Va a importar sus datos de Pocket. Pocket no nos permite descargar el contenido de su servicio, así que el contenido de cada artículo será redescargado por wallabag."
203"This importer will import all your wallabag v1 articles. On your config page, click on \"JSON export\" in the \"Export your wallabag data\" section. You will have a \"wallabag-export-1-xxxx-xx-xx.json\" file.": "Va a importar sus artículos de wallabag v1. En su configuración de wallabag v1, pulse sobre \"Exportar JSON\" dentro de la sección \"Exportar sus datos de wallabag\". Usted tendrá un fichero \"wallabag-export-1-xxxx-xx-xx.json\"."
204"This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on \"JSON\". You will have a \"All articles.json\" file.": "Va a importar sus artículos de otra instancia de wallabag v2. Vaya a Todos los artículos, entonces, en la barra lateral, clickee en \"JSON\". Usted tendrá un fichero \"All articles.json\""
205"You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.": "Puedes importar sus datos desde su cuenta de Pocket. Sólo tienes que pulsar en el botón para autrizar que wallabag se conecte a getpocket.com."
206Import > Pocket: "Importar > Pocket"
207Pocket import isn't configured.: "La importación de Pocket no está configurada."
208You need to define %keyurls% a pocket_consumer_key %keyurle%.: "Debe definir %keyurls% una clava del API Pocket %keyurle%."
209Your server admin needs to define an API Key for Pocket.: "El administrador de vuestro servidor debe definir una clave API Pocket."
210Connect to Pocket and import data: "Conectese a Pocket para importar los datos"
211Please select your wallabag export and click on the below button to upload and import it.: "Seleccione el fichero de su exportación de wallabag v1 y puelse en el botón para subirla y importarla."
212File: "Fichero"
213Upload file: "Importar el fichero"
214Import contents: "Importar los contenidos"
215Import: "Importar"
216Import > Wallabag v1: "Importar > Wallabag v1"
217Import > Wallabag v2: "Importar > Wallabag v2"
218
219# Quickstart
220Quickstart: Comienzo rápido
221Welcome to wallabag!: "Bienvenido a wallabag !"
222We'll accompany you to visit wallabag and show you some features which can interess you.: "Le acompañaremos a su visita de wallabag y le mostraremos algunas características que le pueden interesar."
223Follow us!: "¡Siganos!"
224Configure the application: "Configure la aplicación"
225Change language and design: "Cambie el idoma y el diseño de la aplicación"
226Enable RSS feeds: "Activar los feeds RSS"
227First steps: "Prieros pasos"
228Save your first article: "Guarde su primer artículo"
229And classify it!: "¡Y clasifiquelo!"
230Migrate from an existing service: "Migrar de un servicio existente"
231You're using an other service? We'll help you to retrieve your data on wallabag.: "¿Está usando otro servicio? Le ayudaremos a migrar sus datos a wallabag."
232Migrate from Pocket: "Migrar desde Pocket"
233Migrate from wallabag v1: "Migrar desde wallabag v1"
234Full documentation: "Documentación completa"
235Convert your articles into ePUB or PDF: "Convierta sus artículos a ePub o a PDF"
236See how you can look for an article by using search engine and filters: "Aprenda a utilizar el buscador y los filtros para encontrar el artículo que le interese"
237And so many other articles!: "¡Y muchos más artículos!"
238Support: "Soporte"
239If you need some help, we are here for you.: "Sí necesita ayuda, estamos disponibles para usted."
240On GitHub: "En GitHub"
241By email: "Por email"
242On Gitter: "On Gitter"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 1de8d20f..b809f1ab 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -54,13 +54,13 @@ Old password: 'Mot de passe actuel'
54New password: 'Nouveau mot de passe' 54New password: 'Nouveau mot de passe'
55Repeat new password: 'Confirmez votre nouveau mot de passe' 55Repeat new password: 'Confirmez votre nouveau mot de passe'
56Username: "Nom d'utilisateur" 56Username: "Nom d'utilisateur"
57Two factor authentication: Double authentification 57Two factor authentication: "Double authentification"
58Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion: Activer l'authentification double-facteur veut dire que vous allez recevoir un code par email à chaque nouvelle connexion non approuvée. 58"Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion": "Activer l'authentification double-facteur veut dire que vous allez recevoir un code par email à chaque nouvelle connexion non approuvée."
59 59
60# Tagging rules 60# Tagging rules
61Tagging rules: "Règles de tag automatiques" 61Tagging rules: "Règles de tag automatiques"
62What does « tagging rules » mean?: "Que signifient les règles de tag automatiques ?" 62What does « tagging rules » mean?: "Que signifient les règles de tag automatiques ?"
63"They are rules used by Wallabag to automatically tag new entries.<br />Each time a new entry is added, all the tagging rules will be used to add the tags you configured, thus saving you the trouble to manually classify your entries.": "Ce sont des règles utilisées par wallabag pour classer automatiquement vos nouveaux articles.<br />À chaque fois qu'un nouvel article est ajouté, toutes les règles de tag automatiques seront utilisées afin d'ajouter les tags que vous avez configuré, vous épargnant ainsi l'effort de classifier vos articles manuellement." 63"They are rules used by Wallabag to automatically tag new entries.<br />Each time a new entry is added, all the tagging rules will be used to add the tags you configured, thus saving you the trouble to manually classify your entries.": "Ce sont des règles utilisées par wallabag pour classer automatiquement vos nouveaux articles.<br />À chaque fois qu'un nouvel article est ajouté, toutes les règles de tag automatiques seront utilisées afin d'ajouter les tags que vous avez configurés, vous épargnant ainsi l'effort de classifier vos articles manuellement."
64How do I use them?: "Comment les utiliser ?" 64How do I use them?: "Comment les utiliser ?"
65"Let assume you want to tag new entries as « <i>short reading</i> » when the reading time is inferior to 3 minutes.<br />In that case, you should put « readingTime &lt;= 3 » in the <i>Rule</i> field and « <i>short reading</i> » in the <i>Tags</i> field.<br />Several tags can added simultaneously by separating them by a comma: « <i>short reading, must read</i> »<br />Complex rules can be written by using predefined operators: if « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » then tag as « <i>long reading, github </i> »": "Imaginons que voulez attribuer aux nouveaux articles le tag « <i>lecture courte</i> » lorsque le temps de lecture est inférieur à 3 minutes.<br />Dans ce cas, vous devriez mettre « readingTime &lt;= 3 » dans le champ <i>Règle</i> et « <i>lecture courte</i> » dans le champ <i>Tag</i>.<br />Plusieurs tags peuvent être ajoutés simultanément en les séparant par des virgules : « <i>lecture courte, à lire</i> »<br />Des règles complexes peuvent être créées en utilisant des opérateurs prédéfinis: si « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » alors attribuer les tags « <i>lecteur longue, github </i> »" 65"Let assume you want to tag new entries as « <i>short reading</i> » when the reading time is inferior to 3 minutes.<br />In that case, you should put « readingTime &lt;= 3 » in the <i>Rule</i> field and « <i>short reading</i> » in the <i>Tags</i> field.<br />Several tags can added simultaneously by separating them by a comma: « <i>short reading, must read</i> »<br />Complex rules can be written by using predefined operators: if « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » then tag as « <i>long reading, github </i> »": "Imaginons que voulez attribuer aux nouveaux articles le tag « <i>lecture courte</i> » lorsque le temps de lecture est inférieur à 3 minutes.<br />Dans ce cas, vous devriez mettre « readingTime &lt;= 3 » dans le champ <i>Règle</i> et « <i>lecture courte</i> » dans le champ <i>Tag</i>.<br />Plusieurs tags peuvent être ajoutés simultanément en les séparant par des virgules : « <i>lecture courte, à lire</i> »<br />Des règles complexes peuvent être créées en utilisant des opérateurs prédéfinis: si « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » alors attribuer les tags « <i>lecteur longue, github </i> »"
66Which variables and operators can I use to write rules?: "Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?" 66Which variables and operators can I use to write rules?: "Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?"
@@ -194,14 +194,14 @@ Download: 'Télécharger'
194Does this article appear wrong?: "Est-ce que cet article s'affiche mal ?" 194Does this article appear wrong?: "Est-ce que cet article s'affiche mal ?"
195Problems?: 'Un problème ?' 195Problems?: 'Un problème ?'
196Edit title: "Modifier le titre" 196Edit title: "Modifier le titre"
197Re-fetch content: Recharger le contenu 197Re-fetch content: "Recharger le contenu"
198Tag added: Tag ajouté 198Tag added: "Tag ajouté"
199 199
200# Import 200# Import
201Welcome to wallabag importer. Please select your previous service that you want to migrate.: "Bienvenue dans l'outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer." 201Welcome to wallabag importer. Please select your previous service that you want to migrate.: "Bienvenue dans l'outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer."
202"This importer will import all your Pocket data. Pocket doesn't allow us to retrieve content from their service, so the readable content of each article will be re-fetched by wallabag.": "Cet outil va importer toutes vos données de Pocket. Pocket ne nous autorise pas à récupérer le contenu depuis leur service, donc wallabag doit reparcourir chaque article pour récupérer son contenu." 202"This importer will import all your Pocket data. Pocket doesn't allow us to retrieve content from their service, so the readable content of each article will be re-fetched by wallabag.": "Cet outil va importer toutes vos données de Pocket. Pocket ne nous autorise pas à récupérer le contenu depuis leur service, donc wallabag doit reparcourir chaque article pour récupérer son contenu."
203"This importer will import all your wallabag v1 articles. On your config page, click on \"JSON export\" in the \"Export your wallabag data\" section. You will have a \"wallabag-export-1-xxxx-xx-xx.json\" file.": "Cet outil va importer toutes vos données de wallabag v1. Sur votre page de configuration de wallabag v1, cliquez sur \"Export JSON\" dans la section \"Exporter vos données de wallabag\". Vous allez récupérer un fichier \"wallabag-export-1-xxxx-xx-xx.json\"." 203"This importer will import all your wallabag v1 articles. On your config page, click on \"JSON export\" in the \"Export your wallabag data\" section. You will have a \"wallabag-export-1-xxxx-xx-xx.json\" file.": "Cet outil va importer toutes vos données de wallabag v1. Sur votre page de configuration de wallabag v1, cliquez sur \"Export JSON\" dans la section \"Exporter vos données de wallabag\". Vous allez récupérer un fichier \"wallabag-export-1-xxxx-xx-xx.json\"."
204"This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on \"JSON\". You will have a \"All articles.json\" file.": "Cet outil va importer toutes vos articles d'une autre instance de wallabag v2. Allez dans tous vos articles, puis, sur la barre latérale, cliquez sur \"JSON\". Vous allez récupérer un fichier \"All articles.json\"" 204"This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on \"JSON\". You will have a \"All articles.json\" file.": "Cet outil va importer tous vos articles d'une autre instance de wallabag v2. Allez dans tous vos articles, puis, sur la barre latérale, cliquez sur \"JSON\". Vous allez récupérer un fichier \"All articles.json\""
205"You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.": "Vous pouvez importer vos données depuis votre compte Pocket. Vous n'avez qu'à cliquer sur le bouton ci-dessous et à autoriser wallabag à se connecter à getpocket.com." 205"You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.": "Vous pouvez importer vos données depuis votre compte Pocket. Vous n'avez qu'à cliquer sur le bouton ci-dessous et à autoriser wallabag à se connecter à getpocket.com."
206Import > Pocket: "Import > Pocket" 206Import > Pocket: "Import > Pocket"
207Pocket import isn't configured.: "L'import à partir de Pocket n'est pas configuré." 207Pocket import isn't configured.: "L'import à partir de Pocket n'est pas configuré."
@@ -215,6 +215,8 @@ Import contents: "Importer les contenus"
215Import: "Importer" 215Import: "Importer"
216Import > Wallabag v1: "Importer > Wallabag v1" 216Import > Wallabag v1: "Importer > Wallabag v1"
217Import > Wallabag v2: "Importer > Wallabag v2" 217Import > Wallabag v2: "Importer > Wallabag v2"
218Mark all as read ?: "Marquer tout comme lu ?"
219Mark all imported entries as read: "Marquer tous les contenus importés comme lus"
218 220
219# Quickstart 221# Quickstart
220Quickstart: Pour bien débuter 222Quickstart: Pour bien débuter
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
new file mode 100644
index 00000000..5c0b6df3
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
@@ -0,0 +1,3 @@
1# Config screen
2The password fields must match: 'Las contraseñas no coinciden'
3Password should by at least 8 chars long: 'La contraseña debe tener al menos 8 carácteres'
diff --git a/src/Wallabag/CoreBundle/Resources/views/base.html.twig b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
index 2748c7e3..4c0d4fee 100644
--- a/src/Wallabag/CoreBundle/Resources/views/base.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
@@ -71,7 +71,7 @@
71 {% block footer %}{% endblock %} 71 {% block footer %}{% endblock %}
72 72
73 <div id="warning_message"> 73 <div id="warning_message">
74 You're trying wallabag v2, which is in alpha version. If you find a bug, please have a look to <a href="https://github.com/wallabag/wallabag/issues">our issues list</a> and <a href="https://github.com/wallabag/wallabag/issues/new">open a new if necessary</a> 74 You're trying wallabag v2, which is in beta version. If you find a bug, please have a look to <a href="https://github.com/wallabag/wallabag/issues">our issues list</a> and <a href="https://github.com/wallabag/wallabag/issues/new">open a new one if necessary</a>.
75 </div> 75 </div>
76 76
77 {% if craue_setting('piwik_enabled') %} 77 {% if craue_setting('piwik_enabled') %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Static/quickstart.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Static/quickstart.html.twig
index 3984081c..319eaea3 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Static/quickstart.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Static/quickstart.html.twig
@@ -4,32 +4,47 @@
4 4
5{% block content %} 5{% block content %}
6 6
7 <h2>{% trans %}Welcome to wallabag!{% endtrans %}</h2> 7 <h3>{% trans %}Welcome to wallabag!{% endtrans %}</h3>
8 <p>{% trans %}We'll accompany you to visit wallabag and show you some features which can interess you.{% endtrans %}</p> 8 <p>{% trans %}We'll accompany you to visit wallabag and show you some features which can interess you.{% endtrans %}</p>
9 <p>{% trans %}Follow us!{% endtrans %}</p> 9 <p>{% trans %}Follow us!{% endtrans %}</p>
10 <h4>{% trans %}Configure the application{% endtrans %}</h4> 10 <h4>{% trans %}Configure the application{% endtrans %}</h4>
11 <ul> 11 <ul>
12 <li><a href="{{ path('config') }}">{% trans %}Change language and design{% endtrans %}</a></li> 12 <li><a href="{{ path('config') }}">{% trans %}Change language and design{% endtrans %}</a></li>
13 <li><a href="{{ path('config') }}#set2">{% trans %}Enable RSS feeds{% endtrans %}</a></li> 13 <li><a href="{{ path('config') }}#set2">{% trans %}Enable RSS feeds{% endtrans %}</a></li>
14 <li><a href="{{ path('config') }}#set5">{% trans %}Write rules to automatically tag your articles{% endtrans %}</a></li>
14 </ul> 15 </ul>
15 <h3>{% trans %}First steps{% endtrans %}</h3> 16 {% if is_granted('ROLE_SUPER_ADMIN') %}
17 <h4>{% trans %}Administration{% endtrans %}</h4>
18 <p>{% trans %}As a administrator, you have privileges on wallabag. You can:{% endtrans %}</p>
19 <ul>
20 <li><a href="{{ path('config') }}#set6">{% trans %}Create a new user{% endtrans %}</a></li>
21 <li><a href="{{ path('craue_config_settings_modify') }}#set-analytics">{% trans %}Configure analytics{% endtrans %}</a></li>
22 <li><a href="{{ path('craue_config_settings_modify') }}#set-entry">{% trans %}Enable some parameters about article sharing{% endtrans %}</a></li>
23 <li><a href="{{ path('craue_config_settings_modify') }}#set-export">{% trans %}Configure export{% endtrans %}</a></li>
24 <li><a href="{{ path('craue_config_settings_modify') }}#set-import">{% trans %}Configure import{% endtrans %}</a></li>
25 </ul>
26 {% endif %}
27 <h4>{% trans %}First steps{% endtrans %}</h4>
16 <ul> 28 <ul>
17 <li><a href="{{ path('new') }}">{% trans %}Save your first article{% endtrans %}</a></li> 29 <li><a href="{{ path('new') }}">{% trans %}Save your first article{% endtrans %}</a></li>
18 <li><a href="{{ path('unread') }}">{% trans %}And classify it!{% endtrans %}</a></li> 30 <li><a href="{{ path('unread') }}">{% trans %}And classify it!{% endtrans %}</a></li>
19 </ul> 31 </ul>
20 <h3>{% trans %}Migrate from an existing service{% endtrans %}</h3> 32 <h4>{% trans %}Migrate from an existing service{% endtrans %}</h4>
21 <p>{% trans %}You're using an other service? We'll help you to retrieve your data on wallabag.{% endtrans %}</p> 33 <p>{% trans %}You're using an other service? We'll help you to retrieve your data on wallabag.{% endtrans %}</p>
22 <ul> 34 <ul>
23 <li><a href="{{ path('import_pocket') }}">{% trans %}Migrate from Pocket{% endtrans %}</a></li> 35 <li><a href="{{ path('import_pocket') }}">{% trans %}Migrate from Pocket{% endtrans %}</a></li>
24 <li><a href="{{ path('import_wallabag_v1') }}">{% trans %}Migrate from wallabag v1{% endtrans %}</a></li> 36 <li><a href="{{ path('import_wallabag_v1') }}">{% trans %}Migrate from wallabag v1{% endtrans %}</a></li>
37 <li><a href="{{ path('import_wallabag_v2') }}">{% trans %}Migrate from wallabag v2{% endtrans %}</a></li>
25 </ul> 38 </ul>
26 <h3>{% trans %}Full documentation{% endtrans %}</h3> 39 <h4>{% trans %}Full documentation{% endtrans %}</h4>
27 <ul> 40 <ul>
41 <li><a href="http://wallabag.readthedocs.org">{% trans %}Annotate your article{% endtrans %}</a></li>
28 <li><a href="http://wallabag.readthedocs.org">{% trans %}Convert your articles into ePUB or PDF{% endtrans %}</a></li> 42 <li><a href="http://wallabag.readthedocs.org">{% trans %}Convert your articles into ePUB or PDF{% endtrans %}</a></li>
29 <li><a href="http://wallabag.readthedocs.org">{% trans %}See how you can look for an article by using search engine and filters{% endtrans %}</a></li> 43 <li><a href="http://wallabag.readthedocs.org">{% trans %}See how you can look for an article by using search engine and filters{% endtrans %}</a></li>
44 <li><a href="http://wallabag.readthedocs.org">{% trans %}What can I do if an article encounters errors during fetching?{% endtrans %}</a></li>
30 <li><a href="http://wallabag.readthedocs.org">{% trans %}And so many other articles!{% endtrans %}</a></li> 45 <li><a href="http://wallabag.readthedocs.org">{% trans %}And so many other articles!{% endtrans %}</a></li>
31 </ul> 46 </ul>
32 <h3>{% trans %}Support{% endtrans %}</h3> 47 <h4>{% trans %}Support{% endtrans %}</h4>
33 <p>{% trans %}If you need some help, we are here for you.{% endtrans %}</p> 48 <p>{% trans %}If you need some help, we are here for you.{% endtrans %}</p>
34 <ul> 49 <ul>
35 <li><a href="https://github.com/wallabag/wallabag/issues/">{% trans %}On GitHub{% endtrans %}</a></li> 50 <li><a href="https://github.com/wallabag/wallabag/issues/">{% trans %}On GitHub{% endtrans %}</a></li>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Static/quickstart.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Static/quickstart.html.twig
index 6c786951..dea4242b 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Static/quickstart.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Static/quickstart.html.twig
@@ -16,7 +16,19 @@
16 <ul> 16 <ul>
17 <li><a href="{{ path('config') }}">{% trans %}Change language and design{% endtrans %}</a></li> 17 <li><a href="{{ path('config') }}">{% trans %}Change language and design{% endtrans %}</a></li>
18 <li><a href="{{ path('config') }}#set2">{% trans %}Enable RSS feeds{% endtrans %}</a></li> 18 <li><a href="{{ path('config') }}#set2">{% trans %}Enable RSS feeds{% endtrans %}</a></li>
19 <li><a href="{{ path('config') }}#set5">{% trans %}Write rules to automatically tag your articles{% endtrans %}</a></li>
19 </ul> 20 </ul>
21 {% if is_granted('ROLE_SUPER_ADMIN') %}
22 <h4>{% trans %}Administration{% endtrans %}</h4>
23 <p>{% trans %}As a administrator, you have privileges on wallabag. You can:{% endtrans %}</p>
24 <ul>
25 <li><a href="{{ path('config') }}#set6">{% trans %}Create a new user{% endtrans %}</a></li>
26 <li><a href="{{ path('craue_config_settings_modify') }}#set-analytics">{% trans %}Configure analytics{% endtrans %}</a></li>
27 <li><a href="{{ path('craue_config_settings_modify') }}#set-entry">{% trans %}Enable some parameters about article sharing{% endtrans %}</a></li>
28 <li><a href="{{ path('craue_config_settings_modify') }}#set-export">{% trans %}Configure export{% endtrans %}</a></li>
29 <li><a href="{{ path('craue_config_settings_modify') }}#set-import">{% trans %}Configure import{% endtrans %}</a></li>
30 </ul>
31 {% endif %}
20 <h4>{% trans %}First steps{% endtrans %}</h4> 32 <h4>{% trans %}First steps{% endtrans %}</h4>
21 <ul> 33 <ul>
22 <li><a href="{{ path('new') }}">{% trans %}Save your first article{% endtrans %}</a></li> 34 <li><a href="{{ path('new') }}">{% trans %}Save your first article{% endtrans %}</a></li>
@@ -27,6 +39,7 @@
27 <ul> 39 <ul>
28 <li><a href="{{ path('import_pocket') }}">{% trans %}Migrate from Pocket{% endtrans %}</a></li> 40 <li><a href="{{ path('import_pocket') }}">{% trans %}Migrate from Pocket{% endtrans %}</a></li>
29 <li><a href="{{ path('import_wallabag_v1') }}">{% trans %}Migrate from wallabag v1{% endtrans %}</a></li> 41 <li><a href="{{ path('import_wallabag_v1') }}">{% trans %}Migrate from wallabag v1{% endtrans %}</a></li>
42 <li><a href="{{ path('import_wallabag_v2') }}">{% trans %}Migrate from wallabag v2{% endtrans %}</a></li>
30 </ul> 43 </ul>
31 <h4>{% trans %}Developers{% endtrans %}</h4> 44 <h4>{% trans %}Developers{% endtrans %}</h4>
32 <ul> 45 <ul>
@@ -34,8 +47,10 @@
34 </ul> 47 </ul>
35 <h4>{% trans %}Full documentation{% endtrans %}</h4> 48 <h4>{% trans %}Full documentation{% endtrans %}</h4>
36 <ul> 49 <ul>
50 <li><a href="http://wallabag.readthedocs.org">{% trans %}Annotate your article{% endtrans %}</a></li>
37 <li><a href="http://wallabag.readthedocs.org">{% trans %}Convert your articles into ePUB or PDF{% endtrans %}</a></li> 51 <li><a href="http://wallabag.readthedocs.org">{% trans %}Convert your articles into ePUB or PDF{% endtrans %}</a></li>
38 <li><a href="http://wallabag.readthedocs.org">{% trans %}See how you can look for an article by using search engine and filters{% endtrans %}</a></li> 52 <li><a href="http://wallabag.readthedocs.org">{% trans %}See how you can look for an article by using search engine and filters{% endtrans %}</a></li>
53 <li><a href="http://wallabag.readthedocs.org">{% trans %}What can I do if an article encounters errors during fetching?{% endtrans %}</a></li>
39 <li><a href="http://wallabag.readthedocs.org">{% trans %}And so many other articles!{% endtrans %}</a></li> 54 <li><a href="http://wallabag.readthedocs.org">{% trans %}And so many other articles!{% endtrans %}</a></li>
40 </ul> 55 </ul>
41 <h4>{% trans %}Support{% endtrans %}</h4> 56 <h4>{% trans %}Support{% endtrans %}</h4>
diff --git a/src/Wallabag/ImportBundle/Controller/PocketController.php b/src/Wallabag/ImportBundle/Controller/PocketController.php
index 1c1b4fa8..c88e115e 100644
--- a/src/Wallabag/ImportBundle/Controller/PocketController.php
+++ b/src/Wallabag/ImportBundle/Controller/PocketController.php
@@ -5,6 +5,8 @@ namespace Wallabag\ImportBundle\Controller;
5use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
6use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 6use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 7use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
8 10
9class PocketController extends Controller 11class PocketController extends Controller
10{ 12{
@@ -13,21 +15,31 @@ class PocketController extends Controller
13 */ 15 */
14 public function indexAction() 16 public function indexAction()
15 { 17 {
18 $pocket = $this->get('wallabag_import.pocket.import');
19 $form = $this->createFormBuilder($pocket)
20 ->add('read', CheckboxType::class, array(
21 'label' => 'Mark all as read',
22 'required' => false,
23 ))
24 ->getForm();
25
16 return $this->render('WallabagImportBundle:Pocket:index.html.twig', [ 26 return $this->render('WallabagImportBundle:Pocket:index.html.twig', [
17 'import' => $this->get('wallabag_import.pocket.import'), 27 'import' => $this->get('wallabag_import.pocket.import'),
18 'has_consumer_key' => '' == trim($this->get('craue_config')->get('pocket_consumer_key')) ? false : true, 28 'has_consumer_key' => '' == trim($this->get('craue_config')->get('pocket_consumer_key')) ? false : true,
29 'form' => $form->createView(),
19 ]); 30 ]);
20 } 31 }
21 32
22 /** 33 /**
23 * @Route("/pocket/auth", name="import_pocket_auth") 34 * @Route("/pocket/auth", name="import_pocket_auth")
24 */ 35 */
25 public function authAction() 36 public function authAction(Request $request)
26 { 37 {
27 $requestToken = $this->get('wallabag_import.pocket.import') 38 $requestToken = $this->get('wallabag_import.pocket.import')
28 ->getRequestToken($this->generateUrl('import', array(), UrlGeneratorInterface::ABSOLUTE_URL)); 39 ->getRequestToken($this->generateUrl('import', array(), UrlGeneratorInterface::ABSOLUTE_URL));
29 40
30 $this->get('session')->set('import.pocket.code', $requestToken); 41 $this->get('session')->set('import.pocket.code', $requestToken);
42 $this->get('session')->set('read', $request->request->get('form')['read']);
31 43
32 return $this->redirect( 44 return $this->redirect(
33 'https://getpocket.com/auth/authorize?request_token='.$requestToken.'&redirect_uri='.$this->generateUrl('import_pocket_callback', array(), UrlGeneratorInterface::ABSOLUTE_URL), 45 'https://getpocket.com/auth/authorize?request_token='.$requestToken.'&redirect_uri='.$this->generateUrl('import_pocket_callback', array(), UrlGeneratorInterface::ABSOLUTE_URL),
@@ -42,6 +54,8 @@ class PocketController extends Controller
42 { 54 {
43 $message = 'Import failed, please try again.'; 55 $message = 'Import failed, please try again.';
44 $pocket = $this->get('wallabag_import.pocket.import'); 56 $pocket = $this->get('wallabag_import.pocket.import');
57 $markAsRead = $this->get('session')->get('read');
58 $this->get('session')->remove('read');
45 59
46 // something bad happend on pocket side 60 // something bad happend on pocket side
47 if (false === $pocket->authorize($this->get('session')->get('import.pocket.code'))) { 61 if (false === $pocket->authorize($this->get('session')->get('import.pocket.code'))) {
@@ -53,7 +67,7 @@ class PocketController extends Controller
53 return $this->redirect($this->generateUrl('import_pocket')); 67 return $this->redirect($this->generateUrl('import_pocket'));
54 } 68 }
55 69
56 if (true === $pocket->import()) { 70 if (true === $pocket->setMarkAsRead($markAsRead)->import()) {
57 $summary = $pocket->getSummary(); 71 $summary = $pocket->getSummary();
58 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.'; 72 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
59 } 73 }
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
index 35fe620f..154a0769 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
@@ -21,15 +21,18 @@ class WallabagV1Controller extends Controller
21 21
22 if ($form->isValid()) { 22 if ($form->isValid()) {
23 $file = $form->get('file')->getData(); 23 $file = $form->get('file')->getData();
24 $markAsRead = $form->get('mark_as_read')->getData();
24 $name = $this->getUser()->getId().'.json'; 25 $name = $this->getUser()->getId().'.json';
25 26
26 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 27 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
27 $res = $wallabag 28 $res = $wallabag
28 ->setUser($this->getUser()) 29 ->setUser($this->getUser())
29 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name) 30 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
31 ->setMarkAsRead($markAsRead)
30 ->import(); 32 ->import();
31 33
32 $message = 'Import failed, please try again.'; 34 $message = 'Import failed, please try again.';
35
33 if (true === $res) { 36 if (true === $res) {
34 $summary = $wallabag->getSummary(); 37 $summary = $wallabag->getSummary();
35 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.'; 38 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
index 2e6225f2..6dcd204a 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
@@ -21,12 +21,14 @@ class WallabagV2Controller extends Controller
21 21
22 if ($form->isValid()) { 22 if ($form->isValid()) {
23 $file = $form->get('file')->getData(); 23 $file = $form->get('file')->getData();
24 $markAsRead = $form->get('mark_as_read')->getData();
24 $name = $this->getUser()->getId().'.json'; 25 $name = $this->getUser()->getId().'.json';
25 26
26 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 27 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
27 $res = $wallabag 28 $res = $wallabag
28 ->setUser($this->getUser()) 29 ->setUser($this->getUser())
29 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name) 30 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
31 ->setMarkAsRead($markAsRead)
30 ->import(); 32 ->import();
31 33
32 $message = 'Import failed, please try again.'; 34 $message = 'Import failed, please try again.';
diff --git a/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php b/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
index 2e6b59cb..38670379 100644
--- a/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
+++ b/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
@@ -6,6 +6,7 @@ use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface; 6use Symfony\Component\Form\FormBuilderInterface;
7use Symfony\Component\Form\Extension\Core\Type\SubmitType; 7use Symfony\Component\Form\Extension\Core\Type\SubmitType;
8use Symfony\Component\Form\Extension\Core\Type\FileType; 8use Symfony\Component\Form\Extension\Core\Type\FileType;
9use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
9 10
10class UploadImportType extends AbstractType 11class UploadImportType extends AbstractType
11{ 12{
@@ -13,6 +14,10 @@ class UploadImportType extends AbstractType
13 { 14 {
14 $builder 15 $builder
15 ->add('file', FileType::class) 16 ->add('file', FileType::class)
17 ->add('mark_as_read', CheckboxType::class, array(
18 'label' => 'Mark all as read',
19 'required' => false,
20 ))
16 ->add('save', SubmitType::class) 21 ->add('save', SubmitType::class)
17 ; 22 ;
18 } 23 }
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index 5dfd098c..238ddbd1 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -22,6 +22,7 @@ class PocketImport implements ImportInterface
22 private $consumerKey; 22 private $consumerKey;
23 private $skippedEntries = 0; 23 private $skippedEntries = 0;
24 private $importedEntries = 0; 24 private $importedEntries = 0;
25 private $markAsRead;
25 protected $accessToken; 26 protected $accessToken;
26 27
27 public function __construct(TokenStorageInterface $tokenStorage, EntityManager $em, ContentProxy $contentProxy, Config $craueConfig) 28 public function __construct(TokenStorageInterface $tokenStorage, EntityManager $em, ContentProxy $contentProxy, Config $craueConfig)
@@ -124,6 +125,26 @@ class PocketImport implements ImportInterface
124 } 125 }
125 126
126 /** 127 /**
128 * Set whether articles must be all marked as read.
129 *
130 * @param bool $markAsRead
131 */
132 public function setMarkAsRead($markAsRead)
133 {
134 $this->markAsRead = $markAsRead;
135
136 return $this;
137 }
138
139 /**
140 * Get whether articles must be all marked as read.
141 */
142 public function getRead()
143 {
144 return $this->markAsRead;
145 }
146
147 /**
127 * {@inheritdoc} 148 * {@inheritdoc}
128 */ 149 */
129 public function import() 150 public function import()
@@ -201,7 +222,7 @@ class PocketImport implements ImportInterface
201 $entry = $this->contentProxy->updateEntry($entry, $url); 222 $entry = $this->contentProxy->updateEntry($entry, $url);
202 223
203 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 224 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
204 if ($pocketEntry['status'] == 1) { 225 if ($pocketEntry['status'] == 1 || $this->markAsRead) {
205 $entry->setArchived(true); 226 $entry->setArchived(true);
206 } 227 }
207 228
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
index 05bdb401..1d773d3b 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -19,6 +19,7 @@ class WallabagV1Import implements ImportInterface
19 protected $skippedEntries = 0; 19 protected $skippedEntries = 0;
20 protected $importedEntries = 0; 20 protected $importedEntries = 0;
21 protected $filepath; 21 protected $filepath;
22 protected $markAsRead;
22 23
23 public function __construct(EntityManager $em, ContentProxy $contentProxy) 24 public function __construct(EntityManager $em, ContentProxy $contentProxy)
24 { 25 {
@@ -121,6 +122,18 @@ class WallabagV1Import implements ImportInterface
121 } 122 }
122 123
123 /** 124 /**
125 * Set whether articles must be all marked as read.
126 *
127 * @param bool $markAsRead
128 */
129 public function setMarkAsRead($markAsRead)
130 {
131 $this->markAsRead = $markAsRead;
132
133 return $this;
134 }
135
136 /**
124 * @param $entries 137 * @param $entries
125 */ 138 */
126 protected function parseEntries($entries) 139 protected function parseEntries($entries)
@@ -160,7 +173,7 @@ class WallabagV1Import implements ImportInterface
160 ); 173 );
161 } 174 }
162 175
163 $entry->setArchived($importedEntry['is_read']); 176 $entry->setArchived($importedEntry['is_read'] || $this->markAsRead);
164 $entry->setStarred($importedEntry['is_fav']); 177 $entry->setStarred($importedEntry['is_fav']);
165 178
166 $this->em->persist($entry); 179 $this->em->persist($entry);
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
index 7125eabc..c4bac561 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
@@ -51,7 +51,7 @@ class WallabagV2Import extends WallabagV1Import implements ImportInterface
51 $entry = new Entry($this->user); 51 $entry = new Entry($this->user);
52 $entry->setUrl($importedEntry['url']); 52 $entry->setUrl($importedEntry['url']);
53 $entry->setTitle($importedEntry['title']); 53 $entry->setTitle($importedEntry['title']);
54 $entry->setArchived($importedEntry['is_archived']); 54 $entry->setArchived($importedEntry['is_archived'] || $this->markAsRead);
55 $entry->setStarred($importedEntry['is_starred']); 55 $entry->setStarred($importedEntry['is_starred']);
56 $entry->setContent($importedEntry['content']); 56 $entry->setContent($importedEntry['content']);
57 $entry->setReadingTime($importedEntry['reading_time']); 57 $entry->setReadingTime($importedEntry['reading_time']);
diff --git a/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
index 8aa5da97..3365fc6a 100644
--- a/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
@@ -19,6 +19,13 @@
19 <blockquote>{{ import.description|trans }}</blockquote> 19 <blockquote>{{ import.description|trans }}</blockquote>
20 <p>{% trans %}You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.{% endtrans %}</p> 20 <p>{% trans %}You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.{% endtrans %}</p>
21 <form method="post" action="{{ path('import_pocket_auth') }}"> 21 <form method="post" action="{{ path('import_pocket_auth') }}">
22 <div class="row">
23 <div class="input-field col s6 with-checkbox">
24 <h6>{% trans %}Mark all as read ?{% endtrans %}</h6>
25 {{ form_widget(form.read) }}
26 <label for="form_read">{% trans %}Mark all imported entries as read{% endtrans %}</label>
27 </div>
28 </div>
22 <button class="btn waves-effect waves-light" type="submit" name="action"> 29 <button class="btn waves-effect waves-light" type="submit" name="action">
23 {% trans %}Connect to Pocket and import data{% endtrans %} 30 {% trans %}Connect to Pocket and import data{% endtrans %}
24 </button> 31 </button>
diff --git a/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
index 1359f2e4..a418ed1c 100644
--- a/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
@@ -22,6 +22,11 @@
22 <input class="file-path validate" type="text"> 22 <input class="file-path validate" type="text">
23 </div> 23 </div>
24 </div> 24 </div>
25 <div class="input-field col s6 with-checkbox">
26 <h6>{% trans %}Mark all as read ?{% endtrans %}</h6>
27 {{ form_widget(form.mark_as_read) }}
28 <label for="upload_import_file_mark_as_read">{% trans %}Mark all imported entries as read{% endtrans %}</label>
29 </div>
25 </div> 30 </div>
26 <div class="hidden">{{ form_rest(form) }}</div> 31 <div class="hidden">{{ form_rest(form) }}</div>
27 <button class="btn waves-effect waves-light" type="submit" name="action"> 32 <button class="btn waves-effect waves-light" type="submit" name="action">
diff --git a/src/Wallabag/ImportBundle/Tests/Controller/WallabagV1ControllerTest.php b/src/Wallabag/ImportBundle/Tests/Controller/WallabagV1ControllerTest.php
index 819bb0e6..7f97b0f5 100644
--- a/src/Wallabag/ImportBundle/Tests/Controller/WallabagV1ControllerTest.php
+++ b/src/Wallabag/ImportBundle/Tests/Controller/WallabagV1ControllerTest.php
@@ -58,6 +58,50 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase
58 $this->assertContains('Import summary', $alert[0]); 58 $this->assertContains('Import summary', $alert[0]);
59 } 59 }
60 60
61 public function testImportWallabagWithFileAndMarkAllAsRead()
62 {
63 $this->logInAs('admin');
64 $client = $this->getClient();
65
66 $crawler = $client->request('GET', '/import/wallabag-v1');
67 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
68
69 $file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v1-read.json', 'wallabag-v1-read.json');
70
71 $data = array(
72 'upload_import_file[file]' => $file,
73 'upload_import_file[mark_as_read]' => 1,
74 );
75
76 $client->submit($form, $data);
77
78 $this->assertEquals(302, $client->getResponse()->getStatusCode());
79
80 $crawler = $client->followRedirect();
81
82 $content1 = $client->getContainer()
83 ->get('doctrine.orm.entity_manager')
84 ->getRepository('WallabagCoreBundle:Entry')
85 ->findByUrlAndUserId(
86 'http://gilbert.pellegrom.me/recreating-the-square-slider',
87 $this->getLoggedInUserId()
88 );
89
90 $this->assertTrue($content1->isArchived());
91
92 $content2 = $client->getContainer()
93 ->get('doctrine.orm.entity_manager')
94 ->getRepository('WallabagCoreBundle:Entry')
95 ->findByUrlAndUserId(
96 'https://www.wallabag.org/features/',
97 $this->getLoggedInUserId()
98 );
99
100 $this->assertTrue($content2->isArchived());
101
102 $this->assertContains('Import summary', $client->getResponse()->getContent());
103 }
104
61 public function testImportWallabagWithEmptyFile() 105 public function testImportWallabagWithEmptyFile()
62 { 106 {
63 $this->logInAs('admin'); 107 $this->logInAs('admin');
diff --git a/src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php b/src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php
index f44786b1..bc9e2f42 100644
--- a/src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php
+++ b/src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php
@@ -3,6 +3,7 @@
3namespace Wallabag\ImportBundle\Tests\Import; 3namespace Wallabag\ImportBundle\Tests\Import;
4 4
5use Wallabag\UserBundle\Entity\User; 5use Wallabag\UserBundle\Entity\User;
6use Wallabag\CoreBundle\Entity\Entry;
6use Wallabag\ImportBundle\Import\PocketImport; 7use Wallabag\ImportBundle\Import\PocketImport;
7use GuzzleHttp\Client; 8use GuzzleHttp\Client;
8use GuzzleHttp\Subscriber\Mock; 9use GuzzleHttp\Subscriber\Mock;
@@ -265,9 +266,7 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
265 ->method('getRepository') 266 ->method('getRepository')
266 ->willReturn($entryRepo); 267 ->willReturn($entryRepo);
267 268
268 $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') 269 $entry = new Entry($this->user);
269 ->disableOriginalConstructor()
270 ->getMock();
271 270
272 $this->contentProxy 271 $this->contentProxy
273 ->expects($this->once()) 272 ->expects($this->once())
@@ -283,6 +282,95 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
283 $this->assertEquals(['skipped' => 1, 'imported' => 1], $pocketImport->getSummary()); 282 $this->assertEquals(['skipped' => 1, 'imported' => 1], $pocketImport->getSummary());
284 } 283 }
285 284
285 /**
286 * Will sample results from https://getpocket.com/developer/docs/v3/retrieve.
287 */
288 public function testImportAndMarkAllAsRead()
289 {
290 $client = new Client();
291
292 $mock = new Mock([
293 new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
294 new Response(200, ['Content-Type' => 'application/json'], Stream::factory('
295 {
296 "status": 1,
297 "list": {
298 "229279689": {
299 "item_id": "229279689",
300 "resolved_id": "229279689",
301 "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
302 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
303 "favorite": "1",
304 "status": "1",
305 "resolved_title": "The Massive Ryder Cup Preview",
306 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
307 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
308 "is_article": "1",
309 "has_video": "1",
310 "has_image": "1",
311 "word_count": "3197"
312 },
313 "229279690": {
314 "item_id": "229279689",
315 "resolved_id": "229279689",
316 "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview/2",
317 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
318 "favorite": "1",
319 "status": "0",
320 "resolved_title": "The Massive Ryder Cup Preview",
321 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
322 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
323 "is_article": "1",
324 "has_video": "0",
325 "has_image": "0",
326 "word_count": "3197"
327 }
328 }
329 }
330 ')),
331 ]);
332
333 $client->getEmitter()->attach($mock);
334
335 $pocketImport = $this->getPocketImport();
336
337 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
338 ->disableOriginalConstructor()
339 ->getMock();
340
341 $entryRepo->expects($this->exactly(2))
342 ->method('findByUrlAndUserId')
343 ->will($this->onConsecutiveCalls(false, false));
344
345 $this->em
346 ->expects($this->exactly(2))
347 ->method('getRepository')
348 ->willReturn($entryRepo);
349
350 // check that every entry persisted are archived
351 $this->em
352 ->expects($this->any())
353 ->method('persist')
354 ->with($this->callback(function($persistedEntry) {
355 return $persistedEntry->isArchived();
356 }));
357
358 $entry = new Entry($this->user);
359
360 $this->contentProxy
361 ->expects($this->exactly(2))
362 ->method('updateEntry')
363 ->willReturn($entry);
364
365 $pocketImport->setClient($client);
366 $pocketImport->authorize('wunderbar_code');
367
368 $res = $pocketImport->setMarkAsRead(true)->import();
369
370 $this->assertTrue($res);
371 $this->assertEquals(['skipped' => 0, 'imported' => 2], $pocketImport->getSummary());
372 }
373
286 public function testImportBadResponse() 374 public function testImportBadResponse()
287 { 375 {
288 $client = new Client(); 376 $client = new Client();
diff --git a/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php b/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php
index 9a563a11..fbcd270d 100644
--- a/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php
+++ b/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php
@@ -81,6 +81,39 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
81 $this->assertEquals(['skipped' => 1, 'imported' => 3], $wallabagV1Import->getSummary()); 81 $this->assertEquals(['skipped' => 1, 'imported' => 3], $wallabagV1Import->getSummary());
82 } 82 }
83 83
84 public function testImportAndMarkAllAsRead()
85 {
86 $wallabagV1Import = $this->getWallabagV1Import();
87 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1-read.json');
88
89 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
90 ->disableOriginalConstructor()
91 ->getMock();
92
93 $entryRepo->expects($this->exactly(3))
94 ->method('findByUrlAndUserId')
95 ->will($this->onConsecutiveCalls(false, false, false));
96
97 $this->em
98 ->expects($this->any())
99 ->method('getRepository')
100 ->willReturn($entryRepo);
101
102 // check that every entry persisted are archived
103 $this->em
104 ->expects($this->any())
105 ->method('persist')
106 ->with($this->callback(function($persistedEntry) {
107 return $persistedEntry->isArchived();
108 }));
109
110 $res = $wallabagV1Import->setMarkAsRead(true)->import();
111
112 $this->assertTrue($res);
113
114 $this->assertEquals(['skipped' => 0, 'imported' => 3], $wallabagV1Import->getSummary());
115 }
116
84 public function testImportBadFile() 117 public function testImportBadFile()
85 { 118 {
86 $wallabagV1Import = $this->getWallabagV1Import(); 119 $wallabagV1Import = $this->getWallabagV1Import();
diff --git a/src/Wallabag/ImportBundle/Tests/Import/WallabagV2ImportTest.php b/src/Wallabag/ImportBundle/Tests/Import/WallabagV2ImportTest.php
index 3268cd3e..c461168c 100644
--- a/src/Wallabag/ImportBundle/Tests/Import/WallabagV2ImportTest.php
+++ b/src/Wallabag/ImportBundle/Tests/Import/WallabagV2ImportTest.php
@@ -72,6 +72,39 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
72 $this->assertEquals(['skipped' => 1, 'imported' => 2], $wallabagV2Import->getSummary()); 72 $this->assertEquals(['skipped' => 1, 'imported' => 2], $wallabagV2Import->getSummary());
73 } 73 }
74 74
75 public function testImportAndMarkAllAsRead()
76 {
77 $wallabagV2Import = $this->getWallabagV2Import();
78 $wallabagV2Import->setFilepath(__DIR__.'/../fixtures/wallabag-v2-read.json');
79
80 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
81 ->disableOriginalConstructor()
82 ->getMock();
83
84 $entryRepo->expects($this->exactly(2))
85 ->method('findByUrlAndUserId')
86 ->will($this->onConsecutiveCalls(false, false));
87
88 $this->em
89 ->expects($this->any())
90 ->method('getRepository')
91 ->willReturn($entryRepo);
92
93 // check that every entry persisted are archived
94 $this->em
95 ->expects($this->any())
96 ->method('persist')
97 ->with($this->callback(function($persistedEntry) {
98 return $persistedEntry->isArchived();
99 }));
100
101 $res = $wallabagV2Import->setMarkAsRead(true)->import();
102
103 $this->assertTrue($res);
104
105 $this->assertEquals(['skipped' => 0, 'imported' => 2], $wallabagV2Import->getSummary());
106 }
107
75 public function testImportBadFile() 108 public function testImportBadFile()
76 { 109 {
77 $wallabagV1Import = $this->getWallabagV2Import(); 110 $wallabagV1Import = $this->getWallabagV2Import();
diff --git a/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1-read.json b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1-read.json
new file mode 100644
index 00000000..c4d1cf58
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1-read.json
@@ -0,0 +1,53 @@
1[
2 {
3 "0": "3",
4 "1": "Features - wallabag",
5 "2": "https://www.wallabag.org/features/",
6 "3": "0",
7 "4": "1",
8 "5": "\n\t\t<p>Here are some features. If one is missing, you can <a href=\"https://github.com/wallabag/wallabag\">open a new issue</a>.</p>\n<ul class=\"list-group\"><li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> wallabag is free and open source. Forever.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> No time to read? Save a link in your wallabag to read it later</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Read the saved articles in a comfortable view: the content, only the content</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You save all the content: text and pictures</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can easily migrate from others private services.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You like an article? Share it by email, on twitter or in your <a href=\"https://github.com/sebsauvage/Shaarli\">shaarli</a></li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Because we are increasingly mobile, wallabag fits all your devices</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Saving a link is so easy because we provide you many tools: extensions for Chrome and Firefox, iOS, Android and Windows Phone application, a bookmarklet, a simple field in your config webpage. <a title=\"Download wallabag\" href=\"https://www.wallabag.org/downloads/\">You can download third-party applications here</a>.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> RSS feeds allows you to read your saved links in your RSS agregator</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can set tags to your entries.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> wallabag is multilingual: french, english, spanish, german, italian, russian, persian, czech, polish, ukrainian and slovienian.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You’re not the only one at home to use wallabag? it’s good, wallabag is multi users</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You prefer a dark template? Perfect, many templates are available in the configuration screen</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Many storage allowed: sqlite, mysql and postgresql</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Scroll position is saved: when you return on an article, you come back where you was. So convenient!</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can flattr flattrable articles directly from your wallabag</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You want to retrieve your wallabag datas? hey, remember, wallabag is open source, you can export it</li>\n</ul>",
9 "6": "1",
10 "id": "3",
11 "title": "Features - wallabag",
12 "url": "https://www.wallabag.org/features/",
13 "is_read": "0",
14 "is_fav": "1",
15 "content": "\n\t\t<p>Here are some features. If one is missing, you can <a href=\"https://github.com/wallabag/wallabag\">open a new issue</a>.</p>\n<ul class=\"list-group\"><li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> wallabag is free and open source. Forever.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> No time to read? Save a link in your wallabag to read it later</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Read the saved articles in a comfortable view: the content, only the content</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You save all the content: text and pictures</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can easily migrate from others private services.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You like an article? Share it by email, on twitter or in your <a href=\"https://github.com/sebsauvage/Shaarli\">shaarli</a></li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Because we are increasingly mobile, wallabag fits all your devices</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Saving a link is so easy because we provide you many tools: extensions for Chrome and Firefox, iOS, Android and Windows Phone application, a bookmarklet, a simple field in your config webpage. <a title=\"Download wallabag\" href=\"https://www.wallabag.org/downloads/\">You can download third-party applications here</a>.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> RSS feeds allows you to read your saved links in your RSS agregator</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can set tags to your entries.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> wallabag is multilingual: french, english, spanish, german, italian, russian, persian, czech, polish, ukrainian and slovienian.</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You’re not the only one at home to use wallabag? it’s good, wallabag is multi users</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You prefer a dark template? Perfect, many templates are available in the configuration screen</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Many storage allowed: sqlite, mysql and postgresql</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> Scroll position is saved: when you return on an article, you come back where you was. So convenient!</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You can flattr flattrable articles directly from your wallabag</li>\n<li class=\"list-group-item\"><span class=\"glyphicon glyphicon-ok\"></span> You want to retrieve your wallabag datas? hey, remember, wallabag is open source, you can export it</li>\n</ul>",
16 "user_id": "1",
17 "tags": ""
18 },
19 {
20 "0": "10",
21 "1": "Recreating The Square Slider",
22 "2": "http://gilbert.pellegrom.me/recreating-the-square-slider",
23 "3": "0",
24 "4": "0",
25 "5": "<p>The new <a href=\"https://squareup.com\">Square</a> site is lovely and I really like the slider they have on the homepage. So I decided to try and recreate it in a simple and reusable way.</p>\n\n<p> \n <a href=\"http://gilbert.pellegrom.me/demo/square-slider\">Demo</a> | <a href=\"http://dev7studios.com/downloads/50\">Download</a>\n</p>\n\n\n\n<h3>The HTML</h3>\n\n<pre class=\"language-markup\"><code>&lt;div class=\"square-slider\"&gt; \n &lt;div class=\"slide slide1\"&gt;\n &lt;div class=\"content light\"&gt;\n &lt;h3&gt;Recreating The Square Slider&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset1.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;div class=\"slide slide2\"&gt;\n &lt;div class=\"content dark\"&gt;\n &lt;h3&gt;Looks Amazing Right?&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset2.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;div class=\"slide slide3 inverted\"&gt;\n &lt;div class=\"content light\"&gt;\n &lt;h3&gt;And Simple To Use&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset3.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;a href=\"#\" class=\"prev\"&gt;Prev&lt;/a&gt;\n &lt;a href=\"#\" class=\"next\"&gt;Next&lt;/a&gt;\n &lt;div class=\"overlay\"&gt;&lt;/div&gt;\n&lt;/div&gt;</code></pre>\n\n<h3>The CSS</h3>\n\n<pre class=\"language-css\"><code>.square-slider { \n overflow: hidden;\n position: relative;\n background: #fff;\n}\n.square-slider .slide {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n opacity: 0;\n -moz-opacity: 0;\n -moz-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -webkit-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -o-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -moz-transform: translate3d(0, 0, 0);\n -webkit-transform: translate3d(0, 0, 0);\n -o-transform: translate3d(0, 0, 0);\n -ms-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n}\n.square-slider .slide:first-child { display: block; }\n.square-slider .slide:first-child,\n.square-slider .slide.active {\n opacity: 1;\n -moz-opacity: 1;\n}\n.square-slider .slide .content {\n position: absolute;\n top: 40%;\n left: 50%;\n margin-left: -450px;\n width: 360px;\n text-shadow: 0 1px 1px rgba(0,0,0,0.3);\n z-index: 7;\n -webkit-transition-property: -webkit-transform,opacity;\n -moz-transition-property: -moz-transform,opacity;\n -webkit-transition-duration: 800ms,700ms;\n -moz-transition-duration: 800ms,700ms;\n -webkit-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -moz-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -webkit-transform: translate3d(-30px, 0, 0);\n -moz-transform: translate(-30px, 0);\n}\n.square-slider .slide.inverted .content {\n left: auto;\n right: 50%;\n margin-left: 0;\n margin-right: -450px;\n -webkit-transform: translate3d(30px, 0, 0);\n -moz-transform: translate(30px, 0);\n}\n.square-slider .slide.active .content {\n -webkit-transform: translate3d(0, 0, 0);\n -moz-transform: translate(0, 0);\n}\n.square-slider .slide .asset {\n position: absolute;\n bottom: 0;\n left: 50%;\n -webkit-transition-property: -webkit-transform,opacity;\n -moz-transition-property: -moz-transform,opacity;\n -webkit-transition-duration: 800ms,700ms;\n -moz-transition-duration: 800ms,700ms;\n -webkit-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -moz-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -webkit-transform: translate3d(0, 0, 0);\n -moz-transform: translate(0, 0);\n}\n.square-slider .slide.inverted .asset {\n left: auto;\n right: 50%;\n}\n.square-slider .slide.active .asset {\n -webkit-transform: translate3d(-7px, 3px, 0);\n -moz-transform: translate(-7px, 3px);\n}\n.square-slider .slide.inverted.active .asset {\n -webkit-transform: translate3d(7px, 3px, 0);\n -moz-transform: translate(7px, 3px);\n}\n.square-slider .prev,\n.square-slider .next {\n background: url(images/nav.png) no-repeat;\n display: block;\n width: 67px;\n height: 67px;\n position: absolute;\n top: 50%;\n margin-top: -30px;\n z-index: 10;\n border: 0;\n text-indent: -9999px;\n display: none;\n opacity: 0.6;\n -moz-opacity: 0.6;\n -webkit-transition: opacity 0.5s ease-in;\n -moz-transition: opacity 0.5s ease-in;\n -ms-transition: opacity 0.5s ease-in;\n -o-transition: opacity 0.5s ease-in;\n transition: opacity 0.5s ease-in;\n}\n.square-slider .prev { \n left: 40px; \n background-position: 0 100%;\n}\n.square-slider .next { right: 40px; }\n.square-slider .prev:hover,\n.square-slider .next:hover {\n opacity: 1;\n -moz-opacity: 1;\n}\n.square-slider .overlay {\n position: absolute;\n top: 0;\n left: -100%;\n width: 300%;\n height: 100%;\n z-index: 5;\n -moz-box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n -webkit-box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n}\n\n\n.square-slider {\n width: 100%;\n height: 550px;\n margin: 40px auto;\n}\n.square-slider .slide .content.light { color: #fff; }\n.square-slider .slide .content.dark { \n color: #333; \n text-shadow: 0 1px 1px rgba(255,255,255,0.3);\n}\n.square-slider .slide1 { background: url(images/bg1.jpg) no-repeat 50% 50%; }\n.square-slider .slide2 { background: url(images/bg2.jpg) no-repeat 50% 50%; }\n.square-slider .slide3 { background: url(images/bg3.jpg) no-repeat 50% 50%; }</code></pre>\n\n<h3>The Javascript (jQuery)</h3>\n\n<pre class=\"language-javascript\"><code>(function($){\n\n $('.square-slider').each(function(){\n var slider = $(this),\n slides = slider.find('.slide'),\n currentSlide = 0;\n\n slides.show();\n $(slides[currentSlide]).addClass('active');\n $('.next,.prev', slider).show();\n\n $('.prev', slider).on('click', function(){\n slides.removeClass('active');\n currentSlide--;\n if(currentSlide &lt; 0) currentSlide = slides.length - 1;\n $(slides[currentSlide]).addClass('active');\n return false;\n });\n\n $('.next', slider).on('click', function(){\n slides.removeClass('active');\n currentSlide++;\n if(currentSlide &gt; slides.length - 1) currentSlide = 0;\n $(slides[currentSlide]).addClass('active');\n return false;\n });\n });\n\n})(window.jQuery);</code></pre>\n\n<h3>A Few Notes</h3>\n\n<ul><li>Feel free to <a href=\"http://dev7studios.com/downloads/50\">download the source</a> and customise and use it in your own sites. <strong>Don’t</strong> use the images as they belong to <a href=\"https://squareup.com\">Square Inc</a>.</li>\n<li>Add the <code>.inverted</code> class to a <code>.slide</code> div to swap the position of the asset and content.</li>\n<li>Depending on the background you’ll want to use the <code>.light</code> or <code>.dark</code> class on the <code>.content</code> divs.</li>\n<li>This is pretty much cross browser (as in the slider itself will still work in most browsers, just not with the fancy transitions).</li>\n<li>If javascript is off (really?) it displays the first slide.</li>\n<li>Source code is Public domain.</li>\n</ul><p>Enjoy.</p>\n ",
26 "6": "1",
27 "id": "10",
28 "title": "Recreating The Square Slider",
29 "url": "http://gilbert.pellegrom.me/recreating-the-square-slider",
30 "is_read": "0",
31 "is_fav": "0",
32 "content": "<p>The new <a href=\"https://squareup.com\">Square</a> site is lovely and I really like the slider they have on the homepage. So I decided to try and recreate it in a simple and reusable way.</p>\n\n<p> \n <a href=\"http://gilbert.pellegrom.me/demo/square-slider\">Demo</a> | <a href=\"http://dev7studios.com/downloads/50\">Download</a>\n</p>\n\n\n\n<h3>The HTML</h3>\n\n<pre class=\"language-markup\"><code>&lt;div class=\"square-slider\"&gt; \n &lt;div class=\"slide slide1\"&gt;\n &lt;div class=\"content light\"&gt;\n &lt;h3&gt;Recreating The Square Slider&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset1.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;div class=\"slide slide2\"&gt;\n &lt;div class=\"content dark\"&gt;\n &lt;h3&gt;Looks Amazing Right?&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset2.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;div class=\"slide slide3 inverted\"&gt;\n &lt;div class=\"content light\"&gt;\n &lt;h3&gt;And Simple To Use&lt;/h3&gt;\n &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac eros et augue vulputate \n aliquet pellentesque vitae tortor. Pellentesque mi velit, euismod nec semper&lt;/p&gt;\n &lt;/div&gt;\n &lt;img src=\"images/asset3.png\" alt=\"\" class=\"asset\" /&gt;\n &lt;/div&gt;\n &lt;a href=\"#\" class=\"prev\"&gt;Prev&lt;/a&gt;\n &lt;a href=\"#\" class=\"next\"&gt;Next&lt;/a&gt;\n &lt;div class=\"overlay\"&gt;&lt;/div&gt;\n&lt;/div&gt;</code></pre>\n\n<h3>The CSS</h3>\n\n<pre class=\"language-css\"><code>.square-slider { \n overflow: hidden;\n position: relative;\n background: #fff;\n}\n.square-slider .slide {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n opacity: 0;\n -moz-opacity: 0;\n -moz-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -webkit-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -o-transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n transition: opacity 800ms cubic-bezier(0.51, 0.01, 0.37, 0.98) 100ms;\n -moz-transform: translate3d(0, 0, 0);\n -webkit-transform: translate3d(0, 0, 0);\n -o-transform: translate3d(0, 0, 0);\n -ms-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n}\n.square-slider .slide:first-child { display: block; }\n.square-slider .slide:first-child,\n.square-slider .slide.active {\n opacity: 1;\n -moz-opacity: 1;\n}\n.square-slider .slide .content {\n position: absolute;\n top: 40%;\n left: 50%;\n margin-left: -450px;\n width: 360px;\n text-shadow: 0 1px 1px rgba(0,0,0,0.3);\n z-index: 7;\n -webkit-transition-property: -webkit-transform,opacity;\n -moz-transition-property: -moz-transform,opacity;\n -webkit-transition-duration: 800ms,700ms;\n -moz-transition-duration: 800ms,700ms;\n -webkit-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -moz-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -webkit-transform: translate3d(-30px, 0, 0);\n -moz-transform: translate(-30px, 0);\n}\n.square-slider .slide.inverted .content {\n left: auto;\n right: 50%;\n margin-left: 0;\n margin-right: -450px;\n -webkit-transform: translate3d(30px, 0, 0);\n -moz-transform: translate(30px, 0);\n}\n.square-slider .slide.active .content {\n -webkit-transform: translate3d(0, 0, 0);\n -moz-transform: translate(0, 0);\n}\n.square-slider .slide .asset {\n position: absolute;\n bottom: 0;\n left: 50%;\n -webkit-transition-property: -webkit-transform,opacity;\n -moz-transition-property: -moz-transform,opacity;\n -webkit-transition-duration: 800ms,700ms;\n -moz-transition-duration: 800ms,700ms;\n -webkit-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -moz-transition-timing-function: cubic-bezier(0.51, 0.01, 0.37, 0.98);\n -webkit-transform: translate3d(0, 0, 0);\n -moz-transform: translate(0, 0);\n}\n.square-slider .slide.inverted .asset {\n left: auto;\n right: 50%;\n}\n.square-slider .slide.active .asset {\n -webkit-transform: translate3d(-7px, 3px, 0);\n -moz-transform: translate(-7px, 3px);\n}\n.square-slider .slide.inverted.active .asset {\n -webkit-transform: translate3d(7px, 3px, 0);\n -moz-transform: translate(7px, 3px);\n}\n.square-slider .prev,\n.square-slider .next {\n background: url(images/nav.png) no-repeat;\n display: block;\n width: 67px;\n height: 67px;\n position: absolute;\n top: 50%;\n margin-top: -30px;\n z-index: 10;\n border: 0;\n text-indent: -9999px;\n display: none;\n opacity: 0.6;\n -moz-opacity: 0.6;\n -webkit-transition: opacity 0.5s ease-in;\n -moz-transition: opacity 0.5s ease-in;\n -ms-transition: opacity 0.5s ease-in;\n -o-transition: opacity 0.5s ease-in;\n transition: opacity 0.5s ease-in;\n}\n.square-slider .prev { \n left: 40px; \n background-position: 0 100%;\n}\n.square-slider .next { right: 40px; }\n.square-slider .prev:hover,\n.square-slider .next:hover {\n opacity: 1;\n -moz-opacity: 1;\n}\n.square-slider .overlay {\n position: absolute;\n top: 0;\n left: -100%;\n width: 300%;\n height: 100%;\n z-index: 5;\n -moz-box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n -webkit-box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);\n}\n\n\n.square-slider {\n width: 100%;\n height: 550px;\n margin: 40px auto;\n}\n.square-slider .slide .content.light { color: #fff; }\n.square-slider .slide .content.dark { \n color: #333; \n text-shadow: 0 1px 1px rgba(255,255,255,0.3);\n}\n.square-slider .slide1 { background: url(images/bg1.jpg) no-repeat 50% 50%; }\n.square-slider .slide2 { background: url(images/bg2.jpg) no-repeat 50% 50%; }\n.square-slider .slide3 { background: url(images/bg3.jpg) no-repeat 50% 50%; }</code></pre>\n\n<h3>The Javascript (jQuery)</h3>\n\n<pre class=\"language-javascript\"><code>(function($){\n\n $('.square-slider').each(function(){\n var slider = $(this),\n slides = slider.find('.slide'),\n currentSlide = 0;\n\n slides.show();\n $(slides[currentSlide]).addClass('active');\n $('.next,.prev', slider).show();\n\n $('.prev', slider).on('click', function(){\n slides.removeClass('active');\n currentSlide--;\n if(currentSlide &lt; 0) currentSlide = slides.length - 1;\n $(slides[currentSlide]).addClass('active');\n return false;\n });\n\n $('.next', slider).on('click', function(){\n slides.removeClass('active');\n currentSlide++;\n if(currentSlide &gt; slides.length - 1) currentSlide = 0;\n $(slides[currentSlide]).addClass('active');\n return false;\n });\n });\n\n})(window.jQuery);</code></pre>\n\n<h3>A Few Notes</h3>\n\n<ul><li>Feel free to <a href=\"http://dev7studios.com/downloads/50\">download the source</a> and customise and use it in your own sites. <strong>Don’t</strong> use the images as they belong to <a href=\"https://squareup.com\">Square Inc</a>.</li>\n<li>Add the <code>.inverted</code> class to a <code>.slide</code> div to swap the position of the asset and content.</li>\n<li>Depending on the background you’ll want to use the <code>.light</code> or <code>.dark</code> class on the <code>.content</code> divs.</li>\n<li>This is pretty much cross browser (as in the slider itself will still work in most browsers, just not with the fancy transitions).</li>\n<li>If javascript is off (really?) it displays the first slide.</li>\n<li>Source code is Public domain.</li>\n</ul><p>Enjoy.</p>\n ",
33 "user_id": "1",
34 "tags": ""
35 },
36 {
37 "0": "11",
38 "1": "J’aime le logiciel libre",
39 "2": "http://framablog.org/2015/02/14/jaime-le-logiciel-libre/",
40 "3": "0",
41 "4": "0",
42 "5": "\n <p>Aujourd’hui, c’est la <em>Saint Valentin</em>, et l’occasion de déclarer son amour des logiciels libres !</p>\n<p><a href=\"https://fsfe.org/campaigns/ilovefs/ilovefs.fr.html\"><img src=\"http://framablog.org/wp-content/uploads/2015/02/ilovefs-banner-extralarge.png\" alt=\"ilovefs-banner-extralarge\" width=\"627\" height=\"105\" class=\"aligncenter size-full wp-image-3239\" /></a></p>\n<p>Framasoft vous a déjà proposé <a href=\"http://framablog.org/2015/02/14/on-love-logiciel-libre/\">son adaptation délirante de poèmes</a> pour l’occasion, et voici une petite bande-dessinée qui synthétise l’événement :</p>\n<p><a href=\"http://framablog.org/wp-content/uploads/2015/02/dm_001_jaime_le_logiciel_libre.jpg\"><img src=\"http://framablog.org/wp-content/uploads/2015/02/dm_001_jaime_le_logiciel_libre.jpg\" alt=\"dm_001_jaime_le_logiciel_libre\" width=\"800\" height=\"1200\" class=\"aligncenter size-full wp-image-3265\" /></a></p>\n<p>Cette bande-dessinée est extraite du nouveau blog <a href=\"http://grisebouille.net/\">Grise Bouille</a> hébergé par Framasoft.</p>\n<p><em>Crédit : <a href=\"http://grisebouille.net/\">Simon Gee Giraudot</a> (Creative Commons By-Sa)</em></p>\n<div title=\"Diaspora*\"><a><span><img src=\"http://www.framablog.org/wp-content/uploads/2015/01/diaspora.jpg\" alt=\"\" /></span></a></div><div class=\"twoclick-js\"></div><div><h3><a href=\"http://framablog.org/author/gee/\" title=\"All posts by Gee\">Gee</a></h3><div class=\"bio-gravatar\"><img alt=\"\" src=\"http://0.gravatar.com/avatar/4f71e4ffe5d4d9d89b16949563cd41f3?s=90&amp;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D90&amp;r=G\" class=\"avatar pull-left media-object avatar-90 photo\" height=\"90\" width=\"90\" /></div><a href=\"http://ptilouk.net/\" class=\"bio-icon bio-icon-website\"></a><a href=\"https://framasphere.org/u/gee\" class=\"bio-icon bio-icon-facebook\"></a><a href=\"https://twitter.com/ptilouk\" class=\"bio-icon bio-icon-twitter\"></a><p class=\"bio-description\">Auteur/dessinateur de bandes dessinées (Le Geektionnerd, Superflu, Bastards Inc, etc.) et doctorant en informatique sur son temps salarié.</p></div> ",
43 "6": "1",
44 "id": "11",
45 "title": "J’aime le logiciel libre",
46 "url": "http://framablog.org/2015/02/14/jaime-le-logiciel-libre/",
47 "is_read": "0",
48 "is_fav": "0",
49 "content": "\n <p>Aujourd’hui, c’est la <em>Saint Valentin</em>, et l’occasion de déclarer son amour des logiciels libres !</p>\n<p><a href=\"https://fsfe.org/campaigns/ilovefs/ilovefs.fr.html\"><img src=\"http://framablog.org/wp-content/uploads/2015/02/ilovefs-banner-extralarge.png\" alt=\"ilovefs-banner-extralarge\" width=\"627\" height=\"105\" class=\"aligncenter size-full wp-image-3239\" /></a></p>\n<p>Framasoft vous a déjà proposé <a href=\"http://framablog.org/2015/02/14/on-love-logiciel-libre/\">son adaptation délirante de poèmes</a> pour l’occasion, et voici une petite bande-dessinée qui synthétise l’événement :</p>\n<p><a href=\"http://framablog.org/wp-content/uploads/2015/02/dm_001_jaime_le_logiciel_libre.jpg\"><img src=\"http://framablog.org/wp-content/uploads/2015/02/dm_001_jaime_le_logiciel_libre.jpg\" alt=\"dm_001_jaime_le_logiciel_libre\" width=\"800\" height=\"1200\" class=\"aligncenter size-full wp-image-3265\" /></a></p>\n<p>Cette bande-dessinée est extraite du nouveau blog <a href=\"http://grisebouille.net/\">Grise Bouille</a> hébergé par Framasoft.</p>\n<p><em>Crédit : <a href=\"http://grisebouille.net/\">Simon Gee Giraudot</a> (Creative Commons By-Sa)</em></p>\n<div title=\"Diaspora*\"><a><span><img src=\"http://www.framablog.org/wp-content/uploads/2015/01/diaspora.jpg\" alt=\"\" /></span></a></div><div class=\"twoclick-js\"></div><div><h3><a href=\"http://framablog.org/author/gee/\" title=\"All posts by Gee\">Gee</a></h3><div class=\"bio-gravatar\"><img alt=\"\" src=\"http://0.gravatar.com/avatar/4f71e4ffe5d4d9d89b16949563cd41f3?s=90&amp;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D90&amp;r=G\" class=\"avatar pull-left media-object avatar-90 photo\" height=\"90\" width=\"90\" /></div><a href=\"http://ptilouk.net/\" class=\"bio-icon bio-icon-website\"></a><a href=\"https://framasphere.org/u/gee\" class=\"bio-icon bio-icon-facebook\"></a><a href=\"https://twitter.com/ptilouk\" class=\"bio-icon bio-icon-twitter\"></a><p class=\"bio-description\">Auteur/dessinateur de bandes dessinées (Le Geektionnerd, Superflu, Bastards Inc, etc.) et doctorant en informatique sur son temps salarié.</p></div> ",
50 "user_id": "1",
51 "tags": "framasoft,tag"
52 }
53]
diff --git a/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v2-read.json b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v2-read.json
new file mode 100644
index 00000000..3fa0bddf
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v2-read.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": 4668,
4 "title": "Wikimedia Foundation removes The Diary of Anne Frank due to copyright law requirements « Wikimedia blog",
5 "url": "https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/",
6 "is_archived": true,
7 "is_starred": false,
8 "content": "<p><a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\" rel=\"attachment wp-att-45105\"><img class=\"alignnone size-full wp-image-45105\" src=\"https://wikimediablog.files.wordpress.com/2016/02/annefrankschoolphoto.jpg?w=316&amp;h=520\" alt=\"AnneFrankSchoolPhoto\" width=\"316\" height=\"520\"/></a><br/><small><i>Anne Frank in 1940. <a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\">Photo</a> by Collectie Anne Frank Stichting Amsterdam, public domain.</i></small></p>\n<p>Today, in an unfortunate example of the overreach of the United States’ current copyright law, the Wikimedia Foundation removed the Dutch-language text of <a href=\"https://en.wikipedia.org/wiki/The_Diary_of_a_Young_Girl\"><em>The Diary of a Young Girl</em></a>—more commonly known in English as the <em>Diary of Anne Frank—</em>from Wikisource.<sup id=\"one\"><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#cite1\">[1]</a></sup></p>\n<p>We took this action to comply with the United States’ <a href=\"https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act\">Digital Millennium Copyright Act</a> (DMCA), as we believe the diary is still under US copyright protection under the law as it is currently written. Nevertheless, our removal serves as an excellent example of why the law should be changed to prevent repeated extensions of copyright terms, an issue that has plagued our communities <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Wikipedia_Signpost/2014-02-26/News_and_notes\">for years</a>.</p>\n<h3>What prompted us to remove the diary?</h3>\n<p>The deletion was required because the Foundation is under the jurisdiction of US law and is therefore subject to the DMCA, specifically <a href=\"https://www.law.cornell.edu/uscode/text/17/512\" target=\"_blank\">title 17, chapter 5, section 512 of the United States Code</a>. As we <a href=\"https://meta.wikimedia.org/wiki/Legal/URAA_Statement\" target=\"_blank\">noted</a> in 2013, “The location of the servers, incorporation, and headquarters are just three of many factors that establish US jurisdiction … if infringing content is linked to or embedded in Wikimedia projects, then  the Foundation may still be subject to liability for such use—either as a direct or contributory infringer.</p>\n<p>Based on email discussions sent to the Wikimedia Foundation at legal[at]wikimedia.org, we determined that the Wikimedia Foundation had either “actual knowledge” (i in the statute quoted below) or what is commonly called “red flag knowledge” (ii in the statute quoted below) that the Anne Frank text was hosted on Wikisource and was under copyright. The statute section states that a service provider is only protected by the DMCA when it:</p>\n<p><strong>(i) </strong>does not have actual knowledge that the material or an activity using the material on the system or network is infringing;</p>\n<p><strong>(ii) </strong>in the absence of such actual knowledge, is not aware of facts or circumstances from which infringing activity is apparent; or</p>\n<p>(The rest applies when we get a proper DMCA takedown notice.)</p>\n<p>Of particular concern, the US’ <a href=\"https://en.wikipedia.org/wiki/United_States_Court_of_Appeals_for_the_Ninth_Circuit\">9th Circuit Court of Appeals</a> stated in their ruling for <a href=\"http://law.justia.com/cases/federal/appellate-courts/ca9/09-55902/09-55902-2013-03-14.html\"><em>UMG Recordings, Inc. v. Shelter Capital Partners LLC</em></a> that in circumstances where a hosting provider (like the Wikimedia Foundation) is informed by a third party (like an unrelated user) about infringing copyrighted content, that would likely constitute either actual or red flag knowledge under the DMCA.</p>\n<p>We believe, based on the detail and specificity contained in the emails, that we received that we had actual knowledge sufficient for the DMCA to require us to perform a takedown even in the absence of a demand letter.</p>\n<h3>How is the diary still copyrighted?</h3>\n<p>You may wonder why or how the Anne Frank text is copyrighted at all, as <a href=\"https://en.wikipedia.org/wiki/Anne_Frank\">Anne Frank</a> died in February 1945. With 70 years having passed since her death, the text may have passed into public domain in the Netherlands on January 1, 2016, where it was first published, although <a href=\"http://www.npr.org/sections/thetwo-way/2015/12/31/461606275/mein-kampf-enters-public-domain-arguably-anne-franks-diary-may-too\">there is still some dispute about this</a>.</p>\n<p>However, in the United States, the Anne Frank original text will be under copyright until 2042. This is the result of several factors coming together, and the English-language Wikipedia has actually covered this issue with a multi-part test on its <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Non-U.S._copyrights\">non-US copyrights content guideline.</a></p>\n<p>In short, there are three major laws that together make the diary still copyrighted:</p>\n<ol><li>In general, the U.S. copyright for works published before 1978 is 95 years from date of publication. This came about because copyrights in the U.S. were originally for 28 years, with the ability to then extend that for a second 28 years (making a total of 56). Starting with the <a href=\"https://en.wikipedia.org/wiki/Copyright_Act_of_1976\">1976 Copyright Act</a> and extending to several more acts, the renewal became automatic and was extended. Today, the total term of works published before 1978 is 95 years from date of publication.</li>\n<li>Foreign works of countries that are treaty partners to the United States are covered as if they were US works.</li>\n<li>Even if a country was not a treaty partner under copyright law at the time of a publication, the <a href=\"https://en.wikipedia.org/wiki/Uruguay_Round_Agreements_Act\">1994 Uruguay Round Agreements Act</a> (URAA) restored copyright to works that:\n<ul><li>had been published in a foreign country</li>\n<li>were still under copyright in that country in 1996</li>\n<li>and would have had U.S. copyright but for the fact they were published abroad.</li>\n</ul></li>\n</ol>\n<p>Court challenges to the URAA have all failed, with the most notable (<a href=\"https://en.wikipedia.org/wiki/Golan_v._Holder\"><em>Golan v. Holder</em></a>) resulting in a Supreme Court ruling that upheld the URAA.</p>\n<p>What that means for Anne Frank’s diary is unfortunately simple: no matter how it wound up in the United States and regardless of what formal copyright notices they used, the US grants it copyright until the year 2042, or 95 years after its original publication in 1947.</p>\n<p>Under current copyright law, this remains true regardless of its copyright status anywhere else in the world and regardless of whether it may have been in the public domain in the United States in the past.</p>\n<p><a href=\"https://wikimediafoundation.org/wiki/User:Jrogers_(WMF)\"><em>Jacob Rogers</em></a><em>, Legal Counsel*<br/>Wikimedia Foundation</em></p>\n<p><em>*Special thanks to </em><a href=\"https://wikimediafoundation.org/wiki/User:AMangalick_(WMF)\"><em>Anisha Mangalick</em></a><em>, Legal Fellow, for her assistance in this matter.</em></p>\n<p><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#one\">[1]</a> The diary text was originally located at <a href=\"https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)\" rel=\"nofollow\">https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)</a>.</p>\n<p><em>This article was edited to clarify that it is not just the location of the Wikimedia Foundation’s servers that determine whether we fall in US jurisdiction.</em></p>\n\t\t\t\t\t\t\t\t\t\t\t",
9 "mimetype": "text/html",
10 "language": "en",
11 "reading_time": 4,
12 "domain_name": "blog.wikimedia.org",
13 "tags": []
14 },
15 {
16 "id": 4667,
17 "title": "Tails - Tails 2.0.1 is out",
18 "url": "https://tails.boum.org/news/version_2.0.1/index.en.html",
19 "is_archived": false,
20 "is_starred": false,
21 "content": "<div id=\"pagebody\" readability=\"39\">\n<p>This release fixes <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">numerous security issues</a>. All users must upgrade as soon as possible.</p>\n<div class=\"toc\">\n<ol><li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index1h1\">Changes</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index2h1\">Known issues</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index3h1\">Download or upgrade</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index4h1\">What's coming up?</a></li>\n</ol></div>\n<h2>New features</h2>\n<ul><li readability=\"11\">\n<p>Tails now uses the GNOME Shell desktop environment, in its Classic mode. GNOME Shell provides a modern, simple, and actively developed desktop environment. The Classic mode keeps the traditional Applications, Places menu, and windows list. Accessibility and non-Latin input sources are also better integrated.</p>\n<p>To find your way around, <a href=\"https://tails.boum.org/doc/first_steps/introduction_to_gnome_and_the_tails_desktop/index.en.html\">read our introduction to GNOME and the Tails desktop.</a></p>\n<table class=\"img\"><caption>The desktop and Applications menu</caption>\n<tr><td><img alt=\"Tails 2.0 desktop with applications menu unfolded\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/applications_menu.png\" width=\"512\"/></td>\n</tr></table><table class=\"img\"><caption>The activities overview</caption>\n<tr><td><img alt=\"Tails 2.0 activities overview\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/activities_overview.png\" width=\"512\"/></td>\n</tr></table></li>\n</ul><h2>Upgrades and changes</h2>\n<ul><li readability=\"2\">\n<p>Debian 8 upgrades most included software, for example:</p>\n<ul><li>Many core GNOME utilities from 3.4 to 3.14: Files, Disks, Videos, etc.</li>\n<li>LibreOffice from 3.5 to 4.3</li>\n<li>PiTiVi from 0.15 to 0.93</li>\n<li>Git from 1.7.10 to 2.1.4</li>\n<li>Poedit from 1.5.4 to 1.6.10</li>\n<li>Liferea from 1.8.6 to 1.10</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update Tor Browser to 5.5 (based on Firefox 38.6.0 ESR):</p>\n<ul><li>Add Japanese support.</li>\n</ul></li>\n<li readability=\"2\">\n<p>Remove the Windows camouflage which is currently broken in GNOME Shell. We started working on <a href=\"https://labs.riseup.net/code/issues/10830\">adding it back</a> but <a href=\"https://tails.boum.org/news/windows_camouflage_jessie/index.en.html\">your help is needed</a>!</p>\n</li>\n<li readability=\"1\">\n<p>Change to <code>systemd</code> as init system and use it to:</p>\n<ul><li>Sandbox many services using Linux namespaces and make them harder to exploit.</li>\n<li>Make the launching of Tor and the memory wipe on shutdown more robust.</li>\n<li>Sanitize our code base by replacing many custom scripts.</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update most firmware packages which might improve hardware compatibility.</p>\n</li>\n<li readability=\"1\">\n<p>Notify the user if Tails is running from a non-free virtualization software.</p>\n</li>\n<li readability=\"3\">\n<p>Remove Claws Mail, replaced by <a href=\"https://tails.boum.org/doc/anonymous_internet/icedove/index.en.html\">Icedove</a>, a rebranded version of Mozilla Thunderbird.</p>\n</li>\n</ul><h2>Fixed problems</h2>\n<ul><li readability=\"1\">\n<p>HiDPI displays are better supported. (<a href=\"https://labs.riseup.net/code/issues/8659\">#8659</a>)</p>\n</li>\n<li readability=\"3\">\n<p>Remove the option to open a download with an external application in Tor Browser as this is usually impossible due to the AppArmor confinement. (<a href=\"https://labs.riseup.net/code/issues/9285\">#9285</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Close Vidalia before restarting Tor.</p>\n</li>\n<li readability=\"2\">\n<p>Allow Videos to access the DVD drive. (<a href=\"https://labs.riseup.net/code/issues/10455\">#10455</a>, <a href=\"https://labs.riseup.net/code/issues/9990\">#9990</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Allow configuring printers without administration password. (<a href=\"https://labs.riseup.net/code/issues/8443\">#8443</a>)</p>\n</li>\n</ul>\n<p>See the current list of <a href=\"https://tails.boum.org/support/known_issues/index.en.html\">known issues</a>.</p>\n<p>Go to the <a href=\"https://tails.boum.org/download/index.en.html\">download</a> or <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html\">upgrade</a> page.</p>\n<p>If your Tails does not boot after an automatic upgrade, please <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html#manual\">upgrade your Tails manually</a>.</p>\n<p>The next Tails release is <a href=\"https://tails.boum.org/contribute/calendar/\">scheduled</a> for March 08.</p>\n<p>Have a look at our <a href=\"https://labs.riseup.net/code/projects/tails/roadmap\">roadmap</a> to see where we are heading to.</p>\n<p>We need your help and there are many ways to <a href=\"https://tails.boum.org/contribute/index.en.html\">contribute to Tails</a> (<a href=\"https://tails.boum.org/contribute/how/donate/index.en.html\">donating</a> is only one of them). Come <a href=\"https://tails.boum.org/contribute/talk/\">talk to us</a>!</p>\n</div><div id=\"footer\" class=\"pagefooter\" role=\"contentinfo\" readability=\"15\">\n<p>Tags: <a href=\"https://tails.boum.org/tags/announce/\" rel=\"tag\">announce</a></p>\n<p>Pages linking to this one: <a href=\"https://tails.boum.org/inc/stable_i386_release_notes/index.en.html\">inc/stable i386 release notes</a> <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">security/Numerous security holes in 2.0</a></p>\n<p>Last edited Sat 13 Feb 2016 02:23:58 PM CET </p>\n</div>",
22 "mimetype": "text/html",
23 "language": "en",
24 "reading_time": 1,
25 "domain_name": "tails.boum.org",
26 "tags": []
27 }
28]