From: ArthurHoaro Date: Tue, 27 Oct 2020 18:29:43 +0000 (+0100) Subject: Merge pull request #1604 from ArthurHoaro/feature/server-admin-page X-Git-Tag: v0.12.1^2~26 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=e6215a2ad97182efcf88ef532ec6bd65ae35fd19;hp=034c1ce5269956ec43448f9fd2959e094c861004;p=github%2Fshaarli%2FShaarli.git Merge pull request #1604 from ArthurHoaro/feature/server-admin-page Feature: add a Server administration page --- diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 3aa21829..bd1c7cf3 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php @@ -14,8 +14,9 @@ class ApplicationUtils */ public static $VERSION_FILE = 'shaarli_version.php'; - private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; - private static $GIT_BRANCHES = array('latest', 'stable'); + public static $GITHUB_URL = 'https://github.com/shaarli/Shaarli'; + public static $GIT_RAW_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; + public static $GIT_BRANCHES = array('latest', 'stable'); private static $VERSION_START_TAG = ''; @@ -125,7 +126,7 @@ class ApplicationUtils // Late Static Binding allows overriding within tests // See http://php.net/manual/en/language.oop5.late-static-bindings.php $latestVersion = static::getVersion( - self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE + self::$GIT_RAW_URL . '/' . $branch . '/' . self::$VERSION_FILE ); if (!$latestVersion) { @@ -171,35 +172,45 @@ class ApplicationUtils /** * Checks Shaarli has the proper access permissions to its resources * - * @param ConfigManager $conf Configuration Manager instance. + * @param ConfigManager $conf Configuration Manager instance. + * @param bool $minimalMode In minimal mode we only check permissions to be able to display a template. + * Currently we only need to be able to read the theme and write in raintpl cache. * * @return array A list of the detected configuration issues */ - public static function checkResourcePermissions($conf) + public static function checkResourcePermissions(ConfigManager $conf, bool $minimalMode = false): array { - $errors = array(); + $errors = []; $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/'); // Check script and template directories are readable - foreach (array( + foreach ([ 'application', 'inc', 'plugins', $rainTplDir, $rainTplDir . '/' . $conf->get('resource.theme'), - ) as $path) { + ] as $path) { if (!is_readable(realpath($path))) { $errors[] = '"' . $path . '" ' . t('directory is not readable'); } } // Check cache and data directories are readable and writable - foreach (array( - $conf->get('resource.thumbnails_cache'), - $conf->get('resource.data_dir'), - $conf->get('resource.page_cache'), - $conf->get('resource.raintpl_tmp'), - ) as $path) { + if ($minimalMode) { + $folders = [ + $conf->get('resource.raintpl_tmp'), + ]; + } else { + $folders = [ + $conf->get('resource.thumbnails_cache'), + $conf->get('resource.data_dir'), + $conf->get('resource.page_cache'), + $conf->get('resource.raintpl_tmp'), + ]; + } + + foreach ($folders as $path) { if (!is_readable(realpath($path))) { $errors[] = '"' . $path . '" ' . t('directory is not readable'); } @@ -208,6 +219,10 @@ class ApplicationUtils } } + if ($minimalMode) { + return $errors; + } + // Check configuration files are readable and writable foreach (array( $conf->getConfigFileExt(), @@ -246,4 +261,54 @@ class ApplicationUtils { return hash_hmac('sha256', $currentVersion, $salt); } + + /** + * Get a list of PHP extensions used by Shaarli. + * + * @return array[] List of extension with following keys: + * - name: extension name + * - required: whether the extension is required to use Shaarli + * - desc: short description of extension usage in Shaarli + * - loaded: whether the extension is properly loaded or not + */ + public static function getPhpExtensionsRequirement(): array + { + $extensions = [ + ['name' => 'json', 'required' => true, 'desc' => t('Configuration parsing')], + ['name' => 'simplexml', 'required' => true, 'desc' => t('Slim Framework (routing, etc.)')], + ['name' => 'mbstring', 'required' => true, 'desc' => t('Multibyte (Unicode) string support')], + ['name' => 'gd', 'required' => false, 'desc' => t('Required to use thumbnails')], + ['name' => 'intl', 'required' => false, 'desc' => t('Localized text sorting (e.g. e->è->f)')], + ['name' => 'curl', 'required' => false, 'desc' => t('Better retrieval of bookmark metadata and thumbnail')], + ['name' => 'gettext', 'required' => false, 'desc' => t('Use the translation system in gettext mode')], + ['name' => 'ldap', 'required' => false, 'desc' => t('Login using LDAP server')], + ]; + + foreach ($extensions as &$extension) { + $extension['loaded'] = extension_loaded($extension['name']); + } + + return $extensions; + } + + /** + * Return the EOL date of given PHP version. If the version is unknown, + * we return today + 2 years. + * + * @param string $fullVersion PHP version, e.g. 7.4.7 + * + * @return string Date format: YYYY-MM-DD + */ + public static function getPhpEol(string $fullVersion): string + { + preg_match('/(\d+\.\d+)\.\d+/', $fullVersion, $matches); + + return [ + '7.1' => '2019-12-01', + '7.2' => '2020-11-30', + '7.3' => '2021-12-06', + '7.4' => '2022-11-28', + '8.0' => '2023-12-01', + ][$matches[1]] ?? (new \DateTime('+2 year'))->format('Y-m-d'); + } } diff --git a/application/FileUtils.php b/application/FileUtils.php index 30560bfc..3f940751 100644 --- a/application/FileUtils.php +++ b/application/FileUtils.php @@ -81,4 +81,60 @@ class FileUtils ) ); } + + /** + * Recursively deletes a folder content, and deletes itself optionally. + * If an excluded file is found, folders won't be deleted. + * + * Additional security: raise an exception if it tries to delete a folder outside of Shaarli directory. + * + * @param string $path + * @param bool $selfDelete Delete the provided folder if true, only its content if false. + * @param array $exclude + */ + public static function clearFolder(string $path, bool $selfDelete, array $exclude = []): bool + { + $skipped = false; + + if (!is_dir($path)) { + throw new IOException(t('Provided path is not a directory.')); + } + + if (!static::isPathInShaarliFolder($path)) { + throw new IOException(t('Trying to delete a folder outside of Shaarli path.')); + } + + foreach (new \DirectoryIterator($path) as $file) { + if($file->isDot()) { + continue; + } + + if (in_array($file->getBasename(), $exclude, true)) { + $skipped = true; + continue; + } + + if ($file->isFile()) { + unlink($file->getPathname()); + } elseif($file->isDir()) { + $skipped = static::clearFolder($file->getRealPath(), true, $exclude) || $skipped; + } + } + + if ($selfDelete && !$skipped) { + rmdir($path); + } + + return $skipped; + } + + /** + * Checks that the given path is inside Shaarli directory. + */ + public static function isPathInShaarliFolder(string $path): bool + { + $rootDirectory = dirname(dirname(__FILE__)); + + return strpos(realpath($path), $rootDirectory) !== false; + } } diff --git a/application/front/controller/admin/ServerController.php b/application/front/controller/admin/ServerController.php new file mode 100644 index 00000000..85654a43 --- /dev/null +++ b/application/front/controller/admin/ServerController.php @@ -0,0 +1,87 @@ +assignView('php_version', PHP_VERSION); + $this->assignView('php_eol', format_date($phpEol, false)); + $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); + $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); + $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); + $this->assignView('release_url', ApplicationUtils::$GITHUB_URL . '/releases/tag/' . $latestVersion); + $this->assignView('latest_version', $latestVersion); + $this->assignView('current_version', $currentVersion); + $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode')); + $this->assignView('index_url', index_url($this->container->environment)); + $this->assignView('client_ip', client_ip_id($this->container->environment)); + $this->assignView('trusted_proxies', $this->container->conf->get('security.trusted_proxies', [])); + + $this->assignView( + 'pagetitle', + t('Server administration') . ' - ' . $this->container->conf->get('general.title', 'Shaarli') + ); + + return $response->write($this->render('server')); + } + + /** + * GET /admin/clear-cache?type={$type} - Action to trigger cache folder clearing (either main or thumbnails). + */ + public function clearCache(Request $request, Response $response): Response + { + $exclude = ['.htaccess']; + + if ($request->getQueryParam('type') === static::CACHE_THUMB) { + $folders = [$this->container->conf->get('resource.thumbnails_cache')]; + + $this->saveWarningMessage( + t('Thumbnails cache has been cleared.') . ' ' . + '' . t('Please synchronize them.') .'' + ); + } else { + $folders = [ + $this->container->conf->get('resource.page_cache'), + $this->container->conf->get('resource.raintpl_tmp'), + ]; + + $this->saveSuccessMessage(t('Shaarli\'s cache folder has been cleared!')); + } + + // Make sure that we don't delete root cache folder + $folders = array_map('realpath', array_values(array_filter(array_map('trim', $folders)))); + foreach ($folders as $folder) { + FileUtils::clearFolder($folder, false, $exclude); + } + + return $this->redirect($response, '/admin/server'); + } +} diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php index a8019ead..5267c8f5 100644 --- a/application/front/controller/visitor/BookmarkListController.php +++ b/application/front/controller/visitor/BookmarkListController.php @@ -169,16 +169,24 @@ class BookmarkListController extends ShaarliVisitorController */ protected function updateThumbnail(Bookmark $bookmark, bool $writeDatastore = true): bool { - // Logged in, not async retrieval, thumbnails enabled, and thumbnail should be updated - if ($this->container->loginManager->isLoggedIn() - && true !== $this->container->conf->get('general.enable_async_metadata', true) - && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE - && $bookmark->shouldUpdateThumbnail() - ) { - $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); - $this->container->bookmarkService->set($bookmark, $writeDatastore); - - return true; + if (false === $this->container->loginManager->isLoggedIn()) { + return false; + } + + // If thumbnail should be updated, we reset it to null + if ($bookmark->shouldUpdateThumbnail()) { + $bookmark->setThumbnail(null); + + // Requires an update, not async retrieval, thumbnails enabled + if ($bookmark->shouldUpdateThumbnail() + && true !== $this->container->conf->get('general.enable_async_metadata', true) + && $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE + ) { + $bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl())); + $this->container->bookmarkService->set($bookmark, $writeDatastore); + + return true; + } } return false; diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index 7cb32777..564a5777 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php @@ -53,6 +53,16 @@ class InstallController extends ShaarliVisitorController $this->assignView('cities', $cities); $this->assignView('languages', Languages::getAvailableLanguages()); + $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); + + $this->assignView('php_version', PHP_VERSION); + $this->assignView('php_eol', format_date($phpEol, false)); + $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); + $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); + $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); + + $this->assignView('pagetitle', t('Install Shaarli')); + return $response->write($this->render('install')); } @@ -150,7 +160,7 @@ class InstallController extends ShaarliVisitorController protected function checkPermissions(): bool { // Ensure Shaarli has proper access to its resources - $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); + $errors = ApplicationUtils::checkResourcePermissions($this->container->conf, true); if (empty($errors)) { return true; } diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index 286ac83b..7dc61903 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss @@ -1047,7 +1047,7 @@ body, } table { - margin: auto; + margin: 10px auto 25px auto; width: 90%; .order { @@ -1696,6 +1696,60 @@ form { } } +// SERVER PAGE + +.server-tables-page, +.server-tables { + .window-subtitle { + &::before { + display: block; + margin: 8px auto; + background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color)); + width: 50%; + height: 1px; + content: ''; + } + } + + .server-row { + p { + height: 25px; + padding: 0 10px; + } + } + + .server-label { + text-align: right; + font-weight: bold; + } + + i { + &.fa-color-green { + color: $main-green; + } + + &.fa-color-orange { + color: $orange; + } + + &.fa-color-red { + color: $red; + } + } + + @media screen and (max-width: 64em) { + .server-label { + text-align: center; + } + + .server-row { + p { + text-align: center; + } + } + } +} + // Print rules @media print { .shaarli-menu { diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index f7baedfb..db6bfa3e 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Shaarli\n" -"POT-Creation-Date: 2020-10-16 20:01+0200\n" -"PO-Revision-Date: 2020-10-16 20:02+0200\n" +"POT-Creation-Date: 2020-10-21 15:00+0200\n" +"PO-Revision-Date: 2020-10-21 15:06+0200\n" "Last-Translator: \n" "Language-Team: Shaarli\n" "Language: fr_FR\n" @@ -20,7 +20,7 @@ msgstr "" "X-Poedit-SearchPath-3: init.php\n" "X-Poedit-SearchPath-4: plugins\n" -#: application/ApplicationUtils.php:161 +#: application/ApplicationUtils.php:162 #, php-format msgid "" "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " @@ -31,22 +31,62 @@ msgstr "" "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " "connues et devrait être mise à jour au plus tôt." -#: application/ApplicationUtils.php:192 application/ApplicationUtils.php:204 +#: application/ApplicationUtils.php:195 application/ApplicationUtils.php:215 msgid "directory is not readable" msgstr "le répertoire n'est pas accessible en lecture" -#: application/ApplicationUtils.php:207 +#: application/ApplicationUtils.php:218 msgid "directory is not writable" msgstr "le répertoire n'est pas accessible en écriture" -#: application/ApplicationUtils.php:225 +#: application/ApplicationUtils.php:240 msgid "file is not readable" msgstr "le fichier n'est pas accessible en lecture" -#: application/ApplicationUtils.php:228 +#: application/ApplicationUtils.php:243 msgid "file is not writable" msgstr "le fichier n'est pas accessible en écriture" +#: application/ApplicationUtils.php:277 +msgid "Configuration parsing" +msgstr "Chargement de la configuration" + +#: application/ApplicationUtils.php:278 +msgid "Slim Framework (routing, etc.)" +msgstr "Slim Framwork (routage, etc.)" + +#: application/ApplicationUtils.php:279 +msgid "Multibyte (Unicode) string support" +msgstr "Support des chaînes de caractère multibytes (Unicode)" + +#: application/ApplicationUtils.php:280 +msgid "Required to use thumbnails" +msgstr "Obligatoire pour utiliser les miniatures" + +#: application/ApplicationUtils.php:281 +msgid "Localized text sorting (e.g. e->è->f)" +msgstr "Tri des textes traduits (ex : e->è->f)" + +#: application/ApplicationUtils.php:282 +msgid "Better retrieval of bookmark metadata and thumbnail" +msgstr "Meilleure récupération des meta-données des marque-pages et minatures" + +#: application/ApplicationUtils.php:283 +msgid "Use the translation system in gettext mode" +msgstr "Utiliser le système de traduction en mode gettext" + +#: application/ApplicationUtils.php:284 +msgid "Login using LDAP server" +msgstr "Authentification via un serveur LDAP" + +#: application/FileUtils.php:100 +msgid "Provided path is not a directory." +msgstr "Le chemin fourni n'est pas un dossier." + +#: application/FileUtils.php:104 +msgid "Trying to delete a folder outside of Shaarli path." +msgstr "Tentative de supprimer un dossier en dehors du chemin de Shaarli." + #: application/History.php:179 msgid "History file isn't readable or writable" msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" @@ -330,12 +370,13 @@ msgid "You have enabled or changed thumbnails mode." msgstr "Vous avez activé ou changé le mode de miniatures." #: application/front/controller/admin/ConfigureController.php:103 +#: application/front/controller/admin/ServerController.php:68 #: application/legacy/LegacyUpdater.php:538 msgid "Please synchronize them." msgstr "Merci de les synchroniser." #: application/front/controller/admin/ConfigureController.php:113 -#: application/front/controller/visitor/InstallController.php:136 +#: application/front/controller/visitor/InstallController.php:146 msgid "Error while writing config file after configuration update." msgstr "" "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." @@ -377,33 +418,33 @@ msgstr "" msgid "Shaare a new link" msgstr "Partager un nouveau lien" -#: application/front/controller/admin/ManageShaareController.php:78 +#: application/front/controller/admin/ManageShaareController.php:64 msgid "Note: " msgstr "Note : " -#: application/front/controller/admin/ManageShaareController.php:109 -#: application/front/controller/admin/ManageShaareController.php:206 -#: application/front/controller/admin/ManageShaareController.php:275 -#: application/front/controller/admin/ManageShaareController.php:315 +#: application/front/controller/admin/ManageShaareController.php:95 +#: application/front/controller/admin/ManageShaareController.php:193 +#: application/front/controller/admin/ManageShaareController.php:262 +#: application/front/controller/admin/ManageShaareController.php:302 #, php-format msgid "Bookmark with identifier %s could not be found." msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." -#: application/front/controller/admin/ManageShaareController.php:194 -#: application/front/controller/admin/ManageShaareController.php:252 +#: application/front/controller/admin/ManageShaareController.php:181 +#: application/front/controller/admin/ManageShaareController.php:239 msgid "Invalid bookmark ID provided." msgstr "ID du lien non valide." -#: application/front/controller/admin/ManageShaareController.php:260 +#: application/front/controller/admin/ManageShaareController.php:247 msgid "Invalid visibility provided." msgstr "Visibilité du lien non valide." -#: application/front/controller/admin/ManageShaareController.php:363 +#: application/front/controller/admin/ManageShaareController.php:352 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 msgid "Edit" msgstr "Modifier" -#: application/front/controller/admin/ManageShaareController.php:366 +#: application/front/controller/admin/ManageShaareController.php:355 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 msgid "Shaare" @@ -411,7 +452,7 @@ msgstr "Shaare" #: application/front/controller/admin/ManageTagController.php:29 #: tmp/changetag.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 msgid "Manage tags" msgstr "Gérer les tags" @@ -435,7 +476,7 @@ msgstr[1] "Le tag a été renommé dans %d liens." #: application/front/controller/admin/PasswordController.php:28 #: tmp/changepassword.b91ef64efc3688266305ea9b42e5017e.rtpl.php:13 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 msgid "Change password" msgstr "Modifier le mot de passe" @@ -467,6 +508,20 @@ msgstr "" "Une erreur s'est produite lors de la sauvegarde de la configuration des " "plugins : " +#: application/front/controller/admin/ServerController.php:50 +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 +msgid "Server administration" +msgstr "Administration serveur" + +#: application/front/controller/admin/ServerController.php:67 +msgid "Thumbnails cache has been cleared." +msgstr "Le cache des miniatures a été vidé." + +#: application/front/controller/admin/ServerController.php:76 +msgid "Shaarli's cache folder has been cleared!" +msgstr "Le dossier de cache de Shaarli a été vidé !" + #: application/front/controller/admin/ThumbnailsController.php:37 #: tmp/thumbnails.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 msgid "Thumbnails update" @@ -502,9 +557,14 @@ msgstr "Une erreur inattendue s'est produite." #: application/front/controller/visitor/ErrorNotFoundController.php:25 msgid "Requested page could not be found." -msgstr "" +msgstr "La page demandée n'a pas pu être trouvée." -#: application/front/controller/visitor/InstallController.php:73 +#: application/front/controller/visitor/InstallController.php:64 +#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 +msgid "Install Shaarli" +msgstr "Installation de Shaarli" + +#: application/front/controller/visitor/InstallController.php:83 #, php-format msgid "" "
Sessions do not seem to work correctly on your server.
Make sure the " @@ -523,14 +583,14 @@ msgstr "" "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " "adresse IP ou un Fully Qualified Domain Name.
" -#: application/front/controller/visitor/InstallController.php:144 +#: application/front/controller/visitor/InstallController.php:154 msgid "" "Shaarli is now configured. Please login and start shaaring your bookmarks!" msgstr "" "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " "shaare vos liens !" -#: application/front/controller/visitor/InstallController.php:158 +#: application/front/controller/visitor/InstallController.php:168 msgid "Insufficient permissions:" msgstr "Permissions insuffisantes :" @@ -1016,25 +1076,28 @@ msgstr "" "miniatures." #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:328 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:56 +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:122 msgid "Synchronize thumbnails" msgstr "Synchroniser les miniatures" #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:339 #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:30 +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 msgid "All" msgstr "Tous" #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:343 +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 msgid "Only common media hosts" msgstr "Seulement les hébergeurs de média connus" #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:347 +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 msgid "None" msgstr "Aucune" #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:355 -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:199 msgid "Save" @@ -1060,27 +1123,27 @@ msgstr "Tous les liens d'un jour sur une page." msgid "Next day" msgstr "Jour suivant" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 msgid "Edit Shaare" msgstr "Modifier le Shaare" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:18 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:21 msgid "New Shaare" msgstr "Nouveau Shaare" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 msgid "Created:" msgstr "Création :" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:29 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 msgid "URL" msgstr "URL" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:38 msgid "Title" msgstr "Titre" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:75 #: tmp/pluginsadmin.b91ef64efc3688266305ea9b42e5017e.rtpl.php:99 @@ -1088,33 +1151,33 @@ msgstr "Titre" msgid "Description" msgstr "Description" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 msgid "Tags" msgstr "Tags" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:74 #: tmp/export.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 msgid "Private" msgstr "Privé" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:80 msgid "Description will be rendered with" msgstr "La description sera générée avec" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:68 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 msgid "Markdown syntax documentation" msgstr "Documentation sur la syntaxe Markdown" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:69 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 msgid "Markdown syntax" msgstr "la syntaxe Markdown" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:88 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 msgid "Apply Changes" msgstr "Appliquer les changements" -#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:93 +#: tmp/editlink.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:173 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:147 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:147 @@ -1179,10 +1242,6 @@ msgstr "Les doublons s'appuient sur les URL" msgid "Add default tags" msgstr "Ajouter des tags par défaut" -#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 -msgid "Install Shaarli" -msgstr "Installation de Shaarli" - #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:25 msgid "It looks like it's the first time you run Shaarli. Please configure it." msgstr "" @@ -1215,6 +1274,10 @@ msgstr "Mes liens" msgid "Install" msgstr "Installer" +#: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:190 +msgid "Server requirements" +msgstr "Pré-requis serveur" + #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:14 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:79 msgid "shaare" @@ -1511,6 +1574,100 @@ msgstr "Configuration des extensions" msgid "No parameter available." msgstr "Aucun paramètre disponible." +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:16 +msgid "General" +msgstr "Général" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:20 +msgid "Index URL" +msgstr "URL de l'index" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 +msgid "Base path" +msgstr "Chemin de base" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 +msgid "Client IP" +msgstr "IP du client" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:44 +msgid "Trusted reverse proxies" +msgstr "Reverse proxies de confiance" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:58 +msgid "N/A" +msgstr "N/A" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:84 +msgid "Visit releases page on Github" +msgstr "Visiter la page des releases sur Github" + +#: tmp/server.b91ef64efc3688266305ea9b42e5017e.rtpl.php:121 +msgid "Synchronize all link thumbnails" +msgstr "Synchroniser toutes les miniatures" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:2 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:2 +msgid "Permissions" +msgstr "Permissions" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:8 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:8 +msgid "There are permissions that need to be fixed." +msgstr "Il y a des permissions qui doivent être corrigées." + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:23 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:23 +msgid "All read/write permissions are properly set." +msgstr "Toutes les permissions de lecture/écriture sont définies correctement." + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:32 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:32 +msgid "Running PHP" +msgstr "Fonctionnant avec PHP" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:36 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:36 +msgid "End of life: " +msgstr "Fin de vie : " + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:48 +msgid "Extension" +msgstr "Extension" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:49 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:49 +msgid "Usage" +msgstr "Utilisation" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:50 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:50 +msgid "Status" +msgstr "Statut" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:51 +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:66 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:51 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:66 +msgid "Loaded" +msgstr "Chargé" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60 +msgid "Required" +msgstr "Obligatoire" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:60 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:60 +msgid "Optional" +msgstr "Optionnel" + +#: tmp/server.requirements.b91ef64efc3688266305ea9b42e5017e.rtpl.php:70 +#: tmp/server.requirements.cedf684561d925457130839629000a81.rtpl.php:70 +msgid "Not loaded" +msgstr "Non chargé" + #: tmp/tag.cloud.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 #: tmp/tag.list.b91ef64efc3688266305ea9b42e5017e.rtpl.php:19 msgid "tags" @@ -1561,15 +1718,19 @@ msgstr "Configurer Shaarli" msgid "Enable, disable and configure plugins" msgstr "Activer, désactiver et configurer les extensions" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:27 +msgid "Check instance's server configuration" +msgstr "Vérifier la configuration serveur de l'instance" + +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:34 msgid "Change your password" msgstr "Modifier le mot de passe" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:35 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 msgid "Rename or delete a tag in all links" msgstr "Renommer ou supprimer un tag dans tous les liens" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:41 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 msgid "" "Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, " "delicious...)" @@ -1577,11 +1738,11 @@ msgstr "" "Importer des marques pages au format Netscape HTML (comme exportés depuis " "Firefox, Chrome, Opera, delicious...)" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:42 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 msgid "Import links" msgstr "Importer des liens" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:47 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:53 msgid "" "Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, " "Opera, delicious...)" @@ -1589,15 +1750,11 @@ msgstr "" "Exporter les marques pages au format Netscape HTML (comme exportés depuis " "Firefox, Chrome, Opera, delicious...)" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:54 msgid "Export database" msgstr "Exporter les données" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:55 -msgid "Synchronize all link thumbnails" -msgstr "Synchroniser toutes les miniatures" - -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:81 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:77 msgid "" "Drag one of these button to your bookmarks toolbar or right-click it and " "\"Bookmark This Link\"" @@ -1605,13 +1762,13 @@ msgstr "" "Glisser un de ces boutons dans votre barre de favoris ou cliquer droit " "dessus et « Ajouter aux favoris »" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:78 msgid "then click on the bookmarklet in any page you want to share." msgstr "" "puis cliquer sur le marque-page depuis un site que vous souhaitez partager." -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:86 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:110 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:82 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 msgid "" "Drag this link to your bookmarks toolbar or right-click it and Bookmark This " "Link" @@ -1619,40 +1776,40 @@ msgstr "" "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " "Ajouter aux favoris »" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:87 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:83 msgid "then click ✚Shaare link button in any page you want to share" msgstr "puis cliquer sur ✚Shaare depuis un site que vous souhaitez partager" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:96 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:118 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:92 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:114 msgid "The selected text is too long, it will be truncated." msgstr "Le texte sélectionné est trop long, il sera tronqué." -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:106 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:102 msgid "Shaare link" msgstr "Shaare" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:111 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:107 msgid "" "Then click ✚Add Note button anytime to start composing a private Note (text " "post) to your Shaarli" msgstr "" "Puis cliquer sur ✚Add Note pour commencer à rédiger une Note sur Shaarli" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:127 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:123 msgid "Add Note" msgstr "Ajouter une Note" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:136 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:132 msgid "3rd party" msgstr "Applications tierces" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:139 -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:144 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:135 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:140 msgid "plugin" msgstr "extension" -#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:169 +#: tmp/tools.b91ef64efc3688266305ea9b42e5017e.rtpl.php:165 msgid "" "Drag this link to your bookmarks toolbar, or right-click it and choose " "Bookmark This Link" @@ -1660,9 +1817,6 @@ msgstr "" "Glisser ce lien dans votre barre de favoris ou cliquer droit dessus et « " "Ajouter aux favoris »" -#~ msgid "Provided data is invalid" -#~ msgstr "Les informations fournies ne sont pas valides" - #~ msgid "Rename" #~ msgstr "Renommer" diff --git a/index.php b/index.php index 1b10ee41..a46e32c9 100644 --- a/index.php +++ b/index.php @@ -143,6 +143,8 @@ $app->group('/admin', function () { $this->get('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index'); $this->post('/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save'); $this->get('/token', '\Shaarli\Front\Controller\Admin\TokenController:getToken'); + $this->get('/server', '\Shaarli\Front\Controller\Admin\ServerController:index'); + $this->get('/clear-cache', '\Shaarli\Front\Controller\Admin\ServerController:clearCache'); $this->get('/thumbnails', '\Shaarli\Front\Controller\Admin\ThumbnailsController:index'); $this->get('/metadata', '\Shaarli\Front\Controller\Admin\MetadataController:ajaxRetrieveTitle'); $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php index a232b351..ac46cbf1 100644 --- a/tests/ApplicationUtilsTest.php +++ b/tests/ApplicationUtilsTest.php @@ -339,6 +339,35 @@ class ApplicationUtilsTest extends \Shaarli\TestCase ); } + /** + * Checks resource permissions in minimal mode. + */ + public function testCheckCurrentResourcePermissionsErrorsMinimalMode(): void + { + $conf = new ConfigManager(''); + $conf->set('resource.thumbnails_cache', 'null/cache'); + $conf->set('resource.config', 'null/data/config.php'); + $conf->set('resource.data_dir', 'null/data'); + $conf->set('resource.datastore', 'null/data/store.php'); + $conf->set('resource.ban_file', 'null/data/ipbans.php'); + $conf->set('resource.log', 'null/data/log.txt'); + $conf->set('resource.page_cache', 'null/pagecache'); + $conf->set('resource.raintpl_tmp', 'null/tmp'); + $conf->set('resource.raintpl_tpl', 'null/tpl'); + $conf->set('resource.raintpl_theme', 'null/tpl/default'); + $conf->set('resource.update_check', 'null/data/lastupdatecheck.txt'); + + static::assertSame( + [ + '"null/tpl" directory is not readable', + '"null/tpl/default" directory is not readable', + '"null/tmp" directory is not readable', + '"null/tmp" directory is not writable' + ], + ApplicationUtils::checkResourcePermissions($conf, true) + ); + } + /** * Check update with 'dev' as curent version (master branch). * It should always return false. @@ -349,4 +378,37 @@ class ApplicationUtilsTest extends \Shaarli\TestCase ApplicationUtils::checkUpdate('dev', self::$testUpdateFile, 100, true, true) ); } + + /** + * Basic test of getPhpExtensionsRequirement() + */ + public function testGetPhpExtensionsRequirementSimple(): void + { + static::assertCount(8, ApplicationUtils::getPhpExtensionsRequirement()); + static::assertSame([ + 'name' => 'json', + 'required' => true, + 'desc' => 'Configuration parsing', + 'loaded' => true, + ], ApplicationUtils::getPhpExtensionsRequirement()[0]); + } + + /** + * Test getPhpEol with a known version: 7.4 -> 2022 + */ + public function testGetKnownPhpEol(): void + { + static::assertSame('2022-11-28', ApplicationUtils::getPhpEol('7.4.7')); + } + + /** + * Test getPhpEol with an unknown version: 7.4 -> 2022 + */ + public function testGetUnknownPhpEol(): void + { + static::assertSame( + (((int) (new \DateTime())->format('Y')) + 2) . (new \DateTime())->format('-m-d'), + ApplicationUtils::getPhpEol('7.51.34') + ); + } } diff --git a/tests/FileUtilsTest.php b/tests/FileUtilsTest.php index 9163bdf1..3384504a 100644 --- a/tests/FileUtilsTest.php +++ b/tests/FileUtilsTest.php @@ -3,25 +3,48 @@ namespace Shaarli; use Exception; +use Shaarli\Exceptions\IOException; /** * Class FileUtilsTest * * Test file utility class. */ -class FileUtilsTest extends \Shaarli\TestCase +class FileUtilsTest extends TestCase { /** * @var string Test file path. */ protected static $file = 'sandbox/flat.db'; + protected function setUp(): void + { + @mkdir('sandbox'); + mkdir('sandbox/folder2'); + touch('sandbox/file1'); + touch('sandbox/file2'); + mkdir('sandbox/folder1'); + touch('sandbox/folder1/file1'); + touch('sandbox/folder1/file2'); + mkdir('sandbox/folder3'); + mkdir('/tmp/shaarli-to-delete'); + } + /** * Delete test file after every test. */ protected function tearDown(): void { @unlink(self::$file); + + @unlink('sandbox/folder1/file1'); + @unlink('sandbox/folder1/file2'); + @rmdir('sandbox/folder1'); + @unlink('sandbox/file1'); + @unlink('sandbox/file2'); + @rmdir('sandbox/folder2'); + @rmdir('sandbox/folder3'); + @rmdir('/tmp/shaarli-to-delete'); } /** @@ -107,4 +130,67 @@ class FileUtilsTest extends \Shaarli\TestCase $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); } + + /** + * Test clearFolder with self delete and excluded files + */ + public function testClearFolderSelfDeleteWithExclusion(): void + { + FileUtils::clearFolder('sandbox', true, ['file2']); + + static::assertFileExists('sandbox/folder1/file2'); + static::assertFileExists('sandbox/folder1'); + static::assertFileExists('sandbox/file2'); + static::assertFileExists('sandbox'); + + static::assertFileNotExists('sandbox/folder1/file1'); + static::assertFileNotExists('sandbox/file1'); + static::assertFileNotExists('sandbox/folder3'); + } + + /** + * Test clearFolder with self delete and excluded files + */ + public function testClearFolderSelfDeleteWithoutExclusion(): void + { + FileUtils::clearFolder('sandbox', true); + + static::assertFileNotExists('sandbox'); + } + + /** + * Test clearFolder with self delete and excluded files + */ + public function testClearFolderNoSelfDeleteWithoutExclusion(): void + { + FileUtils::clearFolder('sandbox', false); + + static::assertFileExists('sandbox'); + + // 2 because '.' and '..' + static::assertCount(2, new \DirectoryIterator('sandbox')); + } + + /** + * Test clearFolder on a file instead of a folder + */ + public function testClearFolderOnANonDirectory(): void + { + $this->expectException(IOException::class); + $this->expectExceptionMessage('Provided path is not a directory.'); + + FileUtils::clearFolder('sandbox/file1', false); + } + + /** + * Test clearFolder on a file instead of a folder + */ + public function testClearFolderOutsideOfShaarliDirectory(): void + { + $this->expectException(IOException::class); + $this->expectExceptionMessage('Trying to delete a folder outside of Shaarli path.'); + + + FileUtils::clearFolder('/tmp/shaarli-to-delete', true); + } } diff --git a/tests/front/controller/admin/ServerControllerTest.php b/tests/front/controller/admin/ServerControllerTest.php new file mode 100644 index 00000000..355cce7d --- /dev/null +++ b/tests/front/controller/admin/ServerControllerTest.php @@ -0,0 +1,184 @@ +createContainer(); + + $this->controller = new ServerController($this->container); + + // initialize dummy cache + @mkdir('sandbox/'); + foreach (['pagecache', 'tmp', 'cache'] as $folder) { + @mkdir('sandbox/' . $folder); + @touch('sandbox/' . $folder . '/.htaccess'); + @touch('sandbox/' . $folder . '/1'); + @touch('sandbox/' . $folder . '/2'); + } + } + + public function tearDown(): void + { + foreach (['pagecache', 'tmp', 'cache'] as $folder) { + @unlink('sandbox/' . $folder . '/.htaccess'); + @unlink('sandbox/' . $folder . '/1'); + @unlink('sandbox/' . $folder . '/2'); + @rmdir('sandbox/' . $folder); + } + } + + /** + * Test default display of server administration page. + */ + public function testIndex(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + + // Save RainTPL assigned variables + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $result = $this->controller->index($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertSame('server', (string) $result->getBody()); + + static::assertSame(PHP_VERSION, $assignedVariables['php_version']); + static::assertArrayHasKey('php_has_reached_eol', $assignedVariables); + static::assertArrayHasKey('php_eol', $assignedVariables); + static::assertArrayHasKey('php_extensions', $assignedVariables); + static::assertArrayHasKey('permissions', $assignedVariables); + static::assertEmpty($assignedVariables['permissions']); + + static::assertRegExp( + '#https://github\.com/shaarli/Shaarli/releases/tag/v\d+\.\d+\.\d+#', + $assignedVariables['release_url'] + ); + static::assertRegExp('#v\d+\.\d+\.\d+#', $assignedVariables['latest_version']); + static::assertRegExp('#(v\d+\.\d+\.\d+|dev)#', $assignedVariables['current_version']); + static::assertArrayHasKey('index_url', $assignedVariables); + static::assertArrayHasKey('client_ip', $assignedVariables); + static::assertArrayHasKey('trusted_proxies', $assignedVariables); + + static::assertSame('Server administration - Shaarli', $assignedVariables['pagetitle']); + } + + /** + * Test clearing the main cache + */ + public function testClearMainCache(): void + { + $this->container->conf = $this->createMock(ConfigManager::class); + $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { + if ($key === 'resource.page_cache') { + return 'sandbox/pagecache'; + } elseif ($key === 'resource.raintpl_tmp') { + return 'sandbox/tmp'; + } elseif ($key === 'resource.thumbnails_cache') { + return 'sandbox/cache'; + } else { + return $default; + } + }); + + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->with(SessionManager::KEY_SUCCESS_MESSAGES, ['Shaarli\'s cache folder has been cleared!']) + ; + + $request = $this->createMock(Request::class); + $request->method('getQueryParam')->with('type')->willReturn('main'); + $response = new Response(); + + $result = $this->controller->clearCache($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame('/subfolder/admin/server', (string) $result->getHeaderLine('Location')); + + static::assertFileNotExists('sandbox/pagecache/1'); + static::assertFileNotExists('sandbox/pagecache/2'); + static::assertFileNotExists('sandbox/tmp/1'); + static::assertFileNotExists('sandbox/tmp/2'); + + static::assertFileExists('sandbox/pagecache/.htaccess'); + static::assertFileExists('sandbox/tmp/.htaccess'); + static::assertFileExists('sandbox/cache'); + static::assertFileExists('sandbox/cache/.htaccess'); + static::assertFileExists('sandbox/cache/1'); + static::assertFileExists('sandbox/cache/2'); + } + + /** + * Test clearing thumbnails cache + */ + public function testClearThumbnailsCache(): void + { + $this->container->conf = $this->createMock(ConfigManager::class); + $this->container->conf->method('get')->willReturnCallback(function (string $key, $default) { + if ($key === 'resource.page_cache') { + return 'sandbox/pagecache'; + } elseif ($key === 'resource.raintpl_tmp') { + return 'sandbox/tmp'; + } elseif ($key === 'resource.thumbnails_cache') { + return 'sandbox/cache'; + } else { + return $default; + } + }); + + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->willReturnCallback(function (string $key, array $value): SessionManager { + static::assertSame(SessionManager::KEY_WARNING_MESSAGES, $key); + static::assertCount(1, $value); + static::assertStringStartsWith('Thumbnails cache has been cleared.', $value[0]); + + return $this->container->sessionManager; + }); + ; + + $request = $this->createMock(Request::class); + $request->method('getQueryParam')->with('type')->willReturn('thumbnails'); + $response = new Response(); + + $result = $this->controller->clearCache($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame('/subfolder/admin/server', (string) $result->getHeaderLine('Location')); + + static::assertFileNotExists('sandbox/cache/1'); + static::assertFileNotExists('sandbox/cache/2'); + + static::assertFileExists('sandbox/cache/.htaccess'); + static::assertFileExists('sandbox/pagecache'); + static::assertFileExists('sandbox/pagecache/.htaccess'); + static::assertFileExists('sandbox/pagecache/1'); + static::assertFileExists('sandbox/pagecache/2'); + static::assertFileExists('sandbox/tmp'); + static::assertFileExists('sandbox/tmp/.htaccess'); + static::assertFileExists('sandbox/tmp/1'); + static::assertFileExists('sandbox/tmp/2'); + } +} diff --git a/tests/front/controller/visitor/InstallControllerTest.php b/tests/front/controller/visitor/InstallControllerTest.php index 345ad544..2105ed77 100644 --- a/tests/front/controller/visitor/InstallControllerTest.php +++ b/tests/front/controller/visitor/InstallControllerTest.php @@ -79,6 +79,15 @@ class InstallControllerTest extends TestCase static::assertIsArray($assignedVariables['languages']); static::assertSame('Automatic', $assignedVariables['languages']['auto']); static::assertSame('French', $assignedVariables['languages']['fr']); + + static::assertSame(PHP_VERSION, $assignedVariables['php_version']); + static::assertArrayHasKey('php_has_reached_eol', $assignedVariables); + static::assertArrayHasKey('php_eol', $assignedVariables); + static::assertArrayHasKey('php_extensions', $assignedVariables); + static::assertArrayHasKey('permissions', $assignedVariables); + static::assertEmpty($assignedVariables['permissions']); + + static::assertSame('Install Shaarli', $assignedVariables['pagetitle']); } /** diff --git a/tpl/default/install.html b/tpl/default/install.html index a506a2eb..4f98d49d 100644 --- a/tpl/default/install.html +++ b/tpl/default/install.html @@ -163,6 +163,16 @@ + +
+
+
+

{'Server requirements'|t}

+ + {include="server.requirements"} +
+
+ {include="page.footer"} diff --git a/tpl/default/server.html b/tpl/default/server.html new file mode 100644 index 00000000..de1c8b53 --- /dev/null +++ b/tpl/default/server.html @@ -0,0 +1,129 @@ + + + + {include="includes"} + + +{include="page.header"} + +
+
+
+

{'Server administration'|t}

+ +

{'General'|t}

+ +
+
+

{'Index URL'|t}

+
+ +
+
+
+

{'Base path'|t}

+
+
+

{$base_path}

+
+
+
+
+

{'Client IP'|t}

+
+
+

{$client_ip}

+
+
+
+
+

{'Trusted reverse proxies'|t}

+
+
+ {if="count($trusted_proxies) > 0"} +

+ {loop="$trusted_proxies"} + {$value}
+ {/loop} +

+ {else} +

{'N/A'|t}

+ {/if} +
+
+ + {include="server.requirements"} + +

Version

+ +
+
+

Current version

+
+
+

{$current_version}

+
+
+ +
+
+

Latest release

+
+ +
+ +

Thumbnails

+ +
+
+

Thumbnails status

+
+
+

+ {if="$thumbnails_mode==='all'"} + {'All'|t} + {elseif="$thumbnails_mode==='common'"} + {'Only common media hosts'|t} + {else} + {'None'|t} + {/if} +

+
+
+ + {if="$thumbnails_mode!=='none'"} + + {/if} + +

Cache

+ + + + +
+
+ +{include="page.footer"} + + + diff --git a/tpl/default/server.requirements.html b/tpl/default/server.requirements.html new file mode 100644 index 00000000..85def9b7 --- /dev/null +++ b/tpl/default/server.requirements.html @@ -0,0 +1,68 @@ +
+

{'Permissions'|t}

+ + {if="count($permissions) > 0"} +

+ + {'There are permissions that need to be fixed.'|t} +

+ +

+ {loop="$permissions"} +

{$value}
+ {/loop} +

+ {else} +

+ + {'All read/write permissions are properly set.'|t} +

+ {/if} + +

PHP

+ +

+ {'Running PHP'|t} {$php_version} + {if="$php_has_reached_eol"} +
+ {'End of life: '|t} {$php_eol} + {else} +
+ {/if} +

+ + + + + + + + + + + + {loop="$php_extensions"} + + + + + + + {/loop} + +
{'Extension'|t}{'Usage'|t}{'Status'|t}{'Loaded'|t}
{$value.name}{$value.desc}{$value.required ? t('Required') : t('Optional')} + {if="$value.loaded"} + {$classLoaded="fa-color-green"} + {$strLoaded=t('Loaded')} + {else} + {$strLoaded=t('Not loaded')} + {if="$value.required"} + {$classLoaded="fa-color-red"} + {else} + {$classLoaded="fa-color-orange"} + {/if} + {/if} + + +
+
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index 2cb08e38..2df73598 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html @@ -20,6 +20,12 @@ {'Plugin administration'|t} + {if="!$openshaarli"} - {if="$thumbnails_enabled"} - - {/if} - {loop="$tools_plugin"}
{$value}