Feature: add a Server administration page

It contains mostly read only information about the current Shaarli instance,
PHP version, extensions, file and folder permissions, etc.
Also action buttons to clear the cache or sync thumbnails.

Part of the content of this page is also displayed on the install page,
to check server requirement before installing Shaarli config file.

Fixes #40
Fixes #185
This commit is contained in:
ArthurHoaro 2020-10-21 13:12:15 +02:00
parent d8030c8155
commit 0cf76ccb47
16 changed files with 1086 additions and 104 deletions

View file

@ -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 = '<?php /* ';
private static $VERSION_END_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');
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use Shaarli\ApplicationUtils;
use Shaarli\FileUtils;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Slim controller used to handle Server administration page, and actions.
*/
class ServerController extends ShaarliAdminController
{
/** @var string Cache type - main - by default pagecache/ and tmp/ */
protected const CACHE_MAIN = 'main';
/** @var string Cache type - thumbnails - by default cache/ */
protected const CACHE_THUMB = 'thumbnails';
/**
* GET /admin/server - Display page Server administration
*/
public function index(Request $request, Response $response): Response
{
$latestVersion = 'v' . ApplicationUtils::getVersion(
ApplicationUtils::$GIT_RAW_URL . '/latest/' . ApplicationUtils::$VERSION_FILE
);
$currentVersion = ApplicationUtils::getVersion('./shaarli_version.php');
$currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion;
$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('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.') . ' ' .
'<a href="'. $this->container->basePath .'/admin/thumbnails">' . t('Please synchronize them.') .'</a>'
);
} 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');
}
}

View file

@ -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);
if (false === $this->container->loginManager->isLoggedIn()) {
return false;
}
return true;
// 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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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 ""
"<pre>Sessions do not seem to work correctly on your server.<br>Make sure the "
@ -523,14 +583,14 @@ msgstr ""
"des cookies. Nous vous recommandons d'accéder à votre serveur depuis son "
"adresse IP ou un <em>Fully Qualified Domain Name</em>.<br>"
#: 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"

View file

@ -128,6 +128,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');

View file

@ -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')
);
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use Shaarli\Config\ConfigManager;
use Shaarli\Security\SessionManager;
use Shaarli\TestCase;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Test Server administration controller.
*/
class ServerControllerTest extends TestCase
{
use FrontAdminControllerMockHelper;
/** @var ServerController */
protected $controller;
public function setUp(): void
{
$this->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');
}
}

View file

@ -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']);
}
/**

View file

@ -163,6 +163,16 @@
</div>
</div>
</form>
<div class="pure-g">
<div class="pure-u-lg-1-6 pure-u-1-24"></div>
<div class="pure-u-lg-2-3 pure-u-22-24 page-form page-form-complete">
<h2 class="window-title">{'Server requirements'|t}</h2>
{include="server.requirements"}
</div>
</div>
{include="page.footer"}
</body>
</html>

129
tpl/default/server.html Normal file
View file

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="pure-g">
<div class="pure-u-lg-1-4 pure-u-1-24"></div>
<div class="pure-u-lg-1-2 pure-u-22-24 page-form server-tables-page">
<h2 class="window-title">{'Server administration'|t}</h2>
<h3 class="window-subtitle">{'General'|t}</h3>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>{'Index URL'|t}</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p><a href="{$index_url}" title="{$pagetitle}">{$index_url}</a></p>
</div>
</div>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>{'Base path'|t}</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p>{$base_path}</p>
</div>
</div>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>{'Client IP'|t}</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p>{$client_ip}</p>
</div>
</div>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>{'Trusted reverse proxies'|t}</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
{if="count($trusted_proxies) > 0"}
<p>
{loop="$trusted_proxies"}
{$value}<br>
{/loop}
</p>
{else}
<p>{'N/A'|t}</p>
{/if}
</div>
</div>
{include="server.requirements"}
<h3 class="window-subtitle">Version</h3>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>Current version</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p>{$current_version}</p>
</div>
</div>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>Latest release</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p>
<a href="{$release_url}" title="{'Visit releases page on Github'|t}">
{$latest_version}
</a>
</p>
</div>
</div>
<h3 class="window-subtitle">Thumbnails</h3>
<div class="pure-g server-row">
<div class="pure-u-lg-1-2 pure-u-1 server-label">
<p>Thumbnails status</p>
</div>
<div class="pure-u-lg-1-2 pure-u-1">
<p>
{if="$thumbnails_mode==='all'"}
{'All'|t}
{elseif="$thumbnails_mode==='common'"}
{'Only common media hosts'|t}
{else}
{'None'|t}
{/if}
</p>
</div>
</div>
{if="$thumbnails_mode!=='none'"}
<div class="center tools-item">
<a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span>
</a>
</div>
{/if}
<h3 class="window-subtitle">Cache</h3>
<div class="center tools-item">
<a href="{$base_path}/admin/clear-cache?type=main">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear main cache</span>
</a>
</div>
<div class="center tools-item">
<a href="{$base_path}/admin/clear-cache?type=thumbnails">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">Clear thumbnails cache</span>
</a>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>

View file

@ -0,0 +1,68 @@
<div class="server-tables">
<h3 class="window-subtitle">{'Permissions'|t}</h3>
{if="count($permissions) > 0"}
<p class="center">
<i class="fa fa-close fa-color-red" aria-hidden="true"></i>
{'There are permissions that need to be fixed.'|t}
</p>
<p>
{loop="$permissions"}
<div class="center">{$value}</div>
{/loop}
</p>
{else}
<p class="center">
<i class="fa fa-check fa-color-green" aria-hidden="true"></i>
{'All read/write permissions are properly set.'|t}
</p>
{/if}
<h3 class="window-subtitle">PHP</h3>
<p class="center">
<strong>{'Running PHP'|t} {$php_version}</strong>
{if="$php_has_reached_eol"}
<i class="fa fa-circle fa-color-orange" aria-label="hidden"></i><br>
{'End of life: '|t} {$php_eol}
{else}
<i class="fa fa-circle fa-color-green" aria-label="hidden"></i><br>
{/if}
</p>
<table class="center">
<thead>
<tr>
<th>{'Extension'|t}</th>
<th>{'Usage'|t}</th>
<th>{'Status'|t}</th>
<th>{'Loaded'|t}</th>
</tr>
</thead>
<tbody>
{loop="$php_extensions"}
<tr>
<td>{$value.name}</td>
<td>{$value.desc}</td>
<td>{$value.required ? t('Required') : t('Optional')}</td>
<td>
{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}
<i class="fa fa-circle {$classLoaded}" aria-label="{$strLoaded}" title="{$strLoaded}"></i>
</td>
</tr>
{/loop}
</tbody>
</table>
</div>

View file

@ -20,6 +20,12 @@
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span>
</a>
</div>
<div class="tools-item">
<a href="{$base_path}/admin/server"
title="{'Check instance\'s server configuration'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Server administration'|t}</span>
</a>
</div>
{if="!$openshaarli"}
<div class="tools-item">
<a href="{$base_path}/admin/password" title="{'Change your password'|t}">
@ -45,14 +51,6 @@
</a>
</div>
{if="$thumbnails_enabled"}
<div class="tools-item">
<a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Synchronize thumbnails'|t}</span>
</a>
</div>
{/if}
{loop="$tools_plugin"}
<div class="tools-item">
{$value}