diff options
Diffstat (limited to 'src/Wallabag')
4 files changed, 89 insertions, 41 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index 31bb67fd..c3ba1858 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php | |||
@@ -273,6 +273,9 @@ class EntryRestController extends WallabagRestController | |||
273 | /** | 273 | /** |
274 | * Create an entry. | 274 | * Create an entry. |
275 | * | 275 | * |
276 | * If you want to provide the HTML content (which means wallabag won't fetch it from the url), you must provide `content`, `title` & `url` fields **non-empty**. | ||
277 | * Otherwise, content will be fetched as normal from the url and values will be overwritten. | ||
278 | * | ||
276 | * @ApiDoc( | 279 | * @ApiDoc( |
277 | * parameters={ | 280 | * parameters={ |
278 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, | 281 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, |
@@ -280,6 +283,11 @@ class EntryRestController extends WallabagRestController | |||
280 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, | 283 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, |
281 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, | 284 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, |
282 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, | 285 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, |
286 | * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"}, | ||
287 | * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"}, | ||
288 | * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"}, | ||
289 | * {"name"="published_at", "dataType"="datetime|integer", "format"="YYYY-MM-DDTHH:II:SS+TZ or a timestamp", "required"=false, "description"="Published date of the entry"}, | ||
290 | * {"name"="authors", "dataType"="string", "format"="Name Firstname,author2,author3", "required"=false, "description"="Authors of the entry"}, | ||
283 | * } | 291 | * } |
284 | * ) | 292 | * ) |
285 | * | 293 | * |
@@ -291,32 +299,46 @@ class EntryRestController extends WallabagRestController | |||
291 | 299 | ||
292 | $url = $request->request->get('url'); | 300 | $url = $request->request->get('url'); |
293 | $title = $request->request->get('title'); | 301 | $title = $request->request->get('title'); |
302 | $tags = $request->request->get('tags', []); | ||
294 | $isArchived = $request->request->get('archive'); | 303 | $isArchived = $request->request->get('archive'); |
295 | $isStarred = $request->request->get('starred'); | 304 | $isStarred = $request->request->get('starred'); |
305 | $content = $request->request->get('content'); | ||
306 | $language = $request->request->get('language'); | ||
307 | $picture = $request->request->get('preview_picture'); | ||
308 | $publishedAt = $request->request->get('published_at'); | ||
309 | $authors = $request->request->get('authors', ''); | ||
296 | 310 | ||
297 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId()); | 311 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId()); |
298 | 312 | ||
299 | if (false === $entry) { | 313 | if (false === $entry) { |
300 | $entry = new Entry($this->getUser()); | 314 | $entry = new Entry($this->getUser()); |
301 | try { | ||
302 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry( | ||
303 | $entry, | ||
304 | $url | ||
305 | ); | ||
306 | } catch (\Exception $e) { | ||
307 | $this->get('logger')->error('Error while saving an entry', [ | ||
308 | 'exception' => $e, | ||
309 | 'entry' => $entry, | ||
310 | ]); | ||
311 | $entry->setUrl($url); | ||
312 | } | ||
313 | } | 315 | } |
314 | 316 | ||
315 | if (!is_null($title)) { | 317 | try { |
316 | $entry->setTitle($title); | 318 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry( |
319 | $entry, | ||
320 | $url, | ||
321 | [ | ||
322 | 'title' => $title, | ||
323 | 'html' => $content, | ||
324 | 'url' => $url, | ||
325 | 'language' => $language, | ||
326 | 'date' => $publishedAt, | ||
327 | // faking the preview picture | ||
328 | 'open_graph' => [ | ||
329 | 'og_image' => $picture, | ||
330 | ], | ||
331 | 'authors' => explode(',', $authors), | ||
332 | ] | ||
333 | ); | ||
334 | } catch (\Exception $e) { | ||
335 | $this->get('logger')->error('Error while saving an entry', [ | ||
336 | 'exception' => $e, | ||
337 | 'entry' => $entry, | ||
338 | ]); | ||
339 | $entry->setUrl($url); | ||
317 | } | 340 | } |
318 | 341 | ||
319 | $tags = $request->request->get('tags', ''); | ||
320 | if (!empty($tags)) { | 342 | if (!empty($tags)) { |
321 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); | 343 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); |
322 | } | 344 | } |
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index 4b3e6fbb..8ba77ca9 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php | |||
@@ -31,7 +31,7 @@ class ContentProxy | |||
31 | } | 31 | } |
32 | 32 | ||
33 | /** | 33 | /** |
34 | * Fetch content using graby and hydrate given entry with results information. | 34 | * Fetch content using graby and hydrate given $entry with results information. |
35 | * In case we couldn't find content, we'll try to use Open Graph data. | 35 | * In case we couldn't find content, we'll try to use Open Graph data. |
36 | * | 36 | * |
37 | * We can also force the content, in case of an import from the v1 for example, so the function won't | 37 | * We can also force the content, in case of an import from the v1 for example, so the function won't |
@@ -39,12 +39,17 @@ class ContentProxy | |||
39 | * | 39 | * |
40 | * @param Entry $entry Entry to update | 40 | * @param Entry $entry Entry to update |
41 | * @param string $url Url to grab content for | 41 | * @param string $url Url to grab content for |
42 | * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url | 42 | * @param array $content An array with AT LEAST keys title, html, url to skip the fetchContent from the url |
43 | * | 43 | * |
44 | * @return Entry | 44 | * @return Entry |
45 | */ | 45 | */ |
46 | public function updateEntry(Entry $entry, $url, array $content = []) | 46 | public function updateEntry(Entry $entry, $url, array $content = []) |
47 | { | 47 | { |
48 | // ensure content is a bit cleaned up | ||
49 | if (!empty($content['html'])) { | ||
50 | $content['html'] = $this->graby->cleanupHtml($content['html'], $url); | ||
51 | } | ||
52 | |||
48 | // do we have to fetch the content or the provided one is ok? | 53 | // do we have to fetch the content or the provided one is ok? |
49 | if (empty($content) || false === $this->validateContent($content)) { | 54 | if (empty($content) || false === $this->validateContent($content)) { |
50 | $fetchedContent = $this->graby->fetchContent($url); | 55 | $fetchedContent = $this->graby->fetchContent($url); |
@@ -57,7 +62,7 @@ class ContentProxy | |||
57 | } | 62 | } |
58 | 63 | ||
59 | $title = $content['title']; | 64 | $title = $content['title']; |
60 | if (!$title && isset($content['open_graph']['og_title'])) { | 65 | if (!$title && !empty($content['open_graph']['og_title'])) { |
61 | $title = $content['open_graph']['og_title']; | 66 | $title = $content['open_graph']['og_title']; |
62 | } | 67 | } |
63 | 68 | ||
@@ -65,7 +70,7 @@ class ContentProxy | |||
65 | if (false === $html) { | 70 | if (false === $html) { |
66 | $html = $this->fetchingErrorMessage; | 71 | $html = $this->fetchingErrorMessage; |
67 | 72 | ||
68 | if (isset($content['open_graph']['og_description'])) { | 73 | if (!empty($content['open_graph']['og_description'])) { |
69 | $html .= '<p><i>But we found a short description: </i></p>'; | 74 | $html .= '<p><i>But we found a short description: </i></p>'; |
70 | $html .= $content['open_graph']['og_description']; | 75 | $html .= $content['open_graph']['og_description']; |
71 | } | 76 | } |
@@ -76,8 +81,19 @@ class ContentProxy | |||
76 | $entry->setContent($html); | 81 | $entry->setContent($html); |
77 | $entry->setHttpStatus(isset($content['status']) ? $content['status'] : ''); | 82 | $entry->setHttpStatus(isset($content['status']) ? $content['status'] : ''); |
78 | 83 | ||
79 | if (isset($content['date']) && null !== $content['date'] && '' !== $content['date']) { | 84 | if (!empty($content['date'])) { |
80 | $entry->setPublishedAt(new \DateTime($content['date'])); | 85 | $date = $content['date']; |
86 | |||
87 | // is it a timestamp? | ||
88 | if (filter_var($date, FILTER_VALIDATE_INT) !== false) { | ||
89 | $date = '@'.$content['date']; | ||
90 | } | ||
91 | |||
92 | try { | ||
93 | $entry->setPublishedAt(new \DateTime($date)); | ||
94 | } catch (\Exception $e) { | ||
95 | $this->logger->warning('Error while defining date', ['e' => $e, 'url' => $url, 'date' => $content['date']]); | ||
96 | } | ||
81 | } | 97 | } |
82 | 98 | ||
83 | if (!empty($content['authors'])) { | 99 | if (!empty($content['authors'])) { |
@@ -97,12 +113,12 @@ class ContentProxy | |||
97 | $entry->setDomainName($domainName); | 113 | $entry->setDomainName($domainName); |
98 | } | 114 | } |
99 | 115 | ||
100 | if (isset($content['open_graph']['og_image']) && $content['open_graph']['og_image']) { | 116 | if (!empty($content['open_graph']['og_image'])) { |
101 | $entry->setPreviewPicture($content['open_graph']['og_image']); | 117 | $entry->setPreviewPicture($content['open_graph']['og_image']); |
102 | } | 118 | } |
103 | 119 | ||
104 | // if content is an image define as a preview too | 120 | // if content is an image define as a preview too |
105 | if (isset($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { | 121 | if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { |
106 | $entry->setPreviewPicture($content['url']); | 122 | $entry->setPreviewPicture($content['url']); |
107 | } | 123 | } |
108 | 124 | ||
@@ -128,6 +144,6 @@ class ContentProxy | |||
128 | */ | 144 | */ |
129 | private function validateContent(array $content) | 145 | private function validateContent(array $content) |
130 | { | 146 | { |
131 | return isset($content['title']) && isset($content['html']) && isset($content['url']) && isset($content['language']) && isset($content['content_type']); | 147 | return !empty($content['title']) && !empty($content['html']) && !empty($content['url']); |
132 | } | 148 | } |
133 | } | 149 | } |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig index 174b7b54..b64e1436 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig | |||
@@ -1,18 +1,28 @@ | |||
1 | <div class="card"> | 1 | <div class="card-stacked"> |
2 | <div class="card-stacked"> | 2 | <div class="preview">{% if entry.previewPicture is not null %}<img src="{{ entry.previewPicture }}" />{% endif %}</div> |
3 | <div class="card-content"> | 3 | <div class="card-content"> |
4 | <span class="card-title dot-ellipsis dot-resize-update"> | 4 | <span class="card-title dot-ellipsis dot-resize-update"> |
5 | <a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title | striptags | e('html_attr') }}"> | 5 | <a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title | striptags | e('html_attr') }}"> |
6 | {{ entry.title| striptags | truncate(120, true, '…') | raw }} | 6 | {{ entry.title| striptags | truncate(120, true, '…') | raw }} |
7 | </a> | 7 | </a> |
8 | </span> | 8 | </span> |
9 | <ul class="tools-list right"> | 9 | |
10 | <li> | 10 | <div class="metadata"> |
11 | <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a> | 11 | <a href="{{ entry.url|e }}" class="grey-text domain" target="_blank" title="{{ entry.domainName|removeWww }}"> |
12 | <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a> | 12 | <span>{{ entry.domainName|removeWww }}</span> |
13 | <a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a> | 13 | </a> |
14 | </li> | 14 | {% for tag in entry.tags | slice(0, 3) %} |
15 | </ul> | 15 | <span class="chip hide-on-med-and-down"> |
16 | <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> | ||
17 | </span> | ||
18 | {% endfor %} | ||
16 | </div> | 19 | </div> |
17 | </div> | 20 | </div> |
21 | <ul class="tools-list hide-on-small-only"> | ||
22 | <li> | ||
23 | <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a> | ||
24 | <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a> | ||
25 | <a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a> | ||
26 | </li> | ||
27 | </ul> | ||
18 | </div> | 28 | </div> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig index b2d91c9c..6f657b18 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig | |||
@@ -24,9 +24,9 @@ | |||
24 | </div> | 24 | </div> |
25 | 25 | ||
26 | <br /> | 26 | <br /> |
27 | <ul class="row data"> | 27 | <ul class="{% if listMode == 1 %}collection{% else %}row data{% endif %}"> |
28 | {% for entry in entries %} | 28 | {% for entry in entries %} |
29 | <li id="entry-{{ entry.id|e }}" class="col {% if listMode == 0 %}l3 m6{% endif %} s12"> | 29 | <li id="entry-{{ entry.id|e }}" class="col {% if listMode == 0 %}l3 m6{% else %}collection-item{% endif %} s12"> |
30 | {% if listMode == 1 %} | 30 | {% if listMode == 1 %} |
31 | {% include "@WallabagCore/themes/material/Entry/_card_list.html.twig" with {'entry': entry} only %} | 31 | {% include "@WallabagCore/themes/material/Entry/_card_list.html.twig" with {'entry': entry} only %} |
32 | {% elseif entry.previewPicture is null %} | 32 | {% elseif entry.previewPicture is null %} |