Merge pull request #1604 from ArthurHoaro/feature/server-admin-page

Feature: add a Server administration page
This commit is contained in:
ArthurHoaro 2020-10-27 19:29:43 +01:00 committed by GitHub
commit e6215a2ad9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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 @@ public static function checkUpdate(
// 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) {
@ -172,34 +173,44 @@ public static function checkPHPVersion($minVersion, $curVersion)
* Checks Shaarli has the proper access permissions to its resources
*
* @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(
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'),
) as $path) {
];
}
foreach ($folders as $path) {
if (!is_readable(realpath($path))) {
$errors[] = '"' . $path . '" ' . t('directory is not readable');
}
@ -208,6 +219,10 @@ public static function checkResourcePermissions($conf)
}
}
if ($minimalMode) {
return $errors;
}
// Check configuration files are readable and writable
foreach (array(
$conf->getConfigFileExt(),
@ -246,4 +261,54 @@ public static function getVersionHash($currentVersion, $salt)
{
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 @@ public static function readFlatDB($file, $default = null)
)
);
}
/**
* 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,17 +169,25 @@ public function permalink(Request $request, Response $response, array $args): Re
*/
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()
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->shouldUpdateThumbnail()
) {
$bookmark->setThumbnail($this->container->thumbnailer->get($bookmark->getUrl()));
$this->container->bookmarkService->set($bookmark, $writeDatastore);
return true;
}
}
return false;
}

View file

@ -53,6 +53,16 @@ public function index(Request $request, Response $response): Response
$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 @@ public function save(Request $request, Response $response): Response
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

@ -143,6 +143,8 @@
$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 @@ public function testCheckCurrentResourcePermissionsErrors()
);
}
/**
* 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 @@ public function testCheckUpdateDev()
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 @@ public function testReadNotReadable()
$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 @@ public function testInstallIndexWithValidSession(): void
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 @@ <h2 class="window-title">{'Install Shaarli'|t}</h2>
</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 @@ <h2 class="window-title">{'Settings'|t}</h2>
<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 @@ <h2 class="window-title">{'Settings'|t}</h2>
</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}