aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2020-09-25 13:29:36 +0200
committerArthurHoaro <arthur@hoa.ro>2020-10-15 09:08:46 +0200
commit4cf3564d28dc8e4d08a3e64f09ad045ffbde97ae (patch)
tree8f8ef095cdfea3b35953417fd3d8bb6cdbc7cb46 /application
parentf34554c6c2cd8fe99fe2e8907bfc196a4884416a (diff)
downloadShaarli-4cf3564d28dc8e4d08a3e64f09ad045ffbde97ae.tar.gz
Shaarli-4cf3564d28dc8e4d08a3e64f09ad045ffbde97ae.tar.zst
Shaarli-4cf3564d28dc8e4d08a3e64f09ad045ffbde97ae.zip
Add a setting to retrieve bookmark metadata asynchrounously
- There is a new standalone script (metadata.js) which requests a new controller to get bookmark metadata and fill the form async - This feature is enabled with the new setting: general.enable_async_metadata (enabled by default) - general.retrieve_description is now enabled by default - A small rotating loader animation has a been added to bookmark inputs when metadata is being retrieved (default template) - Custom JS htmlentities has been removed and mathiasbynens/he library is used instead Fixes #1563
Diffstat (limited to 'application')
-rw-r--r--application/config/ConfigManager.php3
-rw-r--r--application/container/ContainerBuilder.php5
-rw-r--r--application/container/ShaarliContainer.php2
-rw-r--r--application/front/controller/admin/ManageShaareController.php36
-rw-r--r--application/front/controller/admin/MetadataController.php29
-rw-r--r--application/http/MetadataRetriever.php68
6 files changed, 118 insertions, 25 deletions
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 4c98be30..fb085023 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -366,7 +366,8 @@ class ConfigManager
366 $this->setEmpty('general.links_per_page', 20); 366 $this->setEmpty('general.links_per_page', 20);
367 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); 367 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
368 $this->setEmpty('general.default_note_title', 'Note: '); 368 $this->setEmpty('general.default_note_title', 'Note: ');
369 $this->setEmpty('general.retrieve_description', false); 369 $this->setEmpty('general.retrieve_description', true);
370 $this->setEmpty('general.enable_async_metadata', true);
370 371
371 $this->setEmpty('updates.check_updates', false); 372 $this->setEmpty('updates.check_updates', false);
372 $this->setEmpty('updates.check_updates_branch', 'stable'); 373 $this->setEmpty('updates.check_updates_branch', 'stable');
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php
index c21d58dd..fd94a1c3 100644
--- a/application/container/ContainerBuilder.php
+++ b/application/container/ContainerBuilder.php
@@ -14,6 +14,7 @@ use Shaarli\Front\Controller\Visitor\ErrorController;
14use Shaarli\Front\Controller\Visitor\ErrorNotFoundController; 14use Shaarli\Front\Controller\Visitor\ErrorNotFoundController;
15use Shaarli\History; 15use Shaarli\History;
16use Shaarli\Http\HttpAccess; 16use Shaarli\Http\HttpAccess;
17use Shaarli\Http\MetadataRetriever;
17use Shaarli\Netscape\NetscapeBookmarkUtils; 18use Shaarli\Netscape\NetscapeBookmarkUtils;
18use Shaarli\Plugin\PluginManager; 19use Shaarli\Plugin\PluginManager;
19use Shaarli\Render\PageBuilder; 20use Shaarli\Render\PageBuilder;
@@ -90,6 +91,10 @@ class ContainerBuilder
90 ); 91 );
91 }; 92 };
92 93
94 $container['metadataRetriever'] = function (ShaarliContainer $container): MetadataRetriever {
95 return new MetadataRetriever($container->conf, $container->httpAccess);
96 };
97
93 $container['pageBuilder'] = function (ShaarliContainer $container): PageBuilder { 98 $container['pageBuilder'] = function (ShaarliContainer $container): PageBuilder {
94 return new PageBuilder( 99 return new PageBuilder(
95 $container->conf, 100 $container->conf,
diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php
index 66e669aa..3a7c238f 100644
--- a/application/container/ShaarliContainer.php
+++ b/application/container/ShaarliContainer.php
@@ -10,6 +10,7 @@ use Shaarli\Feed\FeedBuilder;
10use Shaarli\Formatter\FormatterFactory; 10use Shaarli\Formatter\FormatterFactory;
11use Shaarli\History; 11use Shaarli\History;
12use Shaarli\Http\HttpAccess; 12use Shaarli\Http\HttpAccess;
13use Shaarli\Http\MetadataRetriever;
13use Shaarli\Netscape\NetscapeBookmarkUtils; 14use Shaarli\Netscape\NetscapeBookmarkUtils;
14use Shaarli\Plugin\PluginManager; 15use Shaarli\Plugin\PluginManager;
15use Shaarli\Render\PageBuilder; 16use Shaarli\Render\PageBuilder;
@@ -35,6 +36,7 @@ use Slim\Container;
35 * @property History $history 36 * @property History $history
36 * @property HttpAccess $httpAccess 37 * @property HttpAccess $httpAccess
37 * @property LoginManager $loginManager 38 * @property LoginManager $loginManager
39 * @property MetadataRetriever $metadataRetriever
38 * @property NetscapeBookmarkUtils $netscapeBookmarkUtils 40 * @property NetscapeBookmarkUtils $netscapeBookmarkUtils
39 * @property callable $notFoundHandler Overrides default Slim exception display 41 * @property callable $notFoundHandler Overrides default Slim exception display
40 * @property PageBuilder $pageBuilder 42 * @property PageBuilder $pageBuilder
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php
index bb083486..df2f1631 100644
--- a/application/front/controller/admin/ManageShaareController.php
+++ b/application/front/controller/admin/ManageShaareController.php
@@ -53,36 +53,22 @@ class ManageShaareController extends ShaarliAdminController
53 53
54 // If this is an HTTP(S) link, we try go get the page to extract 54 // If this is an HTTP(S) link, we try go get the page to extract
55 // the title (otherwise we will to straight to the edit form.) 55 // the title (otherwise we will to straight to the edit form.)
56 if (empty($title) && strpos(get_url_scheme($url) ?: '', 'http') !== false) { 56 if (true !== $this->container->conf->get('general.enable_async_metadata', true)
57 $retrieveDescription = $this->container->conf->get('general.retrieve_description'); 57 && empty($title)
58 // Short timeout to keep the application responsive 58 && strpos(get_url_scheme($url) ?: '', 'http') !== false
59 // The callback will fill $charset and $title with data from the downloaded page. 59 ) {
60 $this->container->httpAccess->getHttpResponse( 60 $metadata = $this->container->metadataRetriever->retrieve($url);
61 $url,
62 $this->container->conf->get('general.download_timeout', 30),
63 $this->container->conf->get('general.download_max_size', 4194304),
64 $this->container->httpAccess->getCurlDownloadCallback(
65 $charset,
66 $title,
67 $description,
68 $tags,
69 $retrieveDescription
70 )
71 );
72 if (! empty($title) && strtolower($charset) !== 'utf-8' && mb_check_encoding($charset)) {
73 $title = mb_convert_encoding($title, 'utf-8', $charset);
74 }
75 } 61 }
76 62
77 if (empty($url) && empty($title)) { 63 if (empty($url)) {
78 $title = $this->container->conf->get('general.default_note_title', t('Note: ')); 64 $metadata['title'] = $this->container->conf->get('general.default_note_title', t('Note: '));
79 } 65 }
80 66
81 $link = [ 67 $link = [
82 'title' => $title, 68 'title' => $title ?? $metadata['title'] ?? '',
83 'url' => $url ?? '', 69 'url' => $url ?? '',
84 'description' => $description ?? '', 70 'description' => $description ?? $metadata['description'] ?? '',
85 'tags' => $tags ?? '', 71 'tags' => $tags ?? $metadata['tags'] ?? '',
86 'private' => $private, 72 'private' => $private,
87 ]; 73 ];
88 } else { 74 } else {
@@ -352,6 +338,8 @@ class ManageShaareController extends ShaarliAdminController
352 'source' => $request->getParam('source') ?? '', 338 'source' => $request->getParam('source') ?? '',
353 'tags' => $tags, 339 'tags' => $tags,
354 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false), 340 'default_private_links' => $this->container->conf->get('privacy.default_private_links', false),
341 'async_metadata' => $this->container->conf->get('general.enable_async_metadata', true),
342 'retrieve_description' => $this->container->conf->get('general.retrieve_description', false),
355 ]); 343 ]);
356 344
357 $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK); 345 $this->executePageHooks('render_editlink', $data, TemplatePage::EDIT_LINK);
diff --git a/application/front/controller/admin/MetadataController.php b/application/front/controller/admin/MetadataController.php
new file mode 100644
index 00000000..ff845944
--- /dev/null
+++ b/application/front/controller/admin/MetadataController.php
@@ -0,0 +1,29 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin;
6
7use Slim\Http\Request;
8use Slim\Http\Response;
9
10/**
11 * Controller used to retrieve/update bookmark's metadata.
12 */
13class MetadataController extends ShaarliAdminController
14{
15 /**
16 * GET /admin/metadata/{url} - Attempt to retrieve the bookmark title from provided URL.
17 */
18 public function ajaxRetrieveTitle(Request $request, Response $response): Response
19 {
20 $url = $request->getParam('url');
21
22 // Only try to extract metadata from URL with HTTP(s) scheme
23 if (!empty($url) && strpos(get_url_scheme($url) ?: '', 'http') !== false) {
24 return $response->withJson($this->container->metadataRetriever->retrieve($url));
25 }
26
27 return $response->withJson([]);
28 }
29}
diff --git a/application/http/MetadataRetriever.php b/application/http/MetadataRetriever.php
new file mode 100644
index 00000000..2ca982e2
--- /dev/null
+++ b/application/http/MetadataRetriever.php
@@ -0,0 +1,68 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Http;
6
7use Shaarli\Config\ConfigManager;
8
9/**
10 * HTTP Tool used to extract metadata from external URL (title, description, etc.).
11 */
12class MetadataRetriever
13{
14 /** @var ConfigManager */
15 protected $conf;
16
17 /** @var HttpAccess */
18 protected $httpAccess;
19
20 public function __construct(ConfigManager $conf, HttpAccess $httpAccess)
21 {
22 $this->conf = $conf;
23 $this->httpAccess = $httpAccess;
24 }
25
26 /**
27 * Retrieve metadata for given URL.
28 *
29 * @return array [
30 * 'title' => <remote title>,
31 * 'description' => <remote description>,
32 * 'tags' => <remote keywords>,
33 * ]
34 */
35 public function retrieve(string $url): array
36 {
37 $charset = null;
38 $title = null;
39 $description = null;
40 $tags = null;
41 $retrieveDescription = $this->conf->get('general.retrieve_description');
42
43 // Short timeout to keep the application responsive
44 // The callback will fill $charset and $title with data from the downloaded page.
45 $this->httpAccess->getHttpResponse(
46 $url,
47 $this->conf->get('general.download_timeout', 30),
48 $this->conf->get('general.download_max_size', 4194304),
49 $this->httpAccess->getCurlDownloadCallback(
50 $charset,
51 $title,
52 $description,
53 $tags,
54 $retrieveDescription
55 )
56 );
57
58 if (!empty($title) && strtolower($charset) !== 'utf-8') {
59 $title = mb_convert_encoding($title, 'utf-8', $charset);
60 }
61
62 return [
63 'title' => $title,
64 'description' => $description,
65 'tags' => $tags,
66 ];
67 }
68}