aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/bookmark/LinkUtils.php85
-rw-r--r--application/config/ConfigManager.php1
-rw-r--r--doc/md/Shaarli-configuration.md2
-rw-r--r--inc/languages/fr/LC_MESSAGES/shaarli.po128
-rw-r--r--index.php5
-rw-r--r--tests/bookmark/LinkUtilsTest.php204
-rw-r--r--tpl/default/configure.html16
-rw-r--r--tpl/vintage/configure.html8
8 files changed, 374 insertions, 75 deletions
diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php
index 35a5b290..77eb2d95 100644
--- a/application/bookmark/LinkUtils.php
+++ b/application/bookmark/LinkUtils.php
@@ -7,13 +7,25 @@ use Shaarli\Bookmark\LinkDB;
7 * 7 *
8 * @param string $charset to extract from the downloaded page (reference) 8 * @param string $charset to extract from the downloaded page (reference)
9 * @param string $title to extract from the downloaded page (reference) 9 * @param string $title to extract from the downloaded page (reference)
10 * @param string $description to extract from the downloaded page (reference)
11 * @param string $keywords to extract from the downloaded page (reference)
12 * @param bool $retrieveDescription Automatically tries to retrieve description and keywords from HTML content
10 * @param string $curlGetInfo Optionally overrides curl_getinfo function 13 * @param string $curlGetInfo Optionally overrides curl_getinfo function
11 * 14 *
12 * @return Closure 15 * @return Closure
13 */ 16 */
14function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_getinfo') 17function get_curl_download_callback(
15{ 18 &$charset,
19 &$title,
20 &$description,
21 &$keywords,
22 $retrieveDescription,
23 $curlGetInfo = 'curl_getinfo'
24) {
16 $isRedirected = false; 25 $isRedirected = false;
26 $currentChunk = 0;
27 $foundChunk = null;
28
17 /** 29 /**
18 * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download). 30 * cURL callback function for CURLOPT_WRITEFUNCTION (called during the download).
19 * 31 *
@@ -25,7 +37,18 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
25 * 37 *
26 * @return int|bool length of $data or false if we need to stop the download 38 * @return int|bool length of $data or false if we need to stop the download
27 */ 39 */
28 return function (&$ch, $data) use ($curlGetInfo, &$charset, &$title, &$isRedirected) { 40 return function (&$ch, $data) use (
41 $retrieveDescription,
42 $curlGetInfo,
43 &$charset,
44 &$title,
45 &$description,
46 &$keywords,
47 &$isRedirected,
48 &$currentChunk,
49 &$foundChunk
50 ) {
51 $currentChunk++;
29 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE); 52 $responseCode = $curlGetInfo($ch, CURLINFO_RESPONSE_CODE);
30 if (!empty($responseCode) && in_array($responseCode, [301, 302])) { 53 if (!empty($responseCode) && in_array($responseCode, [301, 302])) {
31 $isRedirected = true; 54 $isRedirected = true;
@@ -50,9 +73,34 @@ function get_curl_download_callback(&$charset, &$title, $curlGetInfo = 'curl_get
50 } 73 }
51 if (empty($title)) { 74 if (empty($title)) {
52 $title = html_extract_title($data); 75 $title = html_extract_title($data);
76 $foundChunk = ! empty($title) ? $currentChunk : $foundChunk;
77 }
78 if ($retrieveDescription && empty($description)) {
79 $description = html_extract_tag('description', $data);
80 $foundChunk = ! empty($description) ? $currentChunk : $foundChunk;
53 } 81 }
82 if ($retrieveDescription && empty($keywords)) {
83 $keywords = html_extract_tag('keywords', $data);
84 if (! empty($keywords)) {
85 $foundChunk = $currentChunk;
86 // Keywords use the format tag1, tag2 multiple words, tag
87 // So we format them to match Shaarli's separator and glue multiple words with '-'
88 $keywords = implode(' ', array_map(function($keyword) {
89 return implode('-', preg_split('/\s+/', trim($keyword)));
90 }, explode(',', $keywords)));
91 }
92 }
93
54 // We got everything we want, stop the download. 94 // We got everything we want, stop the download.
55 if (!empty($responseCode) && !empty($contentType) && !empty($charset) && !empty($title)) { 95 // If we already found either the title, description or keywords,
96 // it's highly unlikely that we'll found the other metas further than
97 // in the same chunk of data or the next one. So we also stop the download after that.
98 if ((!empty($responseCode) && !empty($contentType) && !empty($charset)) && $foundChunk !== null
99 && (! $retrieveDescription
100 || $foundChunk < $currentChunk
101 || (!empty($title) && !empty($description) && !empty($keywords))
102 )
103 ) {
56 return false; 104 return false;
57 } 105 }
58 106
@@ -111,6 +159,35 @@ function html_extract_charset($html)
111} 159}
112 160
113/** 161/**
162 * Extract meta tag from HTML content in either:
163 * - OpenGraph: <meta property="og:[tag]" ...>
164 * - Meta tag: <meta name="[tag]" ...>
165 *
166 * @param string $tag Name of the tag to retrieve.
167 * @param string $html HTML content where to look for charset.
168 *
169 * @return bool|string Charset string if found, false otherwise.
170 */
171function html_extract_tag($tag, $html)
172{
173 $propertiesKey = ['property', 'name', 'itemprop'];
174 $properties = implode('|', $propertiesKey);
175 // Try to retrieve OpenGraph image.
176 $ogRegex = '#<meta[^>]+(?:'. $properties .')=["\']?(?:og:)?'. $tag .'["\'\s][^>]*content=["\']?(.*?)["\'/>]#';
177 // If the attributes are not in the order property => content (e.g. Github)
178 // New regex to keep this readable... more or less.
179 $ogRegexReverse = '#<meta[^>]+content=["\']([^"\']+)[^>]+(?:'. $properties .')=["\']?(?:og)?:'. $tag .'["\'\s/>]#';
180
181 if (preg_match($ogRegex, $html, $matches) > 0
182 || preg_match($ogRegexReverse, $html, $matches) > 0
183 ) {
184 return $matches[1];
185 }
186
187 return false;
188}
189
190/**
114 * Count private links in given linklist. 191 * Count private links in given linklist.
115 * 192 *
116 * @param array|Countable $links Linklist. 193 * @param array|Countable $links Linklist.
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 30993928..c95e6800 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -365,6 +365,7 @@ class ConfigManager
365 $this->setEmpty('general.links_per_page', 20); 365 $this->setEmpty('general.links_per_page', 20);
366 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS); 366 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
367 $this->setEmpty('general.default_note_title', 'Note: '); 367 $this->setEmpty('general.default_note_title', 'Note: ');
368 $this->setEmpty('general.retrieve_description', false);
368 369
369 $this->setEmpty('updates.check_updates', false); 370 $this->setEmpty('updates.check_updates', false);
370 $this->setEmpty('updates.check_updates_branch', 'stable'); 371 $this->setEmpty('updates.check_updates_branch', 'stable');
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md
index a931ab1e..664e36dd 100644
--- a/doc/md/Shaarli-configuration.md
+++ b/doc/md/Shaarli-configuration.md
@@ -56,6 +56,8 @@ _These settings should not be edited_
56- **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). 56- **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php).
57- **enabled_plugins**: List of enabled plugins. 57- **enabled_plugins**: List of enabled plugins.
58- **default_note_title**: Default title of a new note. 58- **default_note_title**: Default title of a new note.
59- **retrieve_description** (boolean): If set to true, for every new links Shaarli will try
60to retrieve the description and keywords from the HTML meta tags.
59 61
60### Security 62### Security
61 63
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po
index c2c73b29..611296f1 100644
--- a/inc/languages/fr/LC_MESSAGES/shaarli.po
+++ b/inc/languages/fr/LC_MESSAGES/shaarli.po
@@ -1,8 +1,8 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Project-Id-Version: Shaarli\n" 3"Project-Id-Version: Shaarli\n"
4"POT-Creation-Date: 2019-05-25 16:37+0200\n" 4"POT-Creation-Date: 2019-07-06 12:14+0200\n"
5"PO-Revision-Date: 2019-05-25 16:37+0200\n" 5"PO-Revision-Date: 2019-07-06 12:17+0200\n"
6"Last-Translator: \n" 6"Last-Translator: \n"
7"Language-Team: Shaarli\n" 7"Language-Team: Shaarli\n"
8"Language: fr_FR\n" 8"Language: fr_FR\n"
@@ -252,7 +252,7 @@ msgstr "404 Introuvable"
252msgid "Couldn't retrieve updater class methods." 252msgid "Couldn't retrieve updater class methods."
253msgstr "Impossible de récupérer les méthodes de la classe Updater." 253msgstr "Impossible de récupérer les méthodes de la classe Updater."
254 254
255#: application/updater/Updater.php:526 index.php:1033 255#: application/updater/Updater.php:526 index.php:1034
256msgid "" 256msgid ""
257"You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update" 257"You have enabled or changed thumbnails mode. <a href=\"?do=thumbs_update"
258"\">Please synchronize them</a>." 258"\">Please synchronize them</a>."
@@ -337,8 +337,8 @@ msgid "You are not supposed to change a password on an Open Shaarli."
337msgstr "" 337msgstr ""
338"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert." 338"Vous n'êtes pas censé modifier le mot de passe d'un Shaarli en mode ouvert."
339 339
340#: index.php:957 index.php:1007 index.php:1092 index.php:1122 index.php:1232 340#: index.php:957 index.php:1007 index.php:1094 index.php:1124 index.php:1234
341#: index.php:1279 341#: index.php:1281
342msgid "Wrong token." 342msgid "Wrong token."
343msgstr "Jeton invalide." 343msgstr "Jeton invalide."
344 344
@@ -356,64 +356,64 @@ msgstr "Votre mot de passe a été modifié"
356msgid "Change password" 356msgid "Change password"
357msgstr "Modifier le mot de passe" 357msgstr "Modifier le mot de passe"
358 358
359#: index.php:1053 359#: index.php:1054
360msgid "Configuration was saved." 360msgid "Configuration was saved."
361msgstr "La configuration a été sauvegardée." 361msgstr "La configuration a été sauvegardée."
362 362
363#: index.php:1076 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 363#: index.php:1078 tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24
364msgid "Configure" 364msgid "Configure"
365msgstr "Configurer" 365msgstr "Configurer"
366 366
367#: index.php:1086 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 367#: index.php:1088 tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
368#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 368#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36
369msgid "Manage tags" 369msgid "Manage tags"
370msgstr "Gérer les tags" 370msgstr "Gérer les tags"
371 371
372#: index.php:1105 372#: index.php:1107
373#, php-format 373#, php-format
374msgid "The tag was removed from %d link." 374msgid "The tag was removed from %d link."
375msgid_plural "The tag was removed from %d links." 375msgid_plural "The tag was removed from %d links."
376msgstr[0] "Le tag a été supprimé de %d lien." 376msgstr[0] "Le tag a été supprimé de %d lien."
377msgstr[1] "Le tag a été supprimé de %d liens." 377msgstr[1] "Le tag a été supprimé de %d liens."
378 378
379#: index.php:1106 379#: index.php:1108
380#, php-format 380#, php-format
381msgid "The tag was renamed in %d link." 381msgid "The tag was renamed in %d link."
382msgid_plural "The tag was renamed in %d links." 382msgid_plural "The tag was renamed in %d links."
383msgstr[0] "Le tag a été renommé dans %d lien." 383msgstr[0] "Le tag a été renommé dans %d lien."
384msgstr[1] "Le tag a été renommé dans %d liens." 384msgstr[1] "Le tag a été renommé dans %d liens."
385 385
386#: index.php:1113 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 386#: index.php:1115 tmp/addlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13
387msgid "Shaare a new link" 387msgid "Shaare a new link"
388msgstr "Partager un nouveau lien" 388msgstr "Partager un nouveau lien"
389 389
390#: index.php:1342 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 390#: index.php:1344 tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
391msgid "Edit" 391msgid "Edit"
392msgstr "Modifier" 392msgstr "Modifier"
393 393
394#: index.php:1342 index.php:1413 394#: index.php:1344 index.php:1416
395#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 395#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
396#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26 396#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:26
397msgid "Shaare" 397msgid "Shaare"
398msgstr "Shaare" 398msgstr "Shaare"
399 399
400#: index.php:1382 400#: index.php:1385
401msgid "Note: " 401msgid "Note: "
402msgstr "Note : " 402msgstr "Note : "
403 403
404#: index.php:1421 404#: index.php:1424
405msgid "Invalid link ID provided" 405msgid "Invalid link ID provided"
406msgstr "" 406msgstr ""
407 407
408#: index.php:1441 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65 408#: index.php:1444 tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:65
409msgid "Export" 409msgid "Export"
410msgstr "Exporter" 410msgstr "Exporter"
411 411
412#: index.php:1503 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 412#: index.php:1506 tmp/import.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83
413msgid "Import" 413msgid "Import"
414msgstr "Importer" 414msgstr "Importer"
415 415
416#: index.php:1513 416#: index.php:1516
417#, php-format 417#, php-format
418msgid "" 418msgid ""
419"The file you are trying to upload is probably bigger than what this " 419"The file you are trying to upload is probably bigger than what this "
@@ -423,20 +423,20 @@ msgstr ""
423"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus " 423"le serveur web peut accepter (%s). Merci de l'envoyer en parties plus "
424"légères." 424"légères."
425 425
426#: index.php:1558 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 426#: index.php:1561 tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26
427#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 427#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22
428msgid "Plugin administration" 428msgid "Plugin administration"
429msgstr "Administration des plugins" 429msgstr "Administration des plugins"
430 430
431#: index.php:1612 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 431#: index.php:1615 tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14
432msgid "Thumbnails update" 432msgid "Thumbnails update"
433msgstr "Mise à jour des miniatures" 433msgstr "Mise à jour des miniatures"
434 434
435#: index.php:1778 435#: index.php:1781
436msgid "Search: " 436msgid "Search: "
437msgstr "Recherche : " 437msgstr "Recherche : "
438 438
439#: index.php:1821 439#: index.php:1824
440#, php-format 440#, php-format
441msgid "" 441msgid ""
442"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the " 442"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
@@ -455,7 +455,7 @@ msgstr ""
455"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " 455"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
456"adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>" 456"adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>"
457 457
458#: index.php:1831 458#: index.php:1834
459msgid "Click to try again." 459msgid "Click to try again."
460msgstr "Cliquer ici pour réessayer." 460msgstr "Cliquer ici pour réessayer."
461 461
@@ -592,7 +592,7 @@ msgstr "Mauvaise réponse du hub %s"
592msgid "Enable PubSubHubbub feed publishing." 592msgid "Enable PubSubHubbub feed publishing."
593msgstr "Active la publication de flux vers PubSubHubbub." 593msgstr "Active la publication de flux vers PubSubHubbub."
594 594
595#: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:68 595#: plugins/qrcode/qrcode.php:72 plugins/wallabag/wallabag.php:68
596msgid "For each link, add a QRCode icon." 596msgid "For each link, add a QRCode icon."
597msgstr "Pour chaque lien, ajouter une icône de QRCode." 597msgstr "Pour chaque lien, ajouter une icône de QRCode."
598 598
@@ -679,6 +679,34 @@ msgstr "Vous pouvez aussi modifier les tags dans la"
679msgid "tag list" 679msgid "tag list"
680msgstr "liste des tags" 680msgstr "liste des tags"
681 681
682#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:143
683#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312
684#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
685msgid "All"
686msgstr "Tous"
687
688#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:147
689#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:316
690msgid "Only common media hosts"
691msgstr "Seulement les hébergeurs de média connus"
692
693#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:151
694#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:320
695msgid "None"
696msgstr "Aucune"
697
698#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:158
699#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:297
700msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
701msgstr ""
702"Vous devez activer l'extension <code>php-gd</code> pour utiliser les "
703"miniatures."
704
705#: tmp/configure.90100d2eaf5d3705e14b9b4f78ecddc9.rtpl.php:162
706#| msgid "Enable thumbnails"
707msgid "Synchonize thumbnails"
708msgstr ""
709
682#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 710#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29
683msgid "title" 711msgid "title"
684msgstr "titre" 712msgstr "titre"
@@ -762,50 +790,41 @@ msgid "Notify me when a new release is ready"
762msgstr "Me notifier lorsqu'une nouvelle version est disponible" 790msgstr "Me notifier lorsqu'une nouvelle version est disponible"
763 791
764#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247 792#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:247
793msgid "Automatically retrieve description for new bookmarks"
794msgstr "Récupérer automatiquement la description"
795
796#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248
797msgid "Shaarli will try to retrieve the description from meta HTML headers"
798msgstr ""
799"Shaarli essaiera de récupérer la description depuis les balises HTML meta "
800"dans les entêtes"
801
802#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263
765#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 803#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169
766msgid "Enable REST API" 804msgid "Enable REST API"
767msgstr "Activer l'API REST" 805msgstr "Activer l'API REST"
768 806
769#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:248 807#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:264
770#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170 808#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:170
771msgid "Allow third party software to use Shaarli such as mobile application" 809msgid "Allow third party software to use Shaarli such as mobile application"
772msgstr "" 810msgstr ""
773"Permet aux applications tierces d'utiliser Shaarli, par exemple les " 811"Permet aux applications tierces d'utiliser Shaarli, par exemple les "
774"applications mobiles" 812"applications mobiles"
775 813
776#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:263 814#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:279
777msgid "API secret" 815msgid "API secret"
778msgstr "Clé d'API secrète" 816msgstr "Clé d'API secrète"
779 817
780#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:277 818#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:293
781msgid "Enable thumbnails" 819msgid "Enable thumbnails"
782msgstr "Activer les miniatures" 820msgstr "Activer les miniatures"
783 821
784#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:281 822#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:301
785msgid "You need to enable the extension <code>php-gd</code> to use thumbnails."
786msgstr ""
787"Vous devez activer l'extension <code>php-gd</code> pour utiliser les "
788"miniatures."
789
790#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:285
791#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 823#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56
792msgid "Synchronize thumbnails" 824msgid "Synchronize thumbnails"
793msgstr "Synchroniser les miniatures" 825msgstr "Synchroniser les miniatures"
794 826
795#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:296 827#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328
796#: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:31
797msgid "All"
798msgstr "Tous"
799
800#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:300
801msgid "Only common media hosts"
802msgstr "Seulement les hébergeurs de média connus"
803
804#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:304
805msgid "None"
806msgstr "Aucune"
807
808#: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:312
809#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72 828#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:72
810#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 829#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139
811#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 830#: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199
@@ -1149,17 +1168,13 @@ msgstr "Déconnexion"
1149 1168
1150#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150 1169#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:150
1151#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:150 1170#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:150
1152#, fuzzy
1153#| msgid "Public"
1154msgid "Set public" 1171msgid "Set public"
1155msgstr "Publics" 1172msgstr "Rendre public"
1156 1173
1157#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155 1174#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:155
1158#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155 1175#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:155
1159#, fuzzy
1160#| msgid "Private"
1161msgid "Set private" 1176msgid "Set private"
1162msgstr "Privé" 1177msgstr "Rendre privé"
1163 1178
1164#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187 1179#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:187
1165#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:187 1180#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:187
@@ -1409,11 +1424,6 @@ msgstr ""
1409"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " 1424"Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « "
1410"Ajouter aux favoris »" 1425"Ajouter aux favoris »"
1411 1426
1412#, fuzzy
1413#~| msgid "Enable thumbnails"
1414#~ msgid "Synchonize thumbnails"
1415#~ msgstr "Activer les miniatures"
1416
1417#~ msgid "" 1427#~ msgid ""
1418#~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this " 1428#~ "You need to browse your Shaarli over <strong>HTTPS</strong> to use this "
1419#~ "functionality." 1429#~ "functionality."
diff --git a/index.php b/index.php
index a14616ed..957d8d9a 100644
--- a/index.php
+++ b/index.php
@@ -1015,6 +1015,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1015 $conf->set('general.timezone', $tz); 1015 $conf->set('general.timezone', $tz);
1016 $conf->set('general.title', escape($_POST['title'])); 1016 $conf->set('general.title', escape($_POST['title']));
1017 $conf->set('general.header_link', escape($_POST['titleLink'])); 1017 $conf->set('general.header_link', escape($_POST['titleLink']));
1018 $conf->set('general.retrieve_description', !empty($_POST['retrieveDescription']));
1018 $conf->set('resource.theme', escape($_POST['theme'])); 1019 $conf->set('resource.theme', escape($_POST['theme']));
1019 $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); 1020 $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection']));
1020 $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); 1021 $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault']));
@@ -1063,6 +1064,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1063 ); 1064 );
1064 $PAGE->assign('continents', $continents); 1065 $PAGE->assign('continents', $continents);
1065 $PAGE->assign('cities', $cities); 1066 $PAGE->assign('cities', $cities);
1067 $PAGE->assign('retrieve_description', $conf->get('general.retrieve_description'));
1066 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); 1068 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
1067 $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); 1069 $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
1068 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); 1070 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
@@ -1364,13 +1366,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1364 // If this is an HTTP(S) link, we try go get the page to extract 1366 // If this is an HTTP(S) link, we try go get the page to extract
1365 // the title (otherwise we will to straight to the edit form.) 1367 // the title (otherwise we will to straight to the edit form.)
1366 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { 1368 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
1369 $retrieveDescription = $conf->get('general.retrieve_description');
1367 // Short timeout to keep the application responsive 1370 // Short timeout to keep the application responsive
1368 // The callback will fill $charset and $title with data from the downloaded page. 1371 // The callback will fill $charset and $title with data from the downloaded page.
1369 get_http_response( 1372 get_http_response(
1370 $url, 1373 $url,
1371 $conf->get('general.download_timeout', 30), 1374 $conf->get('general.download_timeout', 30),
1372 $conf->get('general.download_max_size', 4194304), 1375 $conf->get('general.download_max_size', 4194304),
1373 get_curl_download_callback($charset, $title) 1376 get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription)
1374 ); 1377 );
1375 if (! empty($title) && strtolower($charset) != 'utf-8') { 1378 if (! empty($title) && strtolower($charset) != 'utf-8') {
1376 $title = mb_convert_encoding($title, 'utf-8', $charset); 1379 $title = mb_convert_encoding($title, 'utf-8', $charset);
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php
index 25fb3043..78cb8f2a 100644
--- a/tests/bookmark/LinkUtilsTest.php
+++ b/tests/bookmark/LinkUtilsTest.php
@@ -2,14 +2,16 @@
2 2
3namespace Shaarli\Bookmark; 3namespace Shaarli\Bookmark;
4 4
5use PHPUnit\Framework\TestCase;
5use ReferenceLinkDB; 6use ReferenceLinkDB;
7use Shaarli\Config\ConfigManager;
6 8
7require_once 'tests/utils/CurlUtils.php'; 9require_once 'tests/utils/CurlUtils.php';
8 10
9/** 11/**
10 * Class LinkUtilsTest. 12 * Class LinkUtilsTest.
11 */ 13 */
12class LinkUtilsTest extends \PHPUnit\Framework\TestCase 14class LinkUtilsTest extends TestCase
13{ 15{
14 /** 16 /**
15 * Test html_extract_title() when the title is found. 17 * Test html_extract_title() when the title is found.
@@ -76,11 +78,56 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
76 } 78 }
77 79
78 /** 80 /**
81 * Test html_extract_tag() when the tag <meta name= is found.
82 */
83 public function testHtmlExtractExistentNameTag()
84 {
85 $description = 'Bob and Alice share cookies.';
86 $html = '<html><meta>stuff2</meta><meta name="description" content="' . $description . '"/></html>';
87 $this->assertEquals($description, html_extract_tag('description', $html));
88 }
89
90 /**
91 * Test html_extract_tag() when the tag <meta name= is not found.
92 */
93 public function testHtmlExtractNonExistentNameTag()
94 {
95 $html = '<html><meta>stuff2</meta><meta name="image" content="img"/></html>';
96 $this->assertFalse(html_extract_tag('description', $html));
97 }
98
99 /**
100 * Test html_extract_tag() when the tag <meta property="og: is found.
101 */
102 public function testHtmlExtractExistentOgTag()
103 {
104 $description = 'Bob and Alice share cookies.';
105 $html = '<html><meta>stuff2</meta><meta property="og:description" content="' . $description . '"/></html>';
106 $this->assertEquals($description, html_extract_tag('description', $html));
107 }
108
109 /**
110 * Test html_extract_tag() when the tag <meta property="og: is not found.
111 */
112 public function testHtmlExtractNonExistentOgTag()
113 {
114 $html = '<html><meta>stuff2</meta><meta name="image" content="img"/></html>';
115 $this->assertFalse(html_extract_tag('description', $html));
116 }
117
118 /**
79 * Test the download callback with valid value 119 * Test the download callback with valid value
80 */ 120 */
81 public function testCurlDownloadCallbackOk() 121 public function testCurlDownloadCallbackOk()
82 { 122 {
83 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok'); 123 $callback = get_curl_download_callback(
124 $charset,
125 $title,
126 $desc,
127 $keywords,
128 false,
129 'ut_curl_getinfo_ok'
130 );
84 $data = [ 131 $data = [
85 'HTTP/1.1 200 OK', 132 'HTTP/1.1 200 OK',
86 'Server: GitHub.com', 133 'Server: GitHub.com',
@@ -90,7 +137,9 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
90 'end' => 'th=device-width">' 137 'end' => 'th=device-width">'
91 . '<title>Refactoring · GitHub</title>' 138 . '<title>Refactoring · GitHub</title>'
92 . '<link rel="search" type="application/opensea', 139 . '<link rel="search" type="application/opensea',
93 '<title>ignored</title>', 140 '<title>ignored</title>'
141 . '<meta name="description" content="desc" />'
142 . '<meta name="keywords" content="key1,key2" />',
94 ]; 143 ];
95 foreach ($data as $key => $line) { 144 foreach ($data as $key => $line) {
96 $ignore = null; 145 $ignore = null;
@@ -102,6 +151,8 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
102 } 151 }
103 $this->assertEquals('utf-8', $charset); 152 $this->assertEquals('utf-8', $charset);
104 $this->assertEquals('Refactoring · GitHub', $title); 153 $this->assertEquals('Refactoring · GitHub', $title);
154 $this->assertEmpty($desc);
155 $this->assertEmpty($keywords);
105 } 156 }
106 157
107 /** 158 /**
@@ -109,13 +160,22 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
109 */ 160 */
110 public function testCurlDownloadCallbackOkNoCharset() 161 public function testCurlDownloadCallbackOkNoCharset()
111 { 162 {
112 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset'); 163 $callback = get_curl_download_callback(
164 $charset,
165 $title,
166 $desc,
167 $keywords,
168 false,
169 'ut_curl_getinfo_no_charset'
170 );
113 $data = [ 171 $data = [
114 'HTTP/1.1 200 OK', 172 'HTTP/1.1 200 OK',
115 'end' => 'th=device-width">' 173 'end' => 'th=device-width">'
116 . '<title>Refactoring · GitHub</title>' 174 . '<title>Refactoring · GitHub</title>'
117 . '<link rel="search" type="application/opensea', 175 . '<link rel="search" type="application/opensea',
118 '<title>ignored</title>', 176 '<title>ignored</title>'
177 . '<meta name="description" content="desc" />'
178 . '<meta name="keywords" content="key1,key2" />',
119 ]; 179 ];
120 foreach ($data as $key => $line) { 180 foreach ($data as $key => $line) {
121 $ignore = null; 181 $ignore = null;
@@ -123,6 +183,8 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
123 } 183 }
124 $this->assertEmpty($charset); 184 $this->assertEmpty($charset);
125 $this->assertEquals('Refactoring · GitHub', $title); 185 $this->assertEquals('Refactoring · GitHub', $title);
186 $this->assertEmpty($desc);
187 $this->assertEmpty($keywords);
126 } 188 }
127 189
128 /** 190 /**
@@ -130,14 +192,23 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
130 */ 192 */
131 public function testCurlDownloadCallbackOkHtmlCharset() 193 public function testCurlDownloadCallbackOkHtmlCharset()
132 { 194 {
133 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset'); 195 $callback = get_curl_download_callback(
196 $charset,
197 $title,
198 $desc,
199 $keywords,
200 false,
201 'ut_curl_getinfo_no_charset'
202 );
134 $data = [ 203 $data = [
135 'HTTP/1.1 200 OK', 204 'HTTP/1.1 200 OK',
136 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', 205 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
137 'end' => 'th=device-width">' 206 'end' => 'th=device-width">'
138 . '<title>Refactoring · GitHub</title>' 207 . '<title>Refactoring · GitHub</title>'
139 . '<link rel="search" type="application/opensea', 208 . '<link rel="search" type="application/opensea',
140 '<title>ignored</title>', 209 '<title>ignored</title>'
210 . '<meta name="description" content="desc" />'
211 . '<meta name="keywords" content="key1,key2" />',
141 ]; 212 ];
142 foreach ($data as $key => $line) { 213 foreach ($data as $key => $line) {
143 $ignore = null; 214 $ignore = null;
@@ -149,6 +220,8 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
149 } 220 }
150 $this->assertEquals('utf-8', $charset); 221 $this->assertEquals('utf-8', $charset);
151 $this->assertEquals('Refactoring · GitHub', $title); 222 $this->assertEquals('Refactoring · GitHub', $title);
223 $this->assertEmpty($desc);
224 $this->assertEmpty($keywords);
152 } 225 }
153 226
154 /** 227 /**
@@ -156,7 +229,14 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
156 */ 229 */
157 public function testCurlDownloadCallbackOkNoTitle() 230 public function testCurlDownloadCallbackOkNoTitle()
158 { 231 {
159 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok'); 232 $callback = get_curl_download_callback(
233 $charset,
234 $title,
235 $desc,
236 $keywords,
237 false,
238 'ut_curl_getinfo_ok'
239 );
160 $data = [ 240 $data = [
161 'HTTP/1.1 200 OK', 241 'HTTP/1.1 200 OK',
162 'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea', 242 'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea',
@@ -168,6 +248,8 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
168 } 248 }
169 $this->assertEquals('utf-8', $charset); 249 $this->assertEquals('utf-8', $charset);
170 $this->assertEmpty($title); 250 $this->assertEmpty($title);
251 $this->assertEmpty($desc);
252 $this->assertEmpty($keywords);
171 } 253 }
172 254
173 /** 255 /**
@@ -175,7 +257,14 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
175 */ 257 */
176 public function testCurlDownloadCallbackInvalidContentType() 258 public function testCurlDownloadCallbackInvalidContentType()
177 { 259 {
178 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ct_ko'); 260 $callback = get_curl_download_callback(
261 $charset,
262 $title,
263 $desc,
264 $keywords,
265 false,
266 'ut_curl_getinfo_ct_ko'
267 );
179 $ignore = null; 268 $ignore = null;
180 $this->assertFalse($callback($ignore, '')); 269 $this->assertFalse($callback($ignore, ''));
181 $this->assertEmpty($charset); 270 $this->assertEmpty($charset);
@@ -187,7 +276,14 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
187 */ 276 */
188 public function testCurlDownloadCallbackInvalidResponseCode() 277 public function testCurlDownloadCallbackInvalidResponseCode()
189 { 278 {
190 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rc_ko'); 279 $callback = $callback = get_curl_download_callback(
280 $charset,
281 $title,
282 $desc,
283 $keywords,
284 false,
285 'ut_curl_getinfo_rc_ko'
286 );
191 $ignore = null; 287 $ignore = null;
192 $this->assertFalse($callback($ignore, '')); 288 $this->assertFalse($callback($ignore, ''));
193 $this->assertEmpty($charset); 289 $this->assertEmpty($charset);
@@ -199,7 +295,14 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
199 */ 295 */
200 public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode() 296 public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode()
201 { 297 {
202 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rs_ct_ko'); 298 $callback = $callback = get_curl_download_callback(
299 $charset,
300 $title,
301 $desc,
302 $keywords,
303 false,
304 'ut_curl_getinfo_rs_ct_ko'
305 );
203 $ignore = null; 306 $ignore = null;
204 $this->assertFalse($callback($ignore, '')); 307 $this->assertFalse($callback($ignore, ''));
205 $this->assertEmpty($charset); 308 $this->assertEmpty($charset);
@@ -207,6 +310,85 @@ class LinkUtilsTest extends \PHPUnit\Framework\TestCase
207 } 310 }
208 311
209 /** 312 /**
313 * Test the download callback with valid value, and retrieve_description option enabled.
314 */
315 public function testCurlDownloadCallbackOkWithDesc()
316 {
317 $callback = get_curl_download_callback(
318 $charset,
319 $title,
320 $desc,
321 $keywords,
322 true,
323 'ut_curl_getinfo_ok'
324 );
325 $data = [
326 'HTTP/1.1 200 OK',
327 'Server: GitHub.com',
328 'Date: Sat, 28 Oct 2017 12:01:33 GMT',
329 'Content-Type: text/html; charset=utf-8',
330 'Status: 200 OK',
331 'th=device-width">'
332 . '<title>Refactoring · GitHub</title>'
333 . '<link rel="search" type="application/opensea',
334 'end' => '<title>ignored</title>'
335 . '<meta name="description" content="link desc" />'
336 . '<meta name="keywords" content="key1,key2" />',
337 ];
338 foreach ($data as $key => $line) {
339 $ignore = null;
340 $expected = $key !== 'end' ? strlen($line) : false;
341 $this->assertEquals($expected, $callback($ignore, $line));
342 if ($expected === false) {
343 break;
344 }
345 }
346 $this->assertEquals('utf-8', $charset);
347 $this->assertEquals('Refactoring · GitHub', $title);
348 $this->assertEquals('link desc', $desc);
349 $this->assertEquals('key1 key2', $keywords);
350 }
351
352 /**
353 * Test the download callback with valid value, and retrieve_description option enabled,
354 * but no desc or keyword defined in the page.
355 */
356 public function testCurlDownloadCallbackOkWithDescNotFound()
357 {
358 $callback = get_curl_download_callback(
359 $charset,
360 $title,
361 $desc,
362 $keywords,
363 true,
364 'ut_curl_getinfo_ok'
365 );
366 $data = [
367 'HTTP/1.1 200 OK',
368 'Server: GitHub.com',
369 'Date: Sat, 28 Oct 2017 12:01:33 GMT',
370 'Content-Type: text/html; charset=utf-8',
371 'Status: 200 OK',
372 'th=device-width">'
373 . '<title>Refactoring · GitHub</title>'
374 . '<link rel="search" type="application/opensea',
375 'end' => '<title>ignored</title>',
376 ];
377 foreach ($data as $key => $line) {
378 $ignore = null;
379 $expected = $key !== 'end' ? strlen($line) : false;
380 $this->assertEquals($expected, $callback($ignore, $line));
381 if ($expected === false) {
382 break;
383 }
384 }
385 $this->assertEquals('utf-8', $charset);
386 $this->assertEquals('Refactoring · GitHub', $title);
387 $this->assertEmpty($desc);
388 $this->assertEmpty($keywords);
389 }
390
391 /**
210 * Test count_private. 392 * Test count_private.
211 */ 393 */
212 public function testCountPrivateLinks() 394 public function testCountPrivateLinks()
diff --git a/tpl/default/configure.html b/tpl/default/configure.html
index 7a87c05d..83033624 100644
--- a/tpl/default/configure.html
+++ b/tpl/default/configure.html
@@ -215,6 +215,22 @@
215 <div class="pure-g"> 215 <div class="pure-g">
216 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}"> 216 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
217 <div class="form-label"> 217 <div class="form-label">
218 <label for="retrieveDescription">
219 <span class="label-name">{'Automatically retrieve description for new bookmarks'|t}</span><br>
220 <span class="label-desc">{'Shaarli will try to retrieve the description from meta HTML headers'|t}</span>
221 </label>
222 </div>
223 </div>
224 <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
225 <div class="form-input">
226 <input type="checkbox" name="retrieveDescription" id="retrieveDescription"
227 {if="$retrieve_description"}checked{/if}/>
228 </div>
229 </div>
230 </div>
231 <div class="pure-g">
232 <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
233 <div class="form-label">
218 <label for="enableApi"> 234 <label for="enableApi">
219 <span class="label-name">{'Enable REST API'|t}</span><br> 235 <span class="label-name">{'Enable REST API'|t}</span><br>
220 <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span> 236 <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html
index f1892fa1..160286a5 100644
--- a/tpl/vintage/configure.html
+++ b/tpl/vintage/configure.html
@@ -107,6 +107,14 @@
107 </td> 107 </td>
108 </tr> 108 </tr>
109 <tr> 109 <tr>
110 <td valign="top"><b>Automatically retrieve description for new bookmarks:</b></td>
111 <td>
112 <input type="checkbox" name="retrieveDescription" id="retrieveDescription"
113 {if="$retrieve_description"}checked{/if}/>
114 <label for="retrieveDescription">&nbsp;Shaarli will try to retrieve the description from meta HTML headers</label>
115 </td>
116 </tr>
117 <tr>
110 <td valign="top"><b>Enable REST API</b></td> 118 <td valign="top"><b>Enable REST API</b></td>
111 <td> 119 <td>
112 <input type="checkbox" name="enableApi" id="enableApi" 120 <input type="checkbox" name="enableApi" id="enableApi"