Restore 10602 in master

branche_v10
fredtempez 2 years ago
parent 33d2a4d8c4
commit e6789bb02a

@ -1,22 +1,5 @@
# Changelog
## Version 11.0.00
- Modifications :
- Gestion multi-langues
- Traduction rédigée dans une langue européenne
- Traduction automatique dans une langue européenne
- Traduction selon la langue navigateur
- Configuration du site :
- Activation et désactivation de la déconnexion automatique empêchant plusieurs sessions avec le même compte.
- Suppression de l'option articles par pages.
- Suppression de l'option de backup individuel des fichiers de données, pour l'activer manuellement, créez un fichier vide .backup dans site/data
- Nouvelle option permettant de transférer les fichiers de sauvegarde automatiques dans le gestionnaire de fichiers.
## version 10.6.02
Correction :
- Bug lors du téléchargement des journaux.
## version 10.6.01
Corrections :
- Champs de formulaire, uniformisation de la hauteur (select, input, etc) identique à celle des boutons à 9px.

@ -1,6 +1,5 @@
# ZwiiCMS 10.6.02
# ZwiiCMS 10.6.01
Zwii est un CMS sans base de données (flat-file) qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation.

@ -482,6 +482,7 @@ $(document).ready(function(){
* Active le système d'aide interne
*
*/
$(".buttonHelp").on({
click: function () {
$(".helpDisplayContent").slideToggle();
@ -495,7 +496,7 @@ $(document).ready(function(){
});
/**
* Remove ID Facebook from URL
* Remove ID Facebook from URL
*/
if(/^\?fbclid=/.test(location.search))
location.replace(location.href.replace(/\?fbclid.+/, ""));

File diff suppressed because it is too large Load Diff

@ -29,7 +29,7 @@ body {
}
@media screen and (min-width: 769px) {
@media (min-width: 769px) {
body {
/*margin:0px 10px;*/
margin: 0;
@ -41,7 +41,7 @@ body {
/**
* Petits écrans inférieurs à 768px de largeur, on supprime les marges
*/
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
body {
margin: 0px;
}
@ -65,7 +65,7 @@ body {
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
.siteContainer {
display: flex;
flex-direction: column;
@ -237,12 +237,12 @@ td>.col12 {
}
/* Tableau sur les écrans de petites tailles */
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
.table thead {
font-size: 0.8em;
}
}
@media screen and (max-width: 668px) {
@media (max-width: 668px) {
.table thead {
display:none;
}
@ -408,7 +408,7 @@ td>.col12 {
background-color: rgba(255, 255, 255, 1);
}
@media screen and (min-width: 769px) {
@media (min-width: 769px) {
#bar #barLeft {
float: left;
}
@ -419,7 +419,7 @@ td>.col12 {
}
}
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
#bar {
text-align: center;
padding: 0 1;
@ -455,7 +455,7 @@ td>.col12 {
overflow: hidden;
}
/* Dans theme.css
@media screen and (min-width:768px) {
@media (min-width:768px) {
#site {
margin: 20px auto;
}
@ -464,7 +464,7 @@ td>.col12 {
/* Bannière */
@media screen and (min-width:768px) {
@media (min-width:768px) {
body>header {
margin: 0;
/*-10px;*/
@ -598,13 +598,13 @@ nav::before {
display: flex;
}
@media screen and (min-width: 769px) {
@media (min-width: 769px) {
nav #menu {
display: block;
}
}
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
body>nav {
margin: 0;
}
@ -691,66 +691,41 @@ li .menuSideChild {
/* Container des drapeaux */
#i18nContainerNav, #i18nContainerSite {
#i18nContainer {
z-index: 100;
position: relative ;
float: right;
width: auto;
height: auto;
margin-top: -25px;
}
#i18nContainerNav ul, #i18nContainerSite ul {
#i18nContainer ul {
list-style: none;
}
#i18nContainerNav li, #i18nContainerSite li {
#i18nContainer li {
display: inline-block;
position: relative;
}
#i18nFlag {
width: 70%;
#i18nContainer img {
width: 80%;
}
#i18nFlagSelected {
width: 100%;
}
/*
* Position du bloc dans le site sur les petits écrans
*/
#i18nContainerNav {
display: block;
}
#i18nContainerSite {
display: none;
#i18nContainer img {
margin: 2px;
}
@media screen and (max-width:1024px){
#i18nContainerNav {
display: none;
}
#i18nContainerSite {
display: block;
margin-top: -10px;
}
#i18nFlag {
width: 100%;
padding: 5px;
}
#i18nFlagSelected {
width: 130%;
padding: 5px;
}
}
/* Corps */
@media screen and (min-width:768px) {
@media (min-width:768px) {
section {
padding: 20px;
}
@ -847,7 +822,7 @@ footer #footerbody>div {
}
/* Conserve le pied de page sur une ligne */
@media screen and (max-width: 768px) {
@media (max-width: 768px) {
body>footer {
margin: 0;
}
@ -1056,7 +1031,6 @@ footer #footerSocials .zwiico-github:hover {
user-select: none;
}
/* Lightbox */
.lightbox {
background: #FFF;
@ -1393,7 +1367,7 @@ input[type='checkbox']:disabled+label:before {
display: inline-block;
}
@media screen and (min-width: 769px) {
@media (min-width: 769px) {
.col1 {
width: 8.33333333%;
}
@ -1582,7 +1556,7 @@ th.col12 {
}
/* Tableau sur les écrans de très petites tailles */
@media screen and (max-width: 480px){
@media (max-width: 480px){
.table tr{
display: block;
margin-bottom: 10px;
@ -1731,8 +1705,8 @@ th.col12 {
display: none;
width: 100%;
padding: 10px 10px;
-webkit-box-shadow: 5px 5px 11px 0px #222222;
box-shadow: 5px 5px 11px 0px #222222;
border-radius: 5px;
z-index: 30;
-webkit-box-shadow: 5px 5px 11px 0px #222222;
box-shadow: 5px 5px 11px 0px #222222;
border-radius: 5px;
z-index: 30;
}

@ -1,22 +0,0 @@
/**
* This file is part of Zwii.
* For full copyright and license information, please see the LICENSE
* file that was distributed with this source code.
*
* @author Rémi Jean <remi.jean@outlook.com>
* @copyright Copyright (C) 2008-2018, Rémi Jean
* @author Frédéric Tempez <frederic.tempez@outlook.com>
* @copyright Copyright (C) 2018-2021, Frédéric Tempez
* @license GNU General Public License, version 3
* @link http://zwiicms.fr/
*/
/**
* Éléments spécifiques
*/
/* Site */
section {
min-height: 0px;
}

@ -1,27 +0,0 @@
<?php $layout = new layout($this);
$lan = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); ?>
<!DOCTYPE html>
<html prefix="og: http://ogp.me/ns#" lang="<?php echo $lan;?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php $layout->showMetaTitle(); ?>
<?php $layout->showMetaDescription(); ?>
<?php $layout->showMetaType(); ?>
<?php $layout->showMetaImage(); ?>
<?php $layout->showFavicon(); ?>
<?php $layout->showVendor(); ?>
<?php $layout->showStyle(); ?>
<link rel="stylesheet" href="<?php echo helper::baseUrl(false); ?>core/layout/common.css">
<link rel="stylesheet" href="<?php echo helper::baseUrl(false); ?>core/layout/lity.css">
<link rel="stylesheet" href="<?php echo helper::baseUrl(false) . self::DATA_DIR; ?>theme.css?<?php echo md5_file(self::DATA_DIR.'theme.css'); ?>">
<link rel="stylesheet" href="<?php echo helper::baseUrl(false) . self::DATA_DIR; ?>custom.css?<?php echo md5_file(self::DATA_DIR.'custom.css'); ?>">
</head>
<body>
<?php $layout->showNotification(); ?>
<div id="site" class="container">
<section><?php $layout->showContent(); ?></section>
</div>
<?php $layout->showScript(); ?>
</body>
</html>

@ -36,7 +36,7 @@
<?php
if ( $this->getData(['theme', 'menu', 'position']) === 'top'
AND $this->getData(['theme', 'menu', 'fixed']) === true
AND $this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD',true)
AND $this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD',true)
AND $this->getUser('group') > self::GROUP_MEMBER) {
echo '<nav id="navfixedconnected" >';
} else {
@ -59,6 +59,7 @@
<?php if($this->getData(['theme', 'header', 'position']) === 'body'): ?>
<!-- Bannière dans le fond du site -->
<header>
<?php //$layout->showi18n();?>
<?php
if ($this->getData(['theme','header','linkHomePage'])){
echo "<a href='" . helper::baseUrl(false) . "'>" ;} ?>
@ -152,6 +153,7 @@
<?php endif; ?>
<!-- Corps de page -->
<section>
<?php //$layout->showi18n();?>
<?php
// Gabarit :
// Récupérer la config de la page courante

@ -20,33 +20,21 @@ class addon extends common {
public static $actions = [
'index' => self::GROUP_ADMIN,
'delete' => self::GROUP_ADMIN,
'moduleDelete' => self::GROUP_ADMIN,
'export' => self::GROUP_ADMIN,
'import' => self::GROUP_ADMIN,
'store' => self::GROUP_ADMIN,
'item' => self::GROUP_ADMIN,
'upload' => self::GROUP_ADMIN,
'uploadItem'=> self::GROUP_ADMIN
'import' => self::GROUP_ADMIN
];
const URL_STORE = 'http://zwiicms.fr/?modules/';
const BASEURL_STORE = 'http://zwiicms.fr/';
// Gestion des modules
public static $modInstal = [];
// pour tests
public static $valeur = [];
// le catalogue
public static $storeList = [];
public static $storeItem = [];
/*
* Effacement d'un module installé et non utilisé
*/
public function delete() {
public function moduleDelete() {
// Jeton incorrect
if ($this->getUrl(3) !== $_SESSION['csrf']) {
@ -84,270 +72,6 @@ class addon extends common {
}
}
/***
* Installation d'un module
* Fonction utilisée par upload et storeUpload
*/
private function install ($moduleName, $checkValid){
$tempFolder = 'datamodules';//uniqid();
$zip = new ZipArchive();
if ($zip->open($moduleName) === TRUE) {
$notification = 'Archive ouverte';
mkdir (self::TEMP_DIR . $tempFolder);
$zip->extractTo(self::TEMP_DIR . $tempFolder );
// Archive de module ?
$success = false;
$notification = 'Ce n\'est pas l\'archive d\'un module !';
$moduleDir = self::TEMP_DIR . $tempFolder . '/module';
$moduleName = '';
if ( is_dir( $moduleDir )) {
// Lire le nom du module
if ($dh = opendir( $moduleDir )) {
while ( false !== ($file = readdir($dh)) ) {
if ($file != "." && $file != "..") {
$moduleName = $file;
}
}
closedir($dh);
}
// Module normalisé ?
if( is_file( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php' ) AND is_file( $moduleDir.'/'.$moduleName.'/view/index/index.php' ) ){
// Lecture de la version et de la validation d'update du module pour validation de la mise à jour
// Pour une version <= version installée l'utilisateur doit cocher 'Mise à jour forcée'
$version = '0.0';
$update = '0.0';
$valUpdate = false;
$file = file_get_contents( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php');
$file = str_replace(' ','',$file);
$file = str_replace("\t",'',$file);
$pos1 = strpos($file, 'constVERSION');
if( $pos1 !== false){
$posdeb = strpos($file, "'", $pos1);
$posend = strpos($file, "'", $posdeb + 1);
$version = substr($file, $posdeb + 1, $posend - $posdeb - 1);
}
$pos1 = strpos($file, 'constUPDATE');
if( $pos1 !== false){
$posdeb = strpos($file, "'", $pos1);
$posend = strpos($file, "'", $posdeb + 1);
$update = substr($file, $posdeb + 1, $posend - $posdeb - 1);
}
// Si version actuelle >= version indiquée dans UPDATE la mise à jour est validée
$infoModules = helper::getModules();
if( $infoModules[$moduleName]['update'] >= $update ) $valUpdate = true;
// Module déjà installé ?
$moduleInstal = false;
foreach($infoModules as $key=>$value ){
if($moduleName === $key){
$moduleInstal = true;
}
}
// Validation de la maj si autorisation du concepteur du module ET
// ( Version plus récente OU Check de forçage )
$valNewVersion = floatval($version);
$valInstalVersion = floatval( $infoModules[$moduleName]['version'] );
$newVersion = false;
if( $valNewVersion > $valInstalVersion ) $newVersion = true;
$validMaj = $valUpdate && ( $newVersion || $checkValid);
// Nouvelle installation ou mise à jour du module
if( ! $moduleInstal || $validMaj ){
// Copie récursive des dossiers
$this->copyDir( self::TEMP_DIR . $tempFolder, './' );
$success = true;
if( ! $moduleInstal ){
$notification = 'Module '.$moduleName.' installé';
}
else{
$notification = 'Module '.$moduleName.' mis à jour';
}
}
else{
$success = false;
if( $valNewVersion == $valInstalVersion){
$notification = ' Version détectée '.$version.' = à celle installée '.$infoModules[$moduleName]['version'];
}
else{
$notification = ' Version détectée '.$version.' < à celle installée '.$infoModules[$moduleName]['version'];
}
if( $valUpdate === false){
if( $infoModules[$moduleName]['update'] === $update ){
$notification = ' Mise à jour par ce procédé interdite par le concepteur du module';
}
else{
$notification = ' Mise à jour par ce procédé interdite, votre version est trop ancienne';
}
}
}
}
}
// Supprimer le dossier temporaire même si le module est invalide
$this->removeDir(self::TEMP_DIR . $tempFolder);
$zip->close();
} else {
// erreur à l'ouverture
$success = false;
$notification = 'Impossible d\'ouvrir l\'archive';
}
return(['success' => $success,
'notification'=> $notification
]);
}
/***
* Installation d'un module à partir du gestionnaire de fichier
*/
public function upload() {
// Soumission du formulaire
if($this->isPost()) {
// Installation d'un module
$checkValidMaj = $this->getInput('configModulesCheck', helper::FILTER_BOOLEAN);
$zipFilename = $this->getInput('configModulesInstallation', helper::FILTER_STRING_SHORT);
if( $zipFilename !== ''){
$success = [
'success' => false,
'notification'=> ''
];
$state = $this->install(self::FILE_DIR.'source/'.$zipFilename, $checkValidMaj);
}
$this->addOutput([
'redirect' => helper::baseUrl() . $this->getUrl(),
'notification' => $state['notification'],
'state' => $state['success']
]);
}
// Valeurs en sortie
$this->addOutput([
'title' => 'Téléverser un module',
'view' => 'upload'
]);
}
/***
* Installation d'un module par le catalogue
*/
public function uploadItem() {
// Jeton incorrect
if ($this->getUrl(3) !== $_SESSION['csrf']) {
// Valeurs en sortie
$this->addOutput([
'redirect' => helper::baseUrl() . 'store',
'state' => false,
'notification' => 'Action non autorisée'
]);
} else {
// Récupérer le module en ligne
$moduleName = $this->getUrl(2);
// Informations sur les module en ligne
$store = json_decode(helper::urlGetContents(self::URL_STORE . 'list'), true);
// Url du module à télécharger
$moduleFilePath = $store[$moduleName]['file'];
// Télécharger le fichier
$moduleData = helper::urlGetContents(self::BASEURL_STORE . self::FILE_DIR . 'source/' . $moduleFilePath);
// Extraire de l'arborescence
$d = explode('/',$moduleFilePath);
$moduleFile = $d[count($d)-1];
// Créer le dossier modules
if (!is_dir(self::FILE_DIR . 'source/modules')) {
mkdir (self::FILE_DIR . 'source/modules');
}
// Sauver les données du fichiers
file_put_contents(self::FILE_DIR . 'source/modules/' . $moduleFile, $moduleData);
/**
* $if( $moduleFile !== ''){
* $success = [
* 'success' => false,
* 'notification'=> ''
* ];
* $state = $this->install(self::FILE_DIR.'source/modules/'.$moduleFile, false);
*}
*/
$this->addOutput([
'redirect' => helper::baseUrl() . 'addon/store',
'notification' => $moduleFile . ' téléchargé dans le dossier modules du gestionnaire de fichiers',
'state' => true
]);
}
// Valeurs en sortie
$this->addOutput([
'title' => 'Catalogue de modules',
'view' => 'store'
]);
}
/**
* Catalogue des modules sur le site ZwiiCMS.fr
*/
public function store() {
$store = json_decode(helper::urlGetContents(self::URL_STORE . 'list'), true);
if ($store) {
// Modules installés
$infoModules = helper::getModules();
// Clés moduleIds dans les pages
$inPages = helper::arrayCollumn($this->getData(['page']),'moduleId', 'SORT_DESC');
foreach( $inPages as $key=>$value){
$inPagesTitle[ $this->getData(['page', $key, 'title' ]) ] = $value;
}
// Parcourir les données des modules
foreach ($store as $key=>$value) {
// Module non installé
$ico = template::ico('download');
$class = '';
// Le module est installé
if (array_key_exists($key,$infoModules) === true) {
$class = 'buttonGreen';
$ico = template::ico('update');
}
// Le module est installé et utilisé
if (in_array($key,$inPages) === true) {
$class = 'buttonRed';
$ico = template::ico('update');
}
self::$storeList [] = [
'<a href="' . self::URL_STORE . $key . '" target="_blank" >'.$store[$key]['title'].'</a>',
$store[$key]['fileVersion'],
mb_detect_encoding(strftime('%d %B %Y', $store[$key]['fileDate']), 'UTF-8', true)
? strftime('%d %B %Y', $store[$key]['fileDate'])
: utf8_encode(strftime('%d %B %Y', $store[$key]['fileDate'])),
implode(', ', array_keys($inPagesTitle,$key)),
template::button('moduleExport' . $key, [
'class' => $class,
'href' => helper::baseUrl(). $this->getUrl(0) . '/uploadItem/' . $key.'/' . $_SESSION['csrf'],// appel de fonction vaut exécution, utiliser un paramètre
'value' => $ico
])
];
}
}
// Valeurs en sortie
$this->addOutput([
'title' => 'Catalogue de modules en ligne',
'view' => 'store'
]);
}
/**
* Détail d'un objet du catalogue
*/
public function item() {
$store = json_decode(helper::urlGetContents(self::URL_STORE . 'list'), true);
self::$storeItem = $store [$this->getUrl(2)] ;
self::$storeItem ['fileDate'] = mb_detect_encoding(strftime('%d %B %Y',self::$storeItem ['fileDate']), 'UTF-8', true)
? strftime('%d %B %Y', self::$storeItem ['fileDate'])
: utf8_encode(strftime('%d %B %Y', self::$storeItem ['fileDate']));
// Valeurs en sortie
$this->addOutput([
'title' =>'Module ' . self::$storeItem['title'],
'view' => 'item'
]);
}
/**
* Gestion des modules
@ -372,11 +96,11 @@ class addon extends common {
$infoModules[$key]['realName'],
$infoModules[$key]['version'],
implode(', ', array_keys($inPagesTitle,$key)),
//|| ('delete',$infoModules[$key]) && $infoModules[$key]['delete'] === true && implode(', ',array_keys($inPages,$key)) === ''
//array_key_exists('delete',$infoModules[$key]) && $infoModules[$key]['delete'] === true && implode(', ',array_keys($inPages,$key)) === ''
$infoModules[$key]['delete'] === true && implode(', ',array_keys($inPages,$key)) === ''
? template::button('moduleDelete' . $key, [
'class' => 'moduleDelete buttonRed',
'href' => helper::baseUrl() . $this->getUrl(0) . '/delete/' . $key . '/' . $_SESSION['csrf'],
'href' => helper::baseUrl() . $this->getUrl(0) . '/moduleDelete/' . $key . '/' . $_SESSION['csrf'],
'value' => template::ico('cancel')
])
: '',
@ -395,9 +119,127 @@ class addon extends common {
];
}
// Retour du formulaire ?
if($this->isPost()) {
// Installation d'un module
$success = true;
$checkValidMaj = $this->getInput('configModulesCheck', helper::FILTER_BOOLEAN);
$zipFilename = $this->getInput('configModulesInstallation', helper::FILTER_STRING_SHORT);
if( $zipFilename !== ''){
$tempFolder = 'datamodules';//uniqid();
$zip = new ZipArchive();
if ($zip->open(self::FILE_DIR.'source/'.$zipFilename) === TRUE) {
$notification = 'Archive ouverte';
mkdir (self::TEMP_DIR . $tempFolder);
$zip->extractTo(self::TEMP_DIR . $tempFolder );
// Archive de module ?
$success = false;
$notification = 'Ce n\'est pas l\'archive d\'un module !';
$moduleDir = self::TEMP_DIR . $tempFolder . '/module';
$moduleName = '';
if ( is_dir( $moduleDir )) {
// Lire le nom du module
if ($dh = opendir( $moduleDir )) {
while ( false !== ($file = readdir($dh)) ) {
if ($file != "." && $file != "..") {
$moduleName = $file;
}
}
closedir($dh);
}
// Module normalisé ?
if( is_file( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php' ) AND is_file( $moduleDir.'/'.$moduleName.'/view/index/index.php' ) ){
// Lecture de la version et de la validation d'update du module pour validation de la mise à jour
// Pour une version <= version installée l'utilisateur doit cocher 'Mise à jour forcée'
$version = '0.0';
$update = '0.0';
$valUpdate = false;
$file = file_get_contents( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php');
$file = str_replace(' ','',$file);
$file = str_replace("\t",'',$file);
$pos1 = strpos($file, 'constVERSION');
if( $pos1 !== false){
$posdeb = strpos($file, "'", $pos1);
$posend = strpos($file, "'", $posdeb + 1);
$version = substr($file, $posdeb + 1, $posend - $posdeb - 1);
}
$pos1 = strpos($file, 'constUPDATE');
if( $pos1 !== false){
$posdeb = strpos($file, "'", $pos1);
$posend = strpos($file, "'", $posdeb + 1);
$update = substr($file, $posdeb + 1, $posend - $posdeb - 1);
}
// Si version actuelle >= version indiquée dans UPDATE la mise à jour est validée
if( $infoModules[$moduleName]['update'] >= $update ) $valUpdate = true;
// Module déjà installé ?
$moduleInstal = false;
foreach( self::$modInstal as $key=>$value){
if($moduleName === $value[0]){
$moduleInstal = true;
}
}
// Validation de la maj si autorisation du concepteur du module ET
// ( Version plus récente OU Check de forçage )
$valNewVersion = floatval($version);
$valInstalVersion = floatval( $infoModules[$moduleName]['version'] );
$newVersion = false;
if( $valNewVersion > $valInstalVersion ) $newVersion = true;
$validMaj = $valUpdate && ( $newVersion || $checkValidMaj);
// Nouvelle installation ou mise à jour du module
if( ! $moduleInstal || $validMaj ){
// Copie récursive des dossiers
$this -> custom_copy( self::TEMP_DIR . $tempFolder, './' );
$success = true;
if( ! $moduleInstal ){
$notification = 'Module '.$moduleName.' installé';
}
else{
$notification = 'Module '.$moduleName.' mis à jour';
}
}
else{
$success = false;
if( $valNewVersion == $valInstalVersion){
$notification = ' Version détectée '.$version.' = à celle installée '.$infoModules[$moduleName]['version'];
}
else{
$notification = ' Version détectée '.$version.' < à celle installée '.$infoModules[$moduleName]['version'];
}
if( $valUpdate === false){
if( $infoModules[$moduleName]['update'] === $update ){
$notification = ' Mise à jour par ce procédé interdite par le concepteur du module';
}
else{
$notification = ' Mise à jour par ce procédé interdite, votre version est trop ancienne';
}
}
}
}
}
// Supprimer le dossier temporaire même si le module est invalide
$this->removeDir(self::TEMP_DIR . $tempFolder);
$zip->close();
} else {
// erreur à l'ouverture
$success = false;
$notification = 'Impossible d\'ouvrir l\'archive';
}
}
$this->addOutput([
'redirect' => helper::baseUrl() . $this->getUrl(),
'notification' => $notification,
'state' => $success
]);
}
// Valeurs en sortie
$this->addOutput([
'title' => 'Modules installés',
'title' => 'Gestion des modules',
'view' => 'index'
]);
}
@ -462,7 +304,7 @@ class addon extends common {
// Export des données localisées dans le dossier de données du module
if ($infoModules[$this->getUrl(2)]['dataDirectory'] &&
is_dir($infoModules[$this->getUrl(2)]['dataDirectory'])) {
$this->copyDir ($infoModules[$this->getUrl(2)]['dataDirectory'], $tmpFolder . '/' . $moduleDir);
$this->custom_copy ($infoModules[$this->getUrl(2)]['dataDirectory'], $tmpFolder . '/' . $moduleDir);
}
}
// Enregistrement des pages dans le dossier de langue identique à module
@ -473,13 +315,14 @@ class addon extends common {
$fileName = $this->getUrl(2) . '.zip';
$this->makeZip ($fileName, $tmpFolder, []);
if (file_exists($fileName)) {
ob_start();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($fileName));
ob_clean();
ob_end_flush();
readfile( $fileName);
// Valeurs en sortie
$this->addOutput([
'display' => self::DISPLAY_RAW
]);
unlink($fileName);
$this->removeDir($tmpFolder);
exit();
@ -507,7 +350,7 @@ class addon extends common {
'notification' => 'Action non autorisée'
]);
}
else {
else{
// Soumission du formulaire
if($this->isPost()) {
// Récupérer le fichier et le décompacter
@ -553,7 +396,7 @@ class addon extends common {
}
// Import des fichiers placés ailleurs que dans les dossiers localisés.
$this->copyDir (self::TEMP_DIR . $tempFolder,self::DATA_DIR );
$this->custom_copy (self::TEMP_DIR . $tempFolder,self::DATA_DIR );
// Supprimer le dossier temporaire
$this->removeDir(self::TEMP_DIR . $tempFolder);
@ -581,4 +424,31 @@ class addon extends common {
}
}
/*
* Copie récursive de dossiers
*
*/
private function custom_copy($src, $dst) {
// open the source directory
$dir = opendir($src);
// Make the destination directory if not exist
if (!is_dir($dst)) {
mkdir($dst);
}
// Loop through the files in source directory
while( $file = readdir($dir) ) {
if (( $file != '.' ) && ( $file != '..' )) {
if ( is_dir($src . '/' . $file) ){
// Recursively calling custom copy function
// for sub directory
$this -> custom_copy($src . '/' . $file, $dst . '/' . $file);
}
else {
copy($src . '/' . $file, $dst . '/' . $file);
}
}
}
closedir($dir);
}
}

@ -1,5 +1,5 @@
<h3>IMPORTER OU METTRE A JOUR</h3>
<p>Vous avez au préalable chargé le fichier zip du module sur votre serveur par 'Gérer les fichiers'.</p>
<p>Vous avez au préalable charger le fichier zip du module sur votre serveur par 'Gérer les fichiers'.</p>
<p>Lors d'une mise à jour Zwii contrôle la version du module à installer, pour réinstaller un module de même numéro de version vous devez cocher 'Mise à jour forcée'.
Il est déconseillé d'installer un module plus ancien.</p>
<h3>EXPORTER IMPORTER</h3>

@ -1,36 +1,57 @@
<div class="row">
<div class="col2">
<?php echo template::button('configModulesBack', [
'class' => 'buttonGrey',
'href' => helper::baseUrl(),
'ico' => 'left',
'value' => 'Retour'
]); ?>
<?php echo template::formOpen('configModulesGestion'); ?>
<div class="row">
<div class="col2">
<?php echo template::button('configModulesBack', [
'class' => 'buttonGrey',
'href' => helper::baseUrl(),
'ico' => 'left',
'value' => 'Retour'
]); ?>
</div>
<div class="col2">
<?php echo template::button('addonIndexHelp', [
'class' => 'buttonHelp',
'ico' => 'help',
'value' => 'Aide'
]); ?>
</div>
<div class="col2 offset6">
<?php echo template::submit('configModulesSubmit',[
'value' => 'Valider',
'ico' => 'check'
]); ?>
</div>
</div>
<!-- Aide à propos de la gestion des modules, view index -->
<div class="helpDisplayContent">
<?php echo file_get_contents( 'core/module/addon/view/index/index.help.html') ;?>
</div>
<div class="row">
<div class="col12">
<div class="block">
<h4>Installer ou mettre à jour un module </h4>
<div class="row">
<div class="col6 offset3">
<?php echo template::file('configModulesInstallation', [
'label' => 'Archive ZIP :',
'type' => 2
]); ?>
</div>
</div>
<div class="row">
<div class="col6">
<?php echo template::checkbox('configModulesCheck', true, 'Mise à jour forcée', [
'checked' => false,
'help' => 'Permet de forcer une mise à jour même si la version du module est inférieure ou égale à celle du module installé.',
]); ?>
</div>
</div>
</div>
</div>
</div>
<div class="col2">
<?php echo template::button('addonIndexHelp', [
'class' => 'buttonHelp',
'ico' => 'help',
'value' => 'Aide'
]); ?>
</div>
<div class="col3 offset2">
<?php echo template::button('configStoreUpload', [
'href' => helper::baseUrl() . 'addon/upload',
'value' => 'Téléverser un module'
]); ?>
</div>
<div class="col3">
<?php echo template::button('configModulesStore', [
'href' => helper::baseUrl() . 'addon/store',
'value' => 'Catalogue en ligne'
]); ?>
</div>
</div>
<!-- Aide à propos de la gestion des modules, view index -->
<div class="helpDisplayContent">
<?php echo file_get_contents( 'core/module/addon/view/index/index.help.html') ;?>
</div>
<?php echo template::formClose(); ?>
<?php if($module::$modInstal): ?>
<?php echo template::table([2, 2, 2, 2, 1, 1, 1], $module::$modInstal, ['Module installé', 'Alias', 'Version', 'Page(s)', 'Supprimer', 'Exporter', 'Importer']); ?>
<?php else: ?>

@ -1,14 +0,0 @@
/**
* This file is part of Zwii.
*
* For full copyright and license information, please see the LICENSE
* file that was distributed with this source code.
*
* @author Rémi Jean <remi.jean@outlook.com>
* @copyright Copyright (C) 2008-2018, Rémi Jean
* @author Frédéric Tempez <frederic.tempez@outlook.com>
* @copyright Copyright (C) 2018-2021, Frédéric Tempez
* @license GNU General Public License, version 3
* @link http://zwiicms.fr/
*/

@ -1,41 +0,0 @@
<div class="row">
<div class="col9">
<div class="row">
<div class="col12">
<?php echo $module::$storeItem['content']; ?>
</div>
</div>
</div>
<div class="row">
<div class="col12">
<?php
echo '<img class="downloadItemPicture" src="' . $module::BASEURL_STORE . 'site/file/source/' . $module::$storeItem['picture'] .
'" alt="' . $module::$storeItem['picture'] . '">';
?>
</div>
</div>
<div class="row">
<div class="col12 textAlignCenter">
<?php echo 'Version n°' . $module::$storeItem['fileVersion']; ?>
</div>
</div>
<div class="row">
<div class="col12 textAlignCenter">
<?php echo ' du ' . $module::$storeItem['fileDate']; ?>
</div>
</div>
<div class="row">
<div class="col12 textAlignCenter">
<span>Auteur :
<?php echo $module::$storeItem['fileAuthor']; ?>
</span>
</div>
</div>
<div class="row">
<div class="col12 textAlignCenter">
<span>Licence :
<?php echo $module::$storeItem['fileLicense']; ?>
</span>
</div>
</div>
</div>

@ -1,15 +0,0 @@
<div class="row">
<div class="col2">
<?php echo template::button('configStoreBack', [
'class' => 'buttonGrey',
'href' => helper::baseUrl() . 'addon',
'ico' => 'left',
'value' => 'Retour'
]); ?>
</div>
</div>
<?php if($module::$storeList): ?>
<?php echo template::table([4, 2, 2, 3, 1], $module::$storeList, ['Module', 'Version', 'Date', 'Pages', 'Télécharger']); ?>
<?php else: ?>
<?php echo template::speech('Le catalogue est vide.'); ?>
<?php endif; ?>

@ -1,18 +0,0 @@
/**
* This file is part of Zwii.
*
* For full copyright and license information, please see the LICENSE
* file that was distributed with this source code.
*
* @author Rémi Jean <remi.jean@outlook.com>
* @copyright Copyright (C) 2008-2018, Rémi Jean
* @author Frédéric Tempez <frederic.tempez@outlook.com>
* @copyright Copyright (C) 2018-2021, Frédéric Tempez
* @license GNU General Public License, version 3
* @link http://zwiicms.fr/
*/
/** NE PAS EFFACER
* admin.css
*/

@ -1,6 +0,0 @@
<h3>IMPORTER OU METTRE A JOUR</h3>
<p>Vous avez au préalable chargé le fichier zip du module sur votre serveur en utilisant le 'Catalogue en ligne', écran précédent.</p>
<p>D'autres modules sont également disponibles sur le <a href="https://forum.zwiicms.fr/categories/t%C3%A9l%C3%A9chargements-de-modules" target="_blank" rel="noopener">forum de ZwiiCMS</a>,
téléversez les sur votre serveur avec 'Gérer les fichiers'.</p>
<p>Lors d'une mise à jour Zwii contrôle la version du module à installer, pour réinstaller un module de même numéro de version vous devez cocher 'Mise à jour forcée'.
Il est déconseillé d'installer un module plus ancien.</p>

@ -1,52 +0,0 @@
<?php echo template::formOpen('configModulesUpload'); ?>
<div class="row">
<div class="col2">
<?php echo template::button('configModulesBack', [
'class' => 'buttonGrey',
'href' => helper::baseUrl() . 'addon',
'ico' => 'left',
'value' => 'Retour'
]); ?>
</div>
<div class="col2">
<?php echo template::button('addonIndexHelp', [
'class' => 'buttonHelp',
'ico' => 'help',
'value' => 'Aide'
]); ?>
</div>
<div class="col2 offset6">
<?php echo template::submit('configModulesSubmit',[
'value' => 'Valider',
'ico' => 'check'
]); ?>
</div>
</div>
<!-- Aide à propos de la gestion des modules, view upload -->
<div class="helpDisplayContent">
<?php echo file_get_contents( 'core/module/addon/view/upload/upload.help.html') ;?>
</div>
<div class="row">
<div class="col12">
<div class="block">
<h4>Installer ou mettre à jour un module </h4>
<div class="row">
<div class="col6 offset3">
<?php echo template::file('configModulesInstallation', [
'label' => 'Archive ZIP :',
'type' => 2
]); ?>
</div>
</div>
<div class="row">
<div class="col6">
<?php echo template::checkbox('configModulesCheck', true, 'Mise à jour forcée', [
'checked' => false,
'help' => 'Permet de forcer une mise à jour même si la version du module est inférieure ou égale à celle du module installé.',
]); ?>
</div>
</div>
</div>
</div>
</div>
<?php echo template::formClose(); ?>

@ -18,19 +18,17 @@ class config extends common {
public static $actions = [
'backup' => self::GROUP_ADMIN,
'copyBackups'=> self::GROUP_ADMIN,
'configMetaImage' => self::GROUP_ADMIN,
'generateFiles' => self::GROUP_ADMIN,
'index' => self::GROUP_ADMIN,
'advanced' => self::GROUP_ADMIN,
'restore' => self::GROUP_ADMIN,
'manage' => self::GROUP_ADMIN,
'updateBaseUrl' => self::GROUP_ADMIN,
'script' => self::GROUP_ADMIN,
'logReset' => self::GROUP_ADMIN,
'logDownload'=> self::GROUP_ADMIN,
'blacklistReset' => self::GROUP_ADMIN,
'blacklistDownload' => self::GROUP_ADMIN,
'blacklistDownload' => self::GROUP_ADMIN
];
public static $timezones = [
@ -267,11 +265,11 @@ class config extends common {
/**
* Procédure d'importation
*/
public function restore() {
public function manage() {
// Soumission du formulaire
if($this->isPost()) {
//if ($this->getInput('configRestoreImportFile'))
$fileZip = $this->getInput('configRestoreImportFile');
//if ($this->getInput('configManageImportFile'))
$fileZip = $this->getInput('configManageImportFile');
$file_parts = pathinfo($fileZip);
$folder = date('Y-m-d-h-i-s', time());
$zip = new ZipArchive();
@ -279,7 +277,7 @@ class config extends common {
// Valeurs en sortie erreur
$this->addOutput([
'notification' => 'Le fichier n\'est pas une archive valide',
'redirect' => helper::baseUrl() . 'config/restore',
'redirect' => helper::baseUrl() . 'config/manage',
'state' => false
]);
}
@ -288,7 +286,7 @@ class config extends common {
// Valeurs en sortie erreur
$this->addOutput([
'notification' => 'Impossible de lire l\'archive',
'redirect' => helper::baseUrl() . 'config/restore',
'redirect' => helper::baseUrl() . 'config/manage',
'state' => false
]);
}
@ -313,21 +311,21 @@ class config extends common {
// V10 valide
$version = '10';
// Option active, les users sont stockées
if ($this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true ) {
if ($this->getInput('configManageImportUser', helper::FILTER_BOOLEAN) === true ) {
$users = $this->getData(['user']);
}
} else { // Version invalide
// Valeurs en sortie erreur
$this->addOutput([
'notification' => 'Cette archive n\'est pas une sauvegarde valide',
'redirect' => helper::baseUrl() . 'config/restore',
'redirect' => helper::baseUrl() . 'config/manage',
'state' => false
]);
}
// Préserver les comptes des utilisateurs d'une version 9 si option cochée
// Positionnement d'une variable de session lue au constructeurs
if ($version === '9') {
$_SESSION['KEEP_USERS'] = $this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN);
$_SESSION['KEEP_USERS'] = $this->getInput('configManageImportUser', helper::FILTER_BOOLEAN);
}
// Extraire le zip ou 'site/'
$success = $zip->extractTo( 'site/' );
@ -337,13 +335,13 @@ class config extends common {
// Restaurer les users originaux d'une v10 si option cochée
if (!empty($users) &&
$version === '10' &&
$this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true) {
$this->getInput('configManageImportUser', helper::FILTER_BOOLEAN) === true) {
$this->setData(['user',$users]);
}
// Message de notification
$notification = $success === true ? 'Restauration réalisée avec succès' : 'Erreur inconnue';
$redirect = $this->getInput('configRestoreImportUser', helper::FILTER_BOOLEAN) === true ? helper::baseUrl() . 'config/restore' : helper::baseUrl() . 'user/login/';
$redirect = $this->getInput('configManageImportUser', helper::FILTER_BOOLEAN) === true ? helper::baseUrl() . 'config/manage' : helper::baseUrl() . 'user/login/';
// Valeurs en sortie erreur
$this->addOutput([
'notification' => $notification,
@ -355,7 +353,7 @@ class config extends common {
// Valeurs en sortie
$this->addOutput([
'title' => 'Restaurer',
'view' => 'restore'
'view' => 'manage'
]);
}
@ -398,10 +396,9 @@ class config extends common {
'legalPageId' => $legalPageId,
'searchPageId' => $searchPageId,
'metaDescription' => $this->getInput('configMetaDescription', helper::FILTER_STRING_LONG, true),
'title' => $this->getInput('configTitle', helper::FILTER_STRING_SHORT, true),
'title' => $this->getInput('configTitle', helper::FILTER_STRING_SHORT, true)
]
]);
$this->setData(['config', 'i18n', 'enabled', $this->getInput('configI18n',helper::FILTER_BOOLEAN) ]);
// Générer robots.txt et sitemap
$this->generateFiles();
// Valeurs en sortie
@ -431,11 +428,6 @@ class config extends common {
$this->getInput('configAdvancedAutoUpdate', helper::FILTER_BOOLEAN) === true) {
$this->setData(['core','lastAutoUpdate',0]);
}
// Eviter déconnexion automatique après son activation
if ( $this->getData(['config','autoDisconnect']) === false
AND $this->getInput('configAdvancedAutoDisconnect',helper::FILTER_BOOLEAN) === true ) {
$this->setData(['user',$this->getuser('id'),'accessCsrf',$_SESSION['csrf']]);
}
// Sauvegarder
$this->setData([
'config',
@ -463,7 +455,6 @@ class config extends common {
'proxyUrl' => $this->getInput('configAdvancedProxyUrl'),
'proxyPort' => $this->getInput('configAdvancedProxyPort',helper::FILTER_INT),
'captchaStrong' => $this->getInput('configAdvancedCaptchaStrong',helper::FILTER_BOOLEAN),
'autoDisconnect' => $this->getInput('configAdvancedAutoDisconnect',helper::FILTER_BOOLEAN),
'smtp' => [
'enable' => $this->getInput('configAdvancedSmtpEnable',helper::FILTER_BOOLEAN),
'host' => $this->getInput('configAdvancedSmtpHost',helper::FILTER_STRING_SHORT),
@ -479,23 +470,22 @@ class config extends common {
'timeout' => $this->getInput('configAdvancedConnectTimeout',helper::FILTER_INT),
'log' => $this->getInput('configAdvancedConnectLog',helper::FILTER_BOOLEAN),
'captcha' => $this->getInput('configAdvancedConnectCaptcha',helper::FILTER_BOOLEAN),
],
'i18n' => [
'enabled' => $this->getData(['config', 'i18n', 'enabled']),
'scriptGoogle' => $this->getData(['config', 'i18n', 'scriptGoogle']),
'showCredits' => $this->getData(['config', 'i18n', 'showCredits']),
'autoDetect' => $this->getData(['config', 'i18n', 'autoDetect']),
'admin' => $this->getData(['config', 'i18n', 'admin']),
'fr' => $this->getData(['config', 'i18n', 'fr']),
'de' => $this->getData(['config', 'i18n', 'de']),
'en' => $this->getData(['config', 'i18n', 'en']),
'es' => $this->getData(['config', 'i18n', 'es']),
'it' => $this->getData(['config', 'i18n', 'it']),
'nl' => $this->getData(['config', 'i18n', 'nl']),
'pt' => $this->getData(['config', 'i18n', 'pt']),
]
]
]);
// Efface les fichiers de backup lorsque l'option est désactivée
if ($this->getInput('configAdvancedFileBackup', helper::FILTER_BOOLEAN) === false) {
$path = realpath('site/data');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename)
{
if (strpos($filename,'backup.json')) {
unlink($filename);
}
}
if (file_exists('site/data/.backup')) unlink('site/data/.backup');
} else {
touch('site/data/.backup');
}
// Notice
if(self::$inputNotices === []) {
// Active la réécriture d'URL
@ -622,7 +612,7 @@ class config extends common {
// Valeurs en sortie
$this->addOutput([
'notification' => $success ? $c3. ' conversion' . ($c3 > 1 ? 's' : '') . ' effectuée' . ($c3 > 1 ? 's' : '') : 'Aucune conversion',
'redirect' => helper::baseUrl() . 'config/restore',
'redirect' => helper::baseUrl() . 'config/manage',
'state' => $success ? true : false
]);
}
@ -659,14 +649,14 @@ class config extends common {
/**
* Télécharger le fichier de log
*/
public function logDownload() {
public function logDownload() {
$fileName = self::DATA_DIR . 'journal.log';
if (file_exists($fileName)) {
ob_start();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($fileName));
ob_clean();
ob_clean();
ob_end_flush();
readfile( $fileName);
exit();
@ -701,7 +691,7 @@ class config extends common {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($fileName));
ob_clean();
ob_clean();
ob_end_flush();
readfile( $fileName);
unlink(self::TEMP_DIR . 'blacklist.log');
@ -739,27 +729,6 @@ class config extends common {
}
}
/**
* Récupération des backups auto dans le gestionnaire de fichiers
*/
public function copyBackups() {
// Créer le répertoire manquant
if (!is_dir(self::FILE_DIR.'source/backup')) {
mkdir(self::FILE_DIR.'source/backup');
}
$success = $this->copyDir(self::BACKUP_DIR, self::FILE_DIR . 'source/backup' );
// Effacer htaccess
if (file_exists(self::FILE_DIR.'source/backup/.htaccess')) {
unlink(self::FILE_DIR.'source/backup/.htaccess');
}
// Valeurs en sortie
$this->addOutput([
'redirect' => helper::baseUrl() . 'config/advanced',
'notification' => $success ? 'Copie terminée avec succès' : 'Echec de la copie',
'state' => $success
]);
}
/**
* Fonction de parcours des données de module

@ -16,27 +16,24 @@
<div class="col12">
<div class="block">
<h4>Maintenance</h4>
<div class="row">
<div class="col4">
<?php echo template::checkbox('configAdvancedMaintenance', true, 'Site en maintenance', [
'checked' => $this->getData(['config', 'maintenance'])
]); ?>
</div>
<div class="col3 offset1">
<?php echo template::button('configBackupButton', [
'href' => helper::baseUrl() . 'config/backup',
'value' => 'Sauvegarder',
'ico' => 'download-cloud'
<div class="col3">
<?php echo template::checkbox('configAdvancedMaintenance', true, 'Site en maintenance', [
'checked' => $this->getData(['config', 'maintenance'])
]); ?>
</div>
<div class="col3 offset1">
<?php echo template::button('configRestoreButton', [
'href' => helper::baseUrl() . 'config/restore',
'value' => 'Restaurer',
'ico' => 'upload-cloud'
]); ?>
</div>
<div class="col3 offset1">
<?php echo template::button('configManageButton', [
'href' => helper::baseUrl() . 'config/backup',
'value' => 'Sauvegarder',
'ico' => 'download'
]); ?>
</div>
<div class="col3 offset1">
<?php echo template::button('configManageButton', [
'href' => helper::baseUrl() . 'config/manage',
'value' => 'Restaurer',
'ico' => 'upload'
]); ?>
</div>
</div>
</div>
@ -45,6 +42,7 @@
<div class="col12">
<div class="block">
<h4>Réglages</h4>
<?php $error = helper::urlGetContents('http://zwiicms.fr/update/' . common::ZWII_UPDATE_CHANNEL . '/version');?>
<div class="row">
<div class="col4">
<?php echo template::file('configAdvancedFavicon', [
@ -71,77 +69,61 @@
</div>
</div>
<div class="row">
<div class="col6">
<?php echo template::checkbox('configAdvancedCookieConsent', true, 'Message de consentement aux cookies', [
'checked' => $this->getData(['config', 'cookieConsent']),
'help' => 'Activation obligatoire selon les lois françaises sauf si vous utilisez votre propre système de consentement.'
<div class="col4">
<?php echo template::checkbox('configAdvancedCookieConsent', true, 'Consentement aux cookies', [
'checked' => $this->getData(['config', 'cookieConsent'])
]); ?>
</div>
<div class="col6">
<?php echo template::checkbox('rewrite', true, 'URL intelligentes', [
<div class="col4">
<?php echo template::checkbox('configAdvancedCaptchaStrong', true, 'Captcha renforcé', [
'checked' => $this->getData(['config','captchaStrong']),
'help' => 'Option recommandée pour sécuriser la connexion. S\'applique à tous les captchas du site. Le captcha simple se limite à une addition de nombres de 0 à 10. Le captcha renforcé utilise quatre opérations de nombres de 0 à 20.'
]); ?>
</div>
<div class="col4">
<?php echo template::checkbox('rewrite', true, 'Réécriture d\'URL', [
'checked' => helper::checkRewrite(),
'help' => 'Vérifiez d\'abord que votre serveur autorise l\'URL rewriting (ce qui n\'est pas le cas chez Free).'
'help' => 'Vérifiez d\'abord que votre serveur l\'autorise : ce n\'est pas le cas chez Free.'
]); ?>
</div>
</div>
<div class="row">
<div class="col6">
<?php echo template::checkbox('configAdvancedCaptchaStrong', true, 'Captcha complexe', [
'checked' => $this->getData(['config','captchaStrong']),
'help' => 'Option recommandée pour sécuriser la connexion. S\'applique à tous les captchas du site. Le captcha simple se limite à une addition de nombres de 0 à 10. Le captcha complexe utilise quatre opérations de nombres de 0 à 20. Activation recommandée.'
]); ?>
</div>
<div class="col6">
<?php echo template::checkbox('configAdvancedAutoDisconnect', true, 'Déconnexion automatique de la session', [
'checked' => $this->getData(['config','autoDisconnect']),