Browse Source

Add privacy settings on extension list retrieval (#4603)

* Add privacy settings on extension list retrieval

There is a new privacy page to handle all configuration related to privacy. At
the moment, only privacy related to extensions can be configured.

The new settings allow to change the location of the extension list file and to
choose if the selected file is cached for a day or retrieved for each request.

Fix #4570

* Update code to pass PHPStan

* make fix-all

---------

Co-authored-by: maTh <math-home@web.de>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Alexis Degrugillier 1 year ago
parent
commit
ad2c6e6fbf
80 changed files with 212 additions and 1 deletions
  1. 16 0
      app/Controllers/configureController.php
  2. 12 1
      app/Controllers/extensionController.php
  3. 1 0
      app/Models/UserConfiguration.php
  4. 1 0
      app/i18n/cs/admin.php
  5. 4 0
      app/i18n/cs/conf.php
  6. 1 0
      app/i18n/cs/gen.php
  7. 1 0
      app/i18n/de/admin.php
  8. 4 0
      app/i18n/de/conf.php
  9. 1 0
      app/i18n/de/gen.php
  10. 1 0
      app/i18n/el/admin.php
  11. 4 0
      app/i18n/el/conf.php
  12. 1 0
      app/i18n/el/gen.php
  13. 1 0
      app/i18n/en-us/admin.php
  14. 4 0
      app/i18n/en-us/conf.php
  15. 1 0
      app/i18n/en-us/gen.php
  16. 1 0
      app/i18n/en/admin.php
  17. 4 0
      app/i18n/en/conf.php
  18. 1 0
      app/i18n/en/gen.php
  19. 1 0
      app/i18n/es/admin.php
  20. 4 0
      app/i18n/es/conf.php
  21. 1 0
      app/i18n/es/gen.php
  22. 1 0
      app/i18n/fa/admin.php
  23. 4 0
      app/i18n/fa/conf.php
  24. 1 0
      app/i18n/fa/gen.php
  25. 1 0
      app/i18n/fr/admin.php
  26. 4 0
      app/i18n/fr/conf.php
  27. 1 0
      app/i18n/fr/gen.php
  28. 1 0
      app/i18n/he/admin.php
  29. 4 0
      app/i18n/he/conf.php
  30. 1 0
      app/i18n/he/gen.php
  31. 1 0
      app/i18n/hu/admin.php
  32. 4 0
      app/i18n/hu/conf.php
  33. 1 0
      app/i18n/hu/gen.php
  34. 1 0
      app/i18n/id/admin.php
  35. 4 0
      app/i18n/id/conf.php
  36. 1 0
      app/i18n/id/gen.php
  37. 1 0
      app/i18n/it/admin.php
  38. 4 0
      app/i18n/it/conf.php
  39. 1 0
      app/i18n/it/gen.php
  40. 1 0
      app/i18n/ja/admin.php
  41. 4 0
      app/i18n/ja/conf.php
  42. 1 0
      app/i18n/ja/gen.php
  43. 1 0
      app/i18n/ko/admin.php
  44. 4 0
      app/i18n/ko/conf.php
  45. 1 0
      app/i18n/ko/gen.php
  46. 1 0
      app/i18n/lv/admin.php
  47. 4 0
      app/i18n/lv/conf.php
  48. 1 0
      app/i18n/lv/gen.php
  49. 1 0
      app/i18n/nl/admin.php
  50. 4 0
      app/i18n/nl/conf.php
  51. 1 0
      app/i18n/nl/gen.php
  52. 1 0
      app/i18n/oc/admin.php
  53. 4 0
      app/i18n/oc/conf.php
  54. 1 0
      app/i18n/oc/gen.php
  55. 1 0
      app/i18n/pl/admin.php
  56. 4 0
      app/i18n/pl/conf.php
  57. 1 0
      app/i18n/pl/gen.php
  58. 1 0
      app/i18n/pt-br/admin.php
  59. 4 0
      app/i18n/pt-br/conf.php
  60. 1 0
      app/i18n/pt-br/gen.php
  61. 1 0
      app/i18n/ru/admin.php
  62. 4 0
      app/i18n/ru/conf.php
  63. 1 0
      app/i18n/ru/gen.php
  64. 1 0
      app/i18n/sk/admin.php
  65. 4 0
      app/i18n/sk/conf.php
  66. 1 0
      app/i18n/sk/gen.php
  67. 1 0
      app/i18n/tr/admin.php
  68. 4 0
      app/i18n/tr/conf.php
  69. 1 0
      app/i18n/tr/gen.php
  70. 1 0
      app/i18n/zh-cn/admin.php
  71. 4 0
      app/i18n/zh-cn/conf.php
  72. 1 0
      app/i18n/zh-cn/gen.php
  73. 1 0
      app/i18n/zh-tw/admin.php
  74. 4 0
      app/i18n/zh-tw/conf.php
  75. 1 0
      app/i18n/zh-tw/gen.php
  76. 3 0
      app/layout/aside_configure.phtml
  77. 1 0
      app/layout/header.phtml
  78. 32 0
      app/views/configure/privacy.phtml
  79. 2 0
      app/views/extension/index.phtml
  80. 1 0
      config-user.default.php

+ 16 - 0
app/Controllers/configureController.php

@@ -503,4 +503,20 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
 			Minz_Request::good(_t('feedback.conf.updated'), [ 'c' => 'configure', 'a' => 'system' ]);
 		}
 	}
+
+	public function privacyAction(): void {
+		if (!FreshRSS_Auth::hasAccess('admin')) {
+			Minz_Error::error(403);
+		}
+
+		if (Minz_Request::isPost()) {
+			FreshRSS_Context::userConf()->retrieve_extension_list = Minz_Request::paramBoolean('retrieve_extension_list');
+			FreshRSS_Context::userConf()->save();
+			invalidateHttpCache();
+
+			Minz_Request::good(_t('feedback.conf.updated'), array('c' => 'configure', 'a' => 'privacy'));
+		}
+
+		FreshRSS_View::prependTitle(_t('conf.privacy') . ' · ');
+	}
 }

+ 12 - 1
app/Controllers/extensionController.php

@@ -44,7 +44,18 @@ class FreshRSS_extension_Controller extends FreshRSS_ActionController {
 	 */
 	protected function getAvailableExtensionList(): array {
 		$extensionListUrl = 'https://raw.githubusercontent.com/FreshRSS/Extensions/master/extensions.json';
-		$json = httpGet($extensionListUrl, CACHE_PATH . '/extension_list.json', 'json');
+
+		$cacheFile = CACHE_PATH . '/extension_list.json';
+		if (FreshRSS_Context::userConf()->retrieve_extension_list === true) {
+			if (!file_exists($cacheFile) || (time() - (filemtime($cacheFile) ?: 0) > 86400)) {
+				$json = httpGet($extensionListUrl, $cacheFile, 'json');
+			} else {
+				$json = @file_get_contents($cacheFile) ?: '';
+			}
+		} else {
+			Minz_Log::warning('The extension list retrieval is disabled in privacy configuration');
+			return [];
+		}
 
 		// we ran into problems, simply ignore them
 		if ($json === '') {

+ 1 - 0
app/Models/UserConfiguration.php

@@ -73,6 +73,7 @@ declare(strict_types=1);
  * @property string $view_mode
  * @property array<string,bool|int|string> $volatile
  * @property array<string,array<string,mixed>> $extensions
+ * @property bool $retrieve_extension_list
  */
 final class FreshRSS_UserConfiguration extends Minz_Configuration {
 	use FreshRSS_FilterActionsTrait;

+ 1 - 0
app/i18n/cs/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Popis',
 		'disabled' => 'Zakázáno',
 		'empty_list' => 'Nejsou naistalována žádná rozšíření',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Povoleno',
 		'latest' => 'Nainstalováno',
 		'name' => 'Název',

+ 4 - 0
app/i18n/cs/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Předchozí',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Správa profilu',
 		'api' => 'Správa API',

+ 1 - 0
app/i18n/cs/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Zobrazení',
 		'extensions' => 'Rozšíření',
 		'logs' => 'Protokoly',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Uživatelské dotazy',
 		'reading' => 'Čtení',
 		'search' => 'Hledat slova nebo #štítky',

+ 1 - 0
app/i18n/de/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Beschreibungen',
 		'disabled' => 'Deaktiviert',
 		'empty_list' => 'Es gibt keine installierte Erweiterung.',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Aktiviert',
 		'latest' => 'Installiert',
 		'name' => 'Name',	// IGNORE

+ 4 - 0
app/i18n/de/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Vorherige',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profil-Verwaltung',
 		'api' => 'API-Verwaltung',

+ 1 - 0
app/i18n/de/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Anzeige',
 		'extensions' => 'Erweiterungen',
 		'logs' => 'Protokolle',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Benutzerabfragen',
 		'reading' => 'Lesen',
 		'search' => 'Suche Worte oder #Tags',

+ 1 - 0
app/i18n/el/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Περιγραφή',
 		'disabled' => 'Απενεργοποιημένες',
 		'empty_list' => 'Δεν υπάρχουν εγκατεστημένες επεκτάσεις',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Ενεργοποιημένες',
 		'latest' => 'Εγκατεστημένες',
 		'name' => 'Όνομα',

+ 4 - 0
app/i18n/el/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Previous',	// TODO
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profile management',	// TODO
 		'api' => 'API management',	// TODO

+ 1 - 0
app/i18n/el/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Display',	// TODO
 		'extensions' => 'Extensions',	// TODO
 		'logs' => 'Logs',	// TODO
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'User queries',	// TODO
 		'reading' => 'Reading',	// TODO
 		'search' => 'Search words or #tags',	// TODO

+ 1 - 0
app/i18n/en-us/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Description',	// IGNORE
 		'disabled' => 'Disabled',	// IGNORE
 		'empty_list' => 'There are no installed extensions',	// IGNORE
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Enabled',	// IGNORE
 		'latest' => 'Installed',	// IGNORE
 		'name' => 'Name',	// IGNORE

+ 4 - 0
app/i18n/en-us/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Previous',	// IGNORE
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profile management',	// IGNORE
 		'api' => 'API management',	// IGNORE

+ 1 - 0
app/i18n/en-us/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Display',	// IGNORE
 		'extensions' => 'Extensions',	// IGNORE
 		'logs' => 'Logs',	// IGNORE
+		'privacy' => 'Privacy',	// IGNORE
 		'queries' => 'User queries',	// IGNORE
 		'reading' => 'Reading',	// IGNORE
 		'search' => 'Search words or #tags',	// IGNORE

+ 1 - 0
app/i18n/en/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Description',
 		'disabled' => 'Disabled',
 		'empty_list' => 'There are no installed extensions',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Enabled',
 		'latest' => 'Installed',
 		'name' => 'Name',

+ 4 - 0
app/i18n/en/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Previous',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profile management',
 		'api' => 'API management',

+ 1 - 0
app/i18n/en/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Display',
 		'extensions' => 'Extensions',
 		'logs' => 'Logs',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'User queries',
 		'reading' => 'Reading',
 		'search' => 'Search words or #tags',

+ 1 - 0
app/i18n/es/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Descripción',
 		'disabled' => 'Desactivado',
 		'empty_list' => 'No hay extensiones instaladas',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Activado',
 		'latest' => 'Instalado',
 		'name' => 'Nombre',

+ 4 - 0
app/i18n/es/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Anterior',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Administración de perfiles',
 		'api' => 'Administración de API',

+ 1 - 0
app/i18n/es/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Visualización',
 		'extensions' => 'Extensiones',
 		'logs' => 'Registros',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Peticiones de usuario',
 		'reading' => 'Lectura',
 		'search' => 'Buscar palabras o #etiquetas',

+ 1 - 0
app/i18n/fa/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => ' توضیحات',
 		'disabled' => ' معلول',
 		'empty_list' => ' هیچ برنامه افزودنی نصب شده ای وجود ندارد',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => ' فعال است',
 		'latest' => ' نصب شده است',
 		'name' => ' نام',

+ 4 - 0
app/i18n/fa/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => ' قبلی',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => ' مدیریت پروفایل',
 		'api' => ' مدیریت API',

+ 1 - 0
app/i18n/fa/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'نمایش',
 		'extensions' => ' برنامه های افزودنی',
 		'logs' => ' سیاهههای مربوط',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => ' پرس و جوهای کاربر',
 		'reading' => ' خواندن',
 		'search' => ' کلمات یا #برچسب ها را جستجو کنید',

+ 1 - 0
app/i18n/fr/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Description',	// IGNORE
 		'disabled' => 'Désactivée',
 		'empty_list' => 'Aucune extension installée',
+		'empty_list_help' => 'Vérifiez les logs pour déterminer pourquoi la liste des extensions est vide.',
 		'enabled' => 'Activée',
 		'latest' => 'Installée',
 		'name' => 'Nom',

+ 4 - 0
app/i18n/fr/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Précédent',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Vie privée',
+		'retrieve_extension_list' => 'Récupération de la liste des extensions',
+	),
 	'profile' => array(
 		'_' => 'Gestion du profil',
 		'api' => 'Gestion de l’API',

+ 1 - 0
app/i18n/fr/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Affichage',
 		'extensions' => 'Extensions',	// IGNORE
 		'logs' => 'Logs',	// IGNORE
+		'privacy' => 'Vie privée',
 		'queries' => 'Filtres utilisateurs',
 		'reading' => 'Lecture',
 		'search' => 'Rechercher des mots ou des #tags',

+ 1 - 0
app/i18n/he/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Description',	// TODO
 		'disabled' => 'Disabled',	// TODO
 		'empty_list' => 'There is no installed extension',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Enabled',	// TODO
 		'latest' => 'Installed',	// TODO
 		'name' => 'Name',	// TODO

+ 4 - 0
app/i18n/he/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'הקודם',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profile management',	// TODO
 		'api' => 'API management',	// TODO

+ 1 - 0
app/i18n/he/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'תצוגה',
 		'extensions' => 'Extensions',	// TODO
 		'logs' => 'לוגים',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'שאילתות',
 		'reading' => 'קריאה',
 		'search' => 'חיפוש מילים או #תגים',

+ 1 - 0
app/i18n/hu/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Leírás',
 		'disabled' => 'Kikapcsolva',
 		'empty_list' => 'Nincsenek telepített kiegészítők',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Bekapcsolva',
 		'latest' => 'Telepítve',
 		'name' => 'Név',

+ 4 - 0
app/i18n/hu/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Előző',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profil kezelés',
 		'api' => 'API menedzsment',

+ 1 - 0
app/i18n/hu/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Megjelenítés',
 		'extensions' => 'Kiegészítők',
 		'logs' => 'Log-ok',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Felhasználói lekérdezések',
 		'reading' => 'Olvasás',
 		'search' => 'Szavak vagy #címkék keresése',

+ 1 - 0
app/i18n/id/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Keterangan',
 		'disabled' => 'Dinonaktifkan',
 		'empty_list' => 'Tidak ada ekstensi terpasang',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Diaktifkan',
 		'latest' => 'Terinstal',
 		'name' => 'Nama',

+ 4 - 0
app/i18n/id/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Sebelumnya',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Manajemen Profil',
 		'api' => 'Manajemen API',

+ 1 - 0
app/i18n/id/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Display',	// TODO
 		'extensions' => 'Extensions',	// TODO
 		'logs' => 'Logs',	// TODO
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'User queries',	// TODO
 		'reading' => 'Reading',	// TODO
 		'search' => 'Search words or #tags',	// TODO

+ 1 - 0
app/i18n/it/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Descrizione',
 		'disabled' => 'Disabilitata',
 		'empty_list' => 'Non ci sono estensioni installate',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Abilitata',
 		'latest' => 'Installato',
 		'name' => 'Nome',

+ 4 - 0
app/i18n/it/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Precedente',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Gestione profili',
 		'api' => 'Gestione API',

+ 1 - 0
app/i18n/it/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Visualizzazione',
 		'extensions' => 'Estensioni',
 		'logs' => 'Log',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Ricerche personali',
 		'reading' => 'Lettura',
 		'search' => 'Ricerca parole o #tags',

+ 1 - 0
app/i18n/ja/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => '説明',
 		'disabled' => '無効',
 		'empty_list' => 'インストールされている拡張機能はありません',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => '有効',
 		'latest' => 'インストール済み',
 		'name' => '名前',

+ 4 - 0
app/i18n/ja/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => '前へ',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'プロフィール',
 		'api' => 'API管理',

+ 1 - 0
app/i18n/ja/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'ディスプレイ',
 		'extensions' => '拡張機能',
 		'logs' => 'ログ',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'ユーザークエリ',
 		'reading' => 'リーディング',
 		'search' => '単語で検索するかハッシュタグで検索する',

+ 1 - 0
app/i18n/ko/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => '설명',
 		'disabled' => '비활성화됨',
 		'empty_list' => '설치된 확장 기능이 없습니다',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => '활성화됨',
 		'latest' => '설치됨',
 		'name' => '이름',

+ 4 - 0
app/i18n/ko/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => '이전',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => '프로필 관리',
 		'api' => 'API 관리',

+ 1 - 0
app/i18n/ko/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => '표시',
 		'extensions' => '확장 기능',
 		'logs' => '로그',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => '사용자 쿼리',
 		'reading' => '읽기',
 		'search' => '단어 또는 #태그 검색',

+ 1 - 0
app/i18n/lv/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Apraksts',
 		'disabled' => 'Atspējots',
 		'empty_list' => 'Nav instalētu paplašinājumu',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Ieslēgts',
 		'latest' => 'Instalēts',
 		'name' => 'Vārds',

+ 4 - 0
app/i18n/lv/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Iepriekšējais',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profila pārvalde',
 		'api' => 'API pārvalde',

+ 1 - 0
app/i18n/lv/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Display',	// TODO
 		'extensions' => 'Paplašinājumi',
 		'logs' => 'Žurnāls',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Lietotāja pieprasījumi',
 		'reading' => 'Lasīšana',
 		'search' => 'Meklēt vārdus vai #birkas',

+ 1 - 0
app/i18n/nl/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Beschrijving',
 		'disabled' => 'Uitgeschakeld',
 		'empty_list' => 'Er zijn geïnstalleerde uitbreidingen',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Ingeschakeld',
 		'latest' => 'Geïnstalleerd',
 		'name' => 'Naam',

+ 4 - 0
app/i18n/nl/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Vorige',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profielbeheer',
 		'api' => 'API-beheer',

+ 1 - 0
app/i18n/nl/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Opmaak',
 		'extensions' => 'Uitbreidingen',
 		'logs' => 'Log boeken',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Gebruikers informatie',
 		'reading' => 'Lezen',
 		'search' => 'Zoek woorden of #labels',

+ 1 - 0
app/i18n/oc/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Descripcion',
 		'disabled' => 'Desactivada',
 		'empty_list' => 'Cap d’extensions pas installadas',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Activada',
 		'latest' => 'Installada',
 		'name' => 'Nom',

+ 4 - 0
app/i18n/oc/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Precedent',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Gestion del perfil',
 		'api' => 'Gestion API',

+ 1 - 0
app/i18n/oc/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Afichatge',
 		'extensions' => 'Extensions',	// IGNORE
 		'logs' => 'Jornals d’audit',	// IGNORE
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Filtres utilizaire',
 		'reading' => 'Lectura',
 		'search' => 'Recercar de mots o d’#etiquetas',

+ 1 - 0
app/i18n/pl/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Opis',
 		'disabled' => 'Wyłączone',
 		'empty_list' => 'Brak zainstalowanych rozszerzeń',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Włączone',
 		'latest' => 'Zainstalowane',
 		'name' => 'Nazwa',

+ 4 - 0
app/i18n/pl/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Poprzednie',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Zarządzanie profilem',
 		'api' => 'Zarządzanie API',

+ 1 - 0
app/i18n/pl/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Wyświetlanie',
 		'extensions' => 'Rozszerzenia',
 		'logs' => 'Dziennik',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Zapisane zapytania',
 		'reading' => 'Czytanie',
 		'search' => 'Wyszukaj wyrazy lub #tagi',

+ 1 - 0
app/i18n/pt-br/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Descrição',
 		'disabled' => 'Desabilitado',
 		'empty_list' => 'Não há extensões instaladas',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Habilitada',
 		'latest' => 'Instalado',
 		'name' => 'Nome',

+ 4 - 0
app/i18n/pt-br/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Anterior',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Gerenciamento de perfil',
 		'api' => 'Administração da API',

+ 1 - 0
app/i18n/pt-br/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Visualização',
 		'extensions' => 'Extensões',
 		'logs' => 'Logs',	// IGNORE
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Queries de usuário',
 		'reading' => 'Leitura',
 		'search' => 'Procurar por palavras ou #tags',

+ 1 - 0
app/i18n/ru/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Описание',
 		'disabled' => 'Отключены',
 		'empty_list' => 'Нет установленных расширений',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Включены',
 		'latest' => 'Установлено',
 		'name' => 'Название',

+ 4 - 0
app/i18n/ru/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Предыдущая',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Настройки профиля',
 		'api' => 'Настройки API',

+ 1 - 0
app/i18n/ru/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Отображение',
 		'extensions' => 'Расширения',
 		'logs' => 'Журнал',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Пользовательские запросы',
 		'reading' => 'Чтение',
 		'search' => 'Искать слова или #теги',

+ 1 - 0
app/i18n/sk/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Popis',
 		'disabled' => 'Zakázané',
 		'empty_list' => 'Žiadne nainštalované rozšírenia',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Povolené',
 		'latest' => 'Nainštalované',
 		'name' => 'Názov',

+ 4 - 0
app/i18n/sk/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Predošlý',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Správca profilu',
 		'api' => 'Správa API',

+ 1 - 0
app/i18n/sk/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Zobrazenie',
 		'extensions' => 'Rozšírenia',
 		'logs' => 'Záznamy',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Používateľské dopyty',
 		'reading' => 'Čítanie',
 		'search' => 'Hľadajte slová alebo #značky',

+ 1 - 0
app/i18n/tr/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => 'Açıklama',
 		'disabled' => 'Pasif',
 		'empty_list' => 'Yüklenmiş eklenti bulunmamaktadır',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => 'Aktif',
 		'latest' => 'Kuruldu',
 		'name' => 'İsim',

+ 4 - 0
app/i18n/tr/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => 'Önceki',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => 'Profil yönetimi',
 		'api' => 'API yönetimi',

+ 1 - 0
app/i18n/tr/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => 'Görünüm',
 		'extensions' => 'Eklentiler',
 		'logs' => 'Log kayıtları',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => 'Kullanıcı sorguları',
 		'reading' => 'Okuma',
 		'search' => 'Kelime veya #etiket ara',

+ 1 - 0
app/i18n/zh-cn/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => '描述',
 		'disabled' => '已禁用',
 		'empty_list' => '没有已安装的扩展',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => '已启用',
 		'latest' => '已安装',
 		'name' => '名称',

+ 4 - 0
app/i18n/zh-cn/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => '上一页',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => '账户管理',
 		'api' => 'API 管理',

+ 1 - 0
app/i18n/zh-cn/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => '显示',
 		'extensions' => '扩展',
 		'logs' => '日志',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => '自定义查询',
 		'reading' => '阅读',
 		'search' => '搜索内容或#文章标签',

+ 1 - 0
app/i18n/zh-tw/admin.php

@@ -116,6 +116,7 @@ return array(
 		'description' => '描述',
 		'disabled' => '已禁用',
 		'empty_list' => '沒有已安裝的擴充功能',
+		'empty_list_help' => 'Check the logs to determine the reason behind the empty extension list.',	// TODO
 		'enabled' => '已啟用',
 		'latest' => '已安裝',
 		'name' => '名稱',

+ 4 - 0
app/i18n/zh-tw/conf.php

@@ -99,6 +99,10 @@ return array(
 			'previous' => '上一頁',
 		),
 	),
+	'privacy' => array(
+		'_' => 'Privacy',	// TODO
+		'retrieve_extension_list' => 'Retrieve extension list',	// TODO
+	),
 	'profile' => array(
 		'_' => '個人資料管理',
 		'api' => 'API 管理',

+ 1 - 0
app/i18n/zh-tw/gen.php

@@ -183,6 +183,7 @@ return array(
 		'display' => '顯示',
 		'extensions' => '擴充功能',
 		'logs' => '日誌',
+		'privacy' => 'Privacy',	// TODO
 		'queries' => '自定義查詢',
 		'reading' => '閱讀',
 		'search' => '搜尋內容或#標簽',

+ 3 - 0
app/layout/aside_configure.phtml

@@ -43,6 +43,9 @@
 				<li class="item<?= Minz_Request::controllerName() === 'extension' ? ' active' : '' ?>">
 					<a href="<?= _url('extension', 'index') ?>"><?= _t('gen.menu.extensions') ?></a>
 				</li>
+				<li class="item<?= Minz_Request::controllerName() === 'privacy' ? ' active' : '' ?>">
+					<a href="<?= _url('configure', 'privacy') ?>"><?= _t('gen.menu.privacy') ?></a>
+				</li>
 				<?php if (!FreshRSS_Auth::hasAccess('admin')) { ?>
 				<li class="item<?= Minz_Request::actionName() === 'logs' ? ' active' : '' ?>">
 					<a href="<?= _url('index', 'logs') ?>"><?= _t('gen.menu.logs') ?></a>

+ 1 - 0
app/layout/header.phtml

@@ -79,6 +79,7 @@
 						<li class="item"><a href="<?= _url('configure', 'shortcut') ?>"><?= _t('gen.menu.shortcuts') ?></a></li>
 						<li class="item"><a href="<?= _url('configure', 'queries') ?>"><?= _t('gen.menu.queries') ?></a></li>
 						<li class="item"><a href="<?= _url('extension', 'index') ?>"><?= _t('gen.menu.extensions') ?></a></li>
+						<li class="item"><a href="<?= _url('configure', 'privacy') ?>"><?= _t('gen.menu.privacy') ?></a></li>
 						<?= Minz_ExtensionManager::callHookString('menu_configuration_entry') ?>
 					</ul>
 				</li>

+ 32 - 0
app/views/configure/privacy.phtml

@@ -0,0 +1,32 @@
+<?php
+	/** @var FreshRSS_View $this */
+	$this->partial('aside_configure');
+?>
+
+<main class="post">
+	<div class="link-back-wrapper">
+		<a class="link-back" href="<?= _url('index', 'index') ?>"><?= _t('gen.action.back_to_rss_feeds') ?></a>
+	</div>
+
+	<h1><?= _t('conf.privacy') ?></h1>
+
+	<form method="post" action="<?= _url('configure', 'privacy') ?>">
+		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+
+		<div class="form-group">
+			<label class="group-name" for="retrieve_extension_list"><?= _t('conf.privacy.retrieve_extension_list') ?></label>
+			<div class="group-controls">
+				<input type="checkbox" id="retrieve_extension_list" name="retrieve_extension_list" value="1"<?=
+					FreshRSS_Context::userConf()->retrieve_extension_list !== false ? ' checked="checked"' : '' ?>
+					data-leave-validation="<?= FreshRSS_Context::userConf()->retrieve_extension_list !== false ? 1 : 0 ?>"/>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
+				<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
+			</div>
+		</div>
+	</form>
+</main>

+ 2 - 0
app/views/extension/index.phtml

@@ -76,6 +76,8 @@
 				<?php } ?>
 			</table>
 		</div>
+	<?php } else { ?>
+		<p class="alert alert-warn"><?= _t('admin.extensions.empty_list_help') ?></p>
 	<?php } ?>
 </main>
 

+ 1 - 0
config-user.default.php

@@ -124,6 +124,7 @@ return array (
 	'show_nav_buttons' => true,
 	# List of enabled FreshRSS extensions.
 	'extensions_enabled' => [],
+	'retrieve_extension_list' => true,
 	# Extensions configurations
 	'extensions' => [],
 );