diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-09-25 13:29:36 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-10-15 09:08:46 +0200 |
commit | 4cf3564d28dc8e4d08a3e64f09ad045ffbde97ae (patch) | |
tree | 8f8ef095cdfea3b35953417fd3d8bb6cdbc7cb46 /application | |
parent | f34554c6c2cd8fe99fe2e8907bfc196a4884416a (diff) | |
download | Shaarli-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.php | 3 | ||||
-rw-r--r-- | application/container/ContainerBuilder.php | 5 | ||||
-rw-r--r-- | application/container/ShaarliContainer.php | 2 | ||||
-rw-r--r-- | application/front/controller/admin/ManageShaareController.php | 36 | ||||
-rw-r--r-- | application/front/controller/admin/MetadataController.php | 29 | ||||
-rw-r--r-- | application/http/MetadataRetriever.php | 68 |
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; | |||
14 | use Shaarli\Front\Controller\Visitor\ErrorNotFoundController; | 14 | use Shaarli\Front\Controller\Visitor\ErrorNotFoundController; |
15 | use Shaarli\History; | 15 | use Shaarli\History; |
16 | use Shaarli\Http\HttpAccess; | 16 | use Shaarli\Http\HttpAccess; |
17 | use Shaarli\Http\MetadataRetriever; | ||
17 | use Shaarli\Netscape\NetscapeBookmarkUtils; | 18 | use Shaarli\Netscape\NetscapeBookmarkUtils; |
18 | use Shaarli\Plugin\PluginManager; | 19 | use Shaarli\Plugin\PluginManager; |
19 | use Shaarli\Render\PageBuilder; | 20 | use 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; | |||
10 | use Shaarli\Formatter\FormatterFactory; | 10 | use Shaarli\Formatter\FormatterFactory; |
11 | use Shaarli\History; | 11 | use Shaarli\History; |
12 | use Shaarli\Http\HttpAccess; | 12 | use Shaarli\Http\HttpAccess; |
13 | use Shaarli\Http\MetadataRetriever; | ||
13 | use Shaarli\Netscape\NetscapeBookmarkUtils; | 14 | use Shaarli\Netscape\NetscapeBookmarkUtils; |
14 | use Shaarli\Plugin\PluginManager; | 15 | use Shaarli\Plugin\PluginManager; |
15 | use Shaarli\Render\PageBuilder; | 16 | use 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 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin; | ||
6 | |||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Controller used to retrieve/update bookmark's metadata. | ||
12 | */ | ||
13 | class 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 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Http; | ||
6 | |||
7 | use Shaarli\Config\ConfigManager; | ||
8 | |||
9 | /** | ||
10 | * HTTP Tool used to extract metadata from external URL (title, description, etc.). | ||
11 | */ | ||
12 | class 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 | } | ||