Modifications 4308

4408
Deltacms 2022-11-05 09:31:32 +01:00
parent 285a4170f7
commit 3dacfdbbef
60 changed files with 798 additions and 358 deletions

View File

@ -3,26 +3,12 @@
Require all denied
</Files>
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file \.(html?|txt|css|js|php|woff)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text\.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image\.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/shtml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/woff2
AddOutputFilterByType DEFLATE application/font-woff
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml

View File

@ -1,9 +1,22 @@
# Changelog
## Version 4.3.08 de Deltacms
- Modifications :
- Captcha : nouvelle option 'Captcha simple pour les humains', le Captcha se réduit à une simple case à cocher, une analyse comportementale
qualifie le type de visiteur,
- Captcha : nouveau captcha, suppression des options captcha renforcé et type de captcha,
- Tinymce / image : nouvelle option permettant d'afficher un titre sous l'image,
- News 4.3 prise en compte de l'option titre sous l'image,
- Corrections :
- Configuration / configuration : 2 'textarea' avaient le même identifiant.
- News 4.3 déplacement de l'initialisation de tinymce et flatpickr,
- Blog 6.4 déplacement de l'initialisation de tinymce et flatpickr,
- Divers : mise en conformité W3C.
## Version 4.3.07 de Deltacms
- Modifications :
- Configuration / Thème / Header : nouvelle option bannière visible uniquement sur la page d'accueil,
- Correction :
- Corrections :
- Snipcart : modification dans Core / Page / Edition pour compatibilité avec ce module,
- Agenda 5.4 : correction d'un bug critique lié à la limitation des droits liés aux évènements, mise à jour indispensable,
- Configuration / Thème / Header : correction de plusieurs bugs notamment pour l'affichage en mode aperçu.

View File

@ -1,5 +1,5 @@
# DeltaCMS 4.3.07
# DeltaCMS 4.3.08
DeltaCMS 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.
L'administration du site est bilingue anglais ou français, le site peut être rédigé dans une des principales langues européennes.

View File

@ -35,120 +35,17 @@ class template {
/**
* Crée un champ captcha
* @param string $nameId Nom et id du champ
* @param array $attributes Attributs ($key => $value)
* @return string
*/
public static function captcha($nameId, array $attributes = []) {
// Attributs par défaut
$attributes = array_merge([
'class' => '',
'classWrapper' => '',
'help' => '',
'id' => $nameId,
'name' => $nameId,
'value' => '',
'limit' => false, // captcha simple
'type'=> 'alpha' // num(érique) ou alpha(bétique)
], $attributes);
// Captcha quatre opérations
// Limite addition et soustraction selon le type de captcha
$numbers = [0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20];
$letters = ['u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a'];
$limit = $attributes['limit'] ? count($letters)-1 : 10;
// Tirage de l'opération
mt_srand((int)(microtime(true)*1000000));
// Captcha simple limité à l'addition
$operator = $attributes['limit'] ? mt_rand (1, 4) : 1;
// Limite si multiplication ou division
if ($operator > 2) {
$limit = 10;
}
// Tirage des nombres
mt_srand((int)(microtime(true)*1000000));
$firstNumber = mt_rand (1, $limit);
mt_srand((int)(microtime(true)*1000000));
$secondNumber = mt_rand (1, $limit);
// Permutation si addition ou soustraction
if (($operator < 3) and ($firstNumber < $secondNumber)) {
$temp = $firstNumber;
$firstNumber = $secondNumber;
$secondNumber = $temp;
}
// Icône de l'opérateur et calcul du résultat
switch ($operator) {
case 1:
$operator = template::ico('plus');
$result = $firstNumber + $secondNumber;
break;
case 2:
$operator = template::ico('minus');
$result = $firstNumber - $secondNumber;
break;
case 3:
$operator = template::ico('cancel');
$result = $firstNumber * $secondNumber;
break;
case 4:
$operator = template::ico('divide');
$limit2 = [10, 10, 6, 5, 4, 3, 2, 2, 2, 2];
for ($i = 1; $i <= $firstNumber; $i++) {
$limit = $limit2[$i-1];
}
mt_srand((int)(microtime(true)*1000000));
$secondNumber = mt_rand(1, $limit);
$firstNumber = $firstNumber * $secondNumber;
$result = $firstNumber / $secondNumber;
break;
}
// Hashage du résultat
$result = password_hash($result, PASSWORD_BCRYPT);
// Codage des valeurs de l'opération
$firstLetter = uniqid();
$secondLetter = uniqid();
// Masquage image source pour éviter un décodage
copy ('core/vendor/zwiico/png/' . $attributes['type'] . '/' . $letters[$firstNumber] . '.png', 'site/tmp/' . $firstLetter . '.png');
copy ('core/vendor/zwiico/png/' . $attributes['type'] . '/' . $letters[$secondNumber] . '.png', 'site/tmp/' . $secondLetter . '.png');
// Début du wrapper
$html = '<div id="' . $attributes['id'] . 'Wrapper" class="captcha inputWrapper ' . $attributes['classWrapper'] . '">';
// Label
$html .= self::label($attributes['id'],
'<img class="captcha' . ucFirst($attributes['type']) . '" src="' . helper::baseUrl(false) . 'site/tmp/' . $firstLetter . '.png" />&nbsp;<strong>' . $operator . '</strong>&nbsp;<img class="captcha' . ucFirst($attributes['type']) . '" src="' . helper::baseUrl(false) . 'site/tmp/' . $secondLetter . '.png" /><span style="font-size: 25px;"> = </span> ', [
'help' => $attributes['help']
]);
// Notice
$notice = '';
if(array_key_exists($attributes['id'], common::$inputNotices)) {
$notice = common::$inputNotices[$attributes['id']];
$attributes['class'] .= ' notice';
}
$html .= self::notice($attributes['id'], $notice);
// captcha
$html .= sprintf(
'<input type="text" %s>',
helper::sprintAttributes($attributes)
);
// Champ résultat codé
$html .= self::hidden($attributes['id'] . 'Result', [
'value' => $result,
'before' => false
]);
// Fin du wrapper
$html .= '</div>';
public static function captcha($nameId, $classWrapper) {
$html = '<div id="' . $nameId . 'Wrapper" class="captcha inputWrapper ' . $classWrapper. '">';
$html .= '<img src="core/vendor/captcha/captcha.php" alt="Captcha" id="captcha">';
$html .= '<input name="codeCaptcha" type="text" size="9" style="position:relative;top:-40px;left:-10px;">';
$html .= '<img src="core/vendor/captcha/reload.png" alt="" style="cursor:pointer;position:relative;top:-30px;left:10px;width:24px;height:auto;"';
$html .= ' onclick="document.images.captcha.src=\'core/vendor/captcha/captcha.php?id=\'+Math.round(Math.random(0)*1000)">';
$html .= '</div>';
// Retourne le html
return $html;
}
@ -171,8 +68,10 @@ class template {
'disabled' => false,
'help' => '',
'id' => $nameId,
'name' => $nameId
'name' => $nameId,
'required' => false
], $attributes);
// Sauvegarde des données en cas d'erreur
if($attributes['before'] AND array_key_exists($attributes['id'], common::$inputBefore)) {
$attributes['checked'] = (bool) common::$inputBefore[$attributes['id']];
@ -187,11 +86,20 @@ class template {
}
$html .= self::notice($attributes['id'], $notice);
// Case à cocher
$html .= sprintf(
'<input type="checkbox" value="%s" %s>',
$value,
helper::sprintAttributes($attributes)
);
if( $attributes['required'] === false ){
$html .= sprintf(
'<input type="checkbox" value="%s" %s>',
$value,
helper::sprintAttributes($attributes)
);
} else {
$html .= sprintf(
'<input type="checkbox" value="%s" %s required>',
$value,
helper::sprintAttributes($attributes)
);
}
// Label
$html .= self::label($attributes['id'], '<span>' . $label . '</span>', [
'help' => $attributes['help']

View File

@ -48,7 +48,7 @@ class common {
// Numéro de version
const DELTA_UPDATE_URL = 'https://update.deltacms.fr/master/';
const DELTA_VERSION = '4.3.07';
const DELTA_VERSION = '4.3.08';
const DELTA_UPDATE_CHANNEL = "v4";
public static $actions = [];
@ -267,6 +267,15 @@ class common {
if($this->user === []) {
$this->user = $this->getData(['user', $this->getInput('DELTA_USER_ID')]);
}
/**
* Discrimination humain robot pour shuntage des Captchas
* Initialisation à 'bot' ou 'human' en fonction des données $_SERVER : à développer !
*/
if( !isset( $_SESSION['humanBot'] )){
$_SESSION['humanBot'] = 'bot';
if( !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ) $_SESSION['humanBot'] = 'human';
}
/**
* Traduction du site par script
@ -1189,7 +1198,7 @@ class common {
$item .= '<h3>'. $this->getData(['locale', 'cookies', 'cookiesTitleText']) . '</h3>';
$item .= '<p>' . $this->getData(['locale', 'cookies', 'cookiesDeltaText']) . '</p>';
// Formulaire de réponse
$item .= '<form method="POST" action="" id="cookieForm">';
$item .= '<form method="POST" id="cookieForm">';
$cookieExt = $this->getData(['locale', 'cookies', 'cookiesExtText']);
$stateCookieExt = $this->getInput('DELTA_COOKIE_EXT_CONSENT') === 'true' ? 'checked="checked"' : '';
if( $cookieExt !== null AND $cookieExt !== '' ) {
@ -2162,11 +2171,11 @@ class common {
) ) {
$select = ' class="i18nFlagSelected" ';
} else {
$select = ' class="i18nFlag" ';
$select = ' class="i18nFlag flag" ';
}
echo '<li>';
echo '<a href="' . helper::baseUrl() . 'translate/i18n/' . $key . '/' . $this->getData(['config', 'i18n',$key]) . '/' . $this->getUrl(0) . '"><img ' . $select . ' class="flag" alt="' . $value . '" src="' . helper::baseUrl(false) . 'core/vendor/i18n/png/' . $key . '.png"/></a>';
echo '<a href="' . helper::baseUrl() . 'translate/i18n/' . $key . '/' . $this->getData(['config', 'i18n',$key]) . '/' . $this->getUrl(0) . '"><img ' . $select . ' alt="' . $value . '" src="' . helper::baseUrl(false) . 'core/vendor/i18n/png/' . $key . '.png"/></a>';
echo '</li>';
}
}
@ -2354,7 +2363,10 @@ class core extends common {
$css .= '.block h4 {background-color:'. $colors['normal'] . ';color:' . $colors['text'] .';border-radius: ' . $this->getdata(['theme','block','blockBorderRadius']) . ' ' . $this->getdata(['theme','block','blockBorderRadius']) . ' 0px 0px;}';
$css .= '.block p {margin: 0px -5px;}';
//$css .= '.mce-tinymce {border: 1px solid ' . $this->getdata(['theme','block','borderColor']) .' !important;}';
//Tinymce option titre sous une image valeurs par défaut modifiables dans custom.css
$css .= 'figure.image { border-color: ' . $this->getdata(['theme','block','borderColor']) . '; background-color: ' . $this->getdata(['theme','block','backgroundColor']).'}';
// Bannière
// Eléments communs
@ -2964,6 +2976,7 @@ class core extends common {
'content' => template::speech('La page <strong>' . $accessInfo['pageId'] . '</strong> est ouverte par l\'utilisateur <strong>' . $accessInfo['userName'] . '</strong>')
]);
} else {
$_SESSION['humanBot'] = 'bot';
if ( $this->getData(['locale','page403']) !== 'none'
AND $this->getData(['page',$this->getData(['locale','page403'])]))
{
@ -2977,6 +2990,7 @@ class core extends common {
}
} elseif ($this->output['content'] === '') {
http_response_code(404);
$_SESSION['humanBot'] = 'bot';
if ( $this->getData(['locale','page404']) !== 'none'
AND $this->getData(['page',$this->getData(['locale','page404'])]))
{

View File

@ -66,4 +66,14 @@ if ($this->getData(['core', 'dataVersion']) < 4307) {
// Mise à jour
$this->setData(['core', 'dataVersion', 4307]);
}
if ($this->getData(['core', 'dataVersion']) < 4308) {
$this->setData(['config', 'connect', 'captchaBot', false]);
$this->setData(['locale', 'captchaSimpleText', 'Je ne suis pas un robot' ]);
$this->setData(['locale', 'captchaSimpleHelp', 'Cochez cette case pour prouver que vous n\'êtes pas un robot' ]);
$this->deleteData([ 'config', 'connect', 'captchaStrong' ]);
$this->deleteData([ 'config', 'connect', 'captchaType' ]);
// Mise à jour
$this->setData(['core', 'dataVersion', 4308]);
}
?>

View File

@ -1771,3 +1771,26 @@ th.col12 {
display : none;
}
}
figure.image {
display: inline-block;
border: 1px solid;
margin: 0 6px;
}
figure.align-left {
float: left;
}
figure.align-right {
float: right;
}
figure.image img {
margin: 0;
}
figure.image figcaption {
margin: 4px;
text-align: center;
}

View File

@ -367,6 +367,8 @@ class config extends common {
'sitemapPageLabel' => empty($this->getInput('localeSitemapPageLabel', helper::FILTER_STRING_SHORT)) ? 'Plan du site' : $this->getInput('localeSitemapPageLabel', helper::FILTER_STRING_SHORT),
'metaDescription' => $this->getInput('localeMetaDescription', helper::FILTER_STRING_LONG, true),
'title' => $this->getInput('localeTitle', helper::FILTER_STRING_SHORT, true),
'captchaSimpleText' => $this->getInput('localeCaptchaSimpleText', helper::FILTER_STRING_LONG),
'captchaSimpleHelp' => $this->getInput('localeCaptchaSimpleHelp', helper::FILTER_STRING_LONG),
'cookies' => [
// Les champs sont obligatoires si l'option consentement des cookies est active
'cookiesDeltaText' => $this->getInput('localeCookiesDeltaText', helper::FILTER_STRING_LONG, $this->getData(['config', 'cookieConsent'])),
@ -425,9 +427,8 @@ class config extends common {
'log' => $this->getInput('connectLog',helper::FILTER_BOOLEAN),
'anonymousIp' => $this->getInput('connectAnonymousIp',helper::FILTER_INT),
'captcha' => $this->getInput('connectCaptcha',helper::FILTER_BOOLEAN),
'captchaStrong' => $this->getInput('connectCaptchaStrong',helper::FILTER_BOOLEAN),
'autoDisconnect' => $this->getInput('connectAutoDisconnect',helper::FILTER_BOOLEAN),
'captchaType' => $this->getInput('connectCaptchaType')
'captchaBot' => $this->getInput('connectCaptchaBot',helper::FILTER_BOOLEAN),
'autoDisconnect' => $this->getInput('connectAutoDisconnect',helper::FILTER_BOOLEAN)
],
'i18n' => [
'enable' => $this->getInput('localei18n',helper::FILTER_BOOLEAN),

View File

@ -17,7 +17,7 @@ $text['core_config_view']['bodyheadscript'][2] = 'Script in body';
$text['core_config_view']['connect'][0] = 'Login security';
$text['core_config_view']['connect'][1] = 'Captcha at login';
$text['core_config_view']['connect'][2] = 'Complex captcha';
$text['core_config_view']['connect'][3] = 'Recommended option to secure the connection. Applies to all captchas on the site. The simple captcha is limited to adding numbers from 0 to 10. The complex captcha uses four number operations from 0 to 20. Activation recommended.';
$text['core_config_view']['connect'][3] = 'With this option if you are recognised as human a very simplified captcha will be displayed. This applies to all captchas on the site.';
$text['core_config_view']['connect'][4] = 'Captcha type';
$text['core_config_view']['connect'][5] = 'Automatic logout';
$text['core_config_view']['connect'][6] = 'Disconnects previously opened sessions on other browsers or terminals. Activation recommended.';
@ -33,7 +33,7 @@ $text['core_config_view']['connect'][15] = 'Anonymise IP addresses';
$text['core_config_view']['connect'][16] = 'French regulations require level 2 anonymity';
$text['core_config_view']['connect'][17] = 'Download log';
$text['core_config_view']['connect'][18] = 'Reset log';
$text['core_config_view']['connect'][19] ='Complex captcha';
$text['core_config_view']['connect'][19] = 'Simple Captcha for humans';
$text['core_config_view']['index'][0] = 'Home';
$text['core_config_view']['index'][1] = 'Help';
$text['core_config_view']['index'][2] = 'Configuration';
@ -85,6 +85,11 @@ $text['core_config_view']['locale'][38] = 'Enter the text of the checkbox for ac
$text['core_config_view']['locale'][39] = 'Checkbox third party cookies';
$text['core_config_view']['locale'][40] = 'Validation button';
$text['core_config_view']['locale'][41] = 'Got it';
$text['core_config_view']['locale'][42] = 'Simplified Captcha for humans ';
$text['core_config_view']['locale'][43] = 'Text associated with the checkbox';
$text['core_config_view']['locale'][44] = 'I am not a robot';
$text['core_config_view']['locale'][45] = 'Help message';
$text['core_config_view']['locale'][46] = 'Check this box to prove that you are not a robot';
$text['core_config_view']['network'][0] = 'Settings';
$text['core_config_view']['network'][1] = 'Proxy type';
$text['core_config_view']['network'][2] = 'Proxy address';

View File

@ -17,7 +17,7 @@ $text['core_config_view']['bodyheadscript'][2] = 'Script dans body';
$text['core_config_view']['connect'][0] = 'Sécurité de la connexion';
$text['core_config_view']['connect'][1] = 'Captcha à la connexion';
$text['core_config_view']['connect'][2] = 'Captcha complexe';
$text['core_config_view']['connect'][3] = '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.';
$text['core_config_view']['connect'][3] = 'Avec cette option, si vous êtes reconnu comme humain, un captcha très simplifié sera affiché. Ceci s\'applique à tous les captchas du site.';
$text['core_config_view']['connect'][4] = 'Type de captcha';
$text['core_config_view']['connect'][5] = 'Déconnexion automatique';
$text['core_config_view']['connect'][6] = 'Déconnecte les sessions ouvertes précédemment sur d\'autres navigateurs ou terminaux. Activation recommandée.';
@ -33,7 +33,7 @@ $text['core_config_view']['connect'][15] = 'Anonymat des adresses IP';
$text['core_config_view']['connect'][16] = 'La réglementation française impose un anonymat de niveau 2';
$text['core_config_view']['connect'][17] = 'Télécharger le journal';
$text['core_config_view']['connect'][18] = 'Réinitialiser le journal';
$text['core_config_view']['connect'][19] ='Captcha complexe';
$text['core_config_view']['connect'][19] = 'Captcha simple pour les humains';
$text['core_config_view']['index'][0] = 'Accueil';
$text['core_config_view']['index'][1] = 'Aide';
$text['core_config_view']['index'][2] = 'Configuration';
@ -85,6 +85,11 @@ $text['core_config_view']['locale'][38] = 'Saisissez le texte de la case à coch
$text['core_config_view']['locale'][39] = 'Checkbox cookies tiers';
$text['core_config_view']['locale'][40] = 'Bouton de validation';
$text['core_config_view']['locale'][41] = 'J\'ai compris';
$text['core_config_view']['locale'][42] = 'Captcha simplifié pour les humains ';
$text['core_config_view']['locale'][43] = 'Texte associé à la checkbox';
$text['core_config_view']['locale'][44] = 'Je ne suis pas un robot';
$text['core_config_view']['locale'][45] = 'Message d\'aide';
$text['core_config_view']['locale'][46] = 'Cochez cette case pour prouver que vous n\'êtes pas un robot';
$text['core_config_view']['network'][0] = 'Paramètres';
$text['core_config_view']['network'][1] = 'Type de proxy';
$text['core_config_view']['network'][2] = 'Adresse du proxy';

View File

@ -19,18 +19,12 @@ include('./core/module/config/lang/'. $this->getData(['config', 'i18n', 'langAdm
'checked' => $this->getData(['config', 'connect','captcha'])
]); ?>
</div>
<div class="col3">
<?php echo template::checkbox('connectCaptchaStrong', true, $text['core_config_view']['connect'][19], [
'checked' => $this->getData(['config', 'connect', 'captchaStrong']),
<div class="col6">
<?php echo template::checkbox('connectCaptchaBot', true, $text['core_config_view']['connect'][19], [
'checked' => $this->getData(['config', 'connect', 'captchaBot']),
'help' => $text['core_config_view']['connect'][3]
]); ?>
</div>
<div class="col3">
<?php echo template::select('connectCaptchaType', $captchaTypes , [
'label' => $text['core_config_view']['connect'][4],
'selected' => $this->getData(['config', 'connect', 'captchaType'])
]); ?>
</div>
<div class="col3">
<?php echo template::checkbox('connectAutoDisconnect', true, $text['core_config_view']['connect'][5], [
'checked' => $this->getData(['config','connect', 'autoDisconnect']),

View File

@ -169,6 +169,35 @@ else{
</div>
</div>
</div>
<div class="row">
<div class="col12">
<div class="block">
<h4><?php echo $text['core_config_view']['locale'][42]; echo template::flag($flag, '20px');?>
<span id="labelHelpButton" class="helpDisplayButton">
<a href="https://doc.deltacms.fr/localisation#captcha" target="_blank">
<?php echo template::ico('help', 'left');?>
</a>
</span>
</h4>
<div class="row">
<div class="col6">
<?php echo template::text('localeCaptchaSimpleText', [
'label' => $text['core_config_view']['locale'][43],
'placeholder' => $text['core_config_view']['locale'][44],
'value' => $this->getData(['locale', 'captchaSimpleText'])
]); ?>
</div>
<div class="col6">
<?php echo template::text('localeCaptchaSimpleHelp', [
'label' => $text['core_config_view']['locale'][45],
'placeholder' => $text['core_config_view']['locale'][46],
'value' => $this->getData(['locale', 'captchaSimpleHelp'])
]); ?>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col12">
<div class="block">

View File

@ -154,7 +154,7 @@ include('./core/module/config/lang/'. $this->getData(['config', 'i18n', 'langAdm
<?php $textRewrite = $text['core_config_view']['setup'][30];
if( helper::checkRewrite() === true ) $textRewrite = $text['core_config_view']['setup'][29];
$listText = $text['core_config_view']['setup'][25]. common::DELTA_VERSION."\n".$text['core_config_view']['setup'][26]. phpversion()."\n".$text['core_config_view']['setup'][27]. $_SERVER['SERVER_SOFTWARE']."\n".$text['core_config_view']['setup'][28].$textRewrite;
echo template::textarea('modulesPhp',[
echo template::textarea('modulesPhp1',[
'value' => $listText
]); ?>
</div>
@ -181,7 +181,7 @@ include('./core/module/config/lang/'. $this->getData(['config', 'i18n', 'langAdm
} else{
$listModText .= "\n\n".$text['core_config_view']['setup'][33];
}
echo template::textarea('modulesPhp',[
echo template::textarea('modulesPhp2',[
'value' => $listModText
]); ?>
</div>

View File

@ -11,6 +11,8 @@
"sitemapPageLabel": "Site map",
"metaDescription": "DeltaCMS is a database-less CMS that allows you to easily create and manage a website without any programming knowledge.",
"title": "DeltaCMS",
"captchaSimpleText": "I'am not a robot",
"captchaSimpleHelp": "Check this box to prove that you are human",
"cookies": {
"cookiesDeltaText": "This site uses cookies which are necessary for its operation. They enable it to function more smoothly, for example by memorising connection data, the language you have chosen or the validation of this message.",
"cookiesExtText": "",

View File

@ -5,7 +5,7 @@
"feeds": true,
"feedsLabel": "Syndication RSS",
"itemsperPage": 6,
"versionData": "6.3"
"versionData": "6.4"
},
"texts": {
"NoComment": "No comment yet",
@ -141,7 +141,7 @@
"logoUrl": "",
"logoWidth": "40",
"maxSizeUpload": "500000",
"versionData": "4.3",
"versionData": "4.4",
"uploadJpg": true,
"uploadPng": true,
"uploadPdf": false,

View File

@ -11,6 +11,8 @@
"legalPageLabel": "Mentions légales",
"metaDescription": "DeltaCMS est un CMS sans base de données qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation.",
"title": "DeltaCMS",
"captchaSimpleText": "Je ne suis pas un robot",
"captchaSimpleHelp": "Cochez cette case pour prouver que vous êtes humain",
"cookies": {
"cookiesDeltaText": "Ce site utilise des cookies nécessaires à son fonctionnement, ils permettent de fluidifier son fonctionnement par exemple en mémorisant les données de connexion, la langue que vous avez choisie ou la validation de ce message.",
"cookiesExtText": "",

View File

@ -5,7 +5,7 @@
"feeds": true,
"feedsLabel": "Syndication RSS",
"itemsperPage": 4,
"versionData": "6.3"
"versionData": "6.4"
},
"texts": {
"NoComment": "Pas encore de commentaire",
@ -141,7 +141,7 @@
"logoUrl": "",
"logoWidth": "40",
"maxSizeUpload": "1000000",
"versionData": "4.3",
"versionData": "4.4",
"uploadJpg": true,
"uploadPng": true,
"uploadPdf": false,

View File

@ -11,6 +11,8 @@
"legalPageLabel": "Legal Notices",
"metaDescription": "DeltaCMS is a database-less CMS that allows you to easily create and manage a website without any programming knowledge.",
"title": "DeltaCMS",
"captchaSimpleText": "I'am not a robot",
"captchaSimpleHelp": "Check this box to prove that you are human",
"cookies": {
"cookiesDeltaText": "This site uses cookies which are necessary for its operation. They enable it to function more smoothly, for example by memorising connection data, the language you have chosen or the validation of this message.",
"cookiesExtText": "",

View File

@ -38,9 +38,7 @@ class init extends common {
'log' => false,
'anonymousIp' => 2,
'captcha' => true,
'captchaStrong' => false,
"captchaType" => 'num',
'autoDisconnect' => true
'captchaBot' => true
],
'i18n' => [
'enable'=> true,
@ -70,7 +68,7 @@ class init extends common {
]
],
'core' => [
'dataVersion' => 4307,
'dataVersion' => 4308,
'lastBackup' => 0,
'lastClearTmp' => 0,
'lastAutoUpdate' => 0,
@ -89,6 +87,8 @@ class init extends common {
'legalPageLabel' => 'Mentions légales',
'metaDescription' => 'DeltaCMS est un CMS sans base de données qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation.',
'title' => 'DeltaCMS',
'captchaSimpleText' => 'Je ne suis pas un robot',
'captchaSimpleHelp' => 'Cochez cette case pour prouver que vous êtes humain',
'cookies' => [
'cookiesDeltaText' => 'Ce site utilise des cookies nécessaires à son fonctionnement, ils permettent de fluidifier son fonctionnement par exemple en mémorisant les données de connexion, la langue que vous avez choisie ou la validation de ce message.',
'cookiesExtText' => '',

View File

@ -180,45 +180,57 @@ textarea:hover {
/* Consentement aux cookies */
/*
#cookieConsent {
width: 80%;
color: #FFF;
background: #212223;
opacity: 0.9;
}
#cookieConsent a{
color : yellow;
}
#cookieConsent h3{
color : red;
}
#cookieConsentConfirm {
background: rgba(0,0,255,1);
color: rgba(255,255,255,1);
}
#cookieConsentConfirm:hover {
background: rgba(0,50,255,1);
color: rgba(255,255,255,1);
}
*/
/* Titre sous image */
/*
figure.image {
display: inline-block;
border: 1px solid gray;
margin: 0 6px;
background-color: #f5f2f0;
}
figure.align-left {
float: left;
}
figure.align-right {
float: right;
}
figure.image img {
margin: 0;
}
figure.image figcaption {
margin: 4px;
text-align: center;
}
*/

View File

@ -138,6 +138,7 @@ $text['core_user']['login'][9] = 'Captcha error';
$text['core_user']['login'][10] = 'Account locked ';
$text['core_user']['login'][11] = 'Connection';
$text['core_user']['login'][12] = '';
$text['core_user']['login'][13] = ' Fill in the Captcha ';
$text['core_user']['logout'][0] = 'Successfully disconnected';
$text['core_user']['reset'][0] = 'Incorrect';
$text['core_user']['reset'][1] = 'New password registered';

View File

@ -138,6 +138,7 @@ $text['core_user']['login'][9] = 'Erreur de captcha';
$text['core_user']['login'][10] = 'Accès bloqué ';
$text['core_user']['login'][11] = 'Connexion';
$text['core_user']['login'][12] = '';
$text['core_user']['login'][13] = ' Renseignez le Captcha ';
$text['core_user']['logout'][0] = 'Déconnexion réussie';
$text['core_user']['reset'][0] = 'Incorrect';
$text['core_user']['reset'][1] = 'Nouveau mot de passe enregistré';

View File

@ -405,14 +405,40 @@ class user extends common {
if($this->isPost()) {
// Lire Id du compte
$userId = $this->getInput('userLoginId', helper::FILTER_ID, true);
$detectBot ='';
$captcha = true;
// Check le captcha
if(
$this->getData(['config','connect','captcha'])
AND password_verify($this->getInput('userLoginCaptcha', helper::FILTER_INT), $this->getInput('userLoginCaptchaResult') ) === false )
{
$captcha = false;
} else {
$captcha = true;
if( $this->getData(['config','connect','captcha']) ){
$code ='';
if( isset( $_REQUEST['codeCaptcha'])) $code = strtoupper($_REQUEST['codeCaptcha']);
// option de détection de robot en premier cochée et $_SESSION['humanBot']==='human'
if( $_SESSION['humanBot']==='human' && $this->getData(['config', 'connect', 'captchaBot'])=== true ) {
// Présence des cookies et checkbox cochée ?
$detectBot ='bot';
$captcha = false;
if ( isset ($_COOKIE['evtX']) && isset ($_COOKIE['evtO']) && isset ($_COOKIE['evtV']) && isset ($_COOKIE['evtA'])
&& isset ($_COOKIE['evtH']) && isset ($_COOKIE['evtS']) && $this->getInput('userHumanCheck', helper::FILTER_BOOLEAN) === true ) {
// Calcul des intervals de temps
$time1 = 0;
if( isset ($_COOKIE['evtC'])) $time1 = $_COOKIE['evtC'] - $_COOKIE['evtO']; // temps entre fin de saisie et ouverture de la page
$time2 = $_COOKIE['evtH'] - $_COOKIE['evtO']; // temps entre click checkbox et ouverture de la page
$time3 = $_COOKIE['evtV'] - $_COOKIE['evtH']; // temps entre validation formulaire et click checkbox
$time4 = $_COOKIE['evtS'] - $_COOKIE['evtA']; // temps passé sur la checkbox
if( ( $time1 >= 1000 || ( isset ($_COOKIE['evtX']) && !isset ($_COOKIE['evtC']) ) ) && $time2 >= 1000
&& $time3 >=300 && $time4 >=300 && $this->getInput('userInputBlue')==='' ) {
$detectBot = 'human';
$captcha = true;
}
}
// Bot présumé
if( $detectBot === 'bot') $_SESSION['humanBot']='bot';
}
// $_SESSION['humanBot']==='bot' ou option 'Pas de Captcha pour un humain' non validée
elseif( md5($code) !== $_SESSION['captcha'] ) {
$captcha = false;
} else {
$captcha = true;
}
}
/**
* Aucun compte existant
@ -501,6 +527,7 @@ class user extends common {
// Sinon notification d'échec
} else {
$notification = $text['core_user']['login'][7];
if( $detectBot = 'bot' ) $notification = $text['core_user']['login'][13];
$logStatus = $captcha === true ? $text['core_user']['login'][8] : $text['core_user']['login'][9];
// Cas 1 le nombre de connexions est inférieur aux tentatives autorisées : incrément compteur d'échec
if ($this->getData(['user',$userId,'connectFail']) < $this->getData(['config', 'connect', 'attempt'])) {

View File

@ -1 +1,20 @@
/** vide */
.userCheckBlue {
display: none;
}
.userOuter{
text-align: center;
margin: 0 auto;
width: 50%;
border-radius: 2px;
border: 1px solid;
}
@media screen and (max-width: 768px) {
.userOuter{
width: 100%;
}
}
.userInner{
display: inline-block;
}

View File

@ -0,0 +1,79 @@
/**
* This file is part of DeltaCMS.
* For full copyright and license information, please see the LICENSE
* file that was distributed with this source code.
* @author Sylvain Lelièvre <lelievresylvain@free.fr>
* @copyright Copyright (C) 2021-2022, Sylvain Lelièvre
* @license GNU General Public License, version 3
* @link https://deltacms.fr/
*
* Delta was created from version 11.2.00.24 of ZwiiCMS
* @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
*/
/* Création et mise à jour du cookie sur modification d'un input */
$( ".humanBot" ).mouseleave(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtC = " + time + ";SameSite=Strict";
});
/* Cookie supplémentaire pour autoremplissage des champs */
$( ".humanBot" ).change(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtX = " + time + ";SameSite=Strict";
});
/* Création d'un cookie à l'ouverture de la page formulaire*/
$(document).ready(function(){
const d = new Date();
time = d.getTime();
document.cookie = "evtO = " + time + ";SameSite=Strict";
});
/* Création d'un cookie à la validation de la checkbox 'je ne suis pas un robot'*/
$( ".humanCheck" ).click(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtH = " + time + ";SameSite=Strict";
});
/* Création d'un cookie quand on arrive sur la checkbox 'je ne suis pas un robot' */
$( ".humanCheck" ).mouseenter(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtA = " + time + ";SameSite=Strict";
});
/* Création d'un cookie quand on quitte la checkbox 'je ne suis pas un robot' */
$( ".humanCheck" ).mouseleave(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtS = " + time + ";SameSite=Strict";
});
/* Création d'un cookie à la validation du formulaire */
$( ".humanBotClose" ).click(function() {
const d = new Date();
time = d.getTime();
document.cookie = "evtV = " + time + ";SameSite=Strict";
});
/* Affecter la couleur de bordure des blocs ou du fond à la class formOuter */
$(document).ready(function(){
borderColor = "<?php echo $this->getData(['theme', 'block', 'borderColor']); ?>";
bgColor = "<?php echo $this->getData(['theme', 'site', 'backgroundColor']); ?>";
$(".userOuter").css("border-color", borderColor);
/* Modifier la couleur au survol */
$( ".userOuter" ).mouseenter(function() {
$(".userOuter").css("background-color", borderColor);
});
$( ".userOuter" ).mouseleave(function() {
$(".userOuter").css("background-color", bgColor);
});
});

View File

@ -1,8 +1,10 @@
<?php echo template::formOpen('userLoginForm');
<?php
// Lexique
include('./core/module/user/lang/'. $this->getData(['config', 'i18n', 'langAdmin']) . '/lex_user.php');
echo template::formOpen('userLoginForm');
?>
<div class="row">
<div class="row humanBot">
<div class="col6">
<?php echo template::text('userLoginId', [
'label' => $text['core_user_view']['login'][0],
@ -15,16 +17,32 @@ include('./core/module/user/lang/'. $this->getData(['config', 'i18n', 'langAdmin
]); ?>
</div>
</div>
<?php if ($this->getData(['config', 'connect','captcha'])): ?>
<div class="row">
<div class="col12 textAlignCenter">
<?php echo template::captcha('userLoginCaptcha', [
'limit' => $this->getData(['config','connect', 'captchaStrong']),
'type' => $this->getData(['config','connect', 'captchaType'])
]); ?>
<?php if ($this->getData(['config', 'connect','captcha'])){ ?>
<?php if( ($_SESSION['humanBot']==='bot') || $this->getData(['config', 'connect', 'captchaBot'])=== false ) { ?>
<div class="row">
<div class="col12 textAlignCenter">
<?php echo template::captcha('userLoginCaptcha', ''); ?>
</div>
</div>
</div>
<?php endif;?>
<?php } else { ?>
<div class="userCheckBlue">
<?php echo template::text('userInputBlue', [
'label' => 'Input Blue',
'value' => ''
]); ?>
</div>
<br>
<div class="userOuter">
<div class="userInner humanCheck">
<?php echo template::checkbox('userHumanCheck', true, $this->getData(['locale', 'captchaSimpleText']), [
'checked' => false,
'help' => $this->getData(['locale', 'captchaSimpleHelp']),
'required' => true
]); ?>
</div>
</div>
<br>
<?php } } ?>
<div class="row">
<div class="col6">
<?php echo template::checkbox('userLoginLongTime', true, $text['core_user_view']['login'][4], [
@ -36,14 +54,14 @@ include('./core/module/user/lang/'. $this->getData(['config', 'i18n', 'langAdmin
</div>
</div>
<div class="row">
<div class="col3 offset6">
<div class="col4 offset4">
<?php echo template::button('userLoginBack', [
'href' => helper::baseUrl() . str_replace('_', '/', str_replace('__', '#', $this->getUrl(2))),
'ico' => 'left',
'value' => $text['core_user_view']['login'][2]
]); ?>
</div>
<div class="col3">
<div class="col4 humanBotClose">
<?php echo template::submit('userLoginSubmit', [
'value' => $text['core_user_view']['login'][3],
'ico' => 'lock'

36
core/vendor/captcha/captcha.php vendored Normal file
View File

@ -0,0 +1,36 @@
<?php
session_start();
if ( !defined('ABSPATH') ) define('ABSPATH', dirname(__FILE__) . '/');
function random($tab) {
return $tab[array_rand($tab)];
}
$chars = '0123456789';
$char1 = mt_rand( 0, strlen($chars) - 1 );
$char2 = mt_rand( 0, strlen($chars) - 1 );
$_SESSION['captcha'] = md5((int)$char1 + (int)$char2);
// polices utilisées
$fonts = glob('polices/*.woff');
// création de l'image captcha
$image = imagecreatefrompng('captcha.png');
// couleurs des caractères
$colors = array ( imagecolorallocate($image, 238, 238, 238),
imagecolorallocate($image, 51, 51, 51),
imagecolorallocate($image, 0, 102, 153),
imagecolorallocate($image, 204, 0, 51),
imagecolorallocate($image, 255, 51, 51),
imagecolorallocate($image, 51, 255, 51),
imagecolorallocate($image, 255, 255, 51) );
// positions, polices, caractères et couleurs randomisées
imagettftext($image, 28, -10, 7, 50, random($colors), ABSPATH .'/'. random($fonts), $char1);
imagettftext($image, 28, 0, 50, 50, random($colors), ABSPATH .'/'. 'polices/Eskiula.woff', '+');
imagettftext($image, 28, -35, 75, 50, random($colors), ABSPATH .'/'. random($fonts), $char2);
imagettftext($image, 28, 0, 125, 50, random($colors), ABSPATH .'/'. 'polices/Eskiula.woff', '=');
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);
?>

BIN
core/vendor/captcha/captcha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

BIN
core/vendor/captcha/polices/Eskiula.woff vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
core/vendor/captcha/reload.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -77,8 +77,10 @@ tinymce.init({
{title: 'Une popup (Lity)', value: 'data-lity'},
{title: 'Une galerie d\'images (SimpleLightbox)', value: 'gallery'}
],
// Titre des image
// Titre des images
image_title: true,
// figure html5
image_caption: true,
// Pages internes
link_list: baseUrl + "core/vendor/tinymce/links.php",
// Contenu du menu contextuel

View File

@ -18,7 +18,7 @@
class blog extends common {
const VERSION = '6.3';
const VERSION = '6.4';
const REALNAME = 'Blog';
const DELETE = true;
const UPDATE = '0.0';
@ -290,20 +290,10 @@ class blog extends common {
$userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']);
}
unset($userFirstname);
// Passage de la langue d'administration à Tinymce et flatpickr
$lang_admin = $text['blog']['add'][2];
$lang_flatpickr = $text['blog']['add'][3];
?>
<script>
var lang_admin = "<?php echo $lang_admin; ?>";
var lang_flatpickr = "<?php echo $lang_flatpickr; ?>";
</script>
<?php
// Valeurs en sortie
$this->addOutput([
'title' => $text['blog']['add'][1],
'vendor' => [
'tinymce',
'flatpickr'
],
'view' => 'add'
@ -702,25 +692,11 @@ class blog extends common {
$userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']) . ' (' . $groupEdits[$this->getData(['user', $userId, 'group'])] . ')';
}
unset($userFirstname);
// Passage de la langue d'administration à Tinymce
$lang_admin = 'fr_FR';
$lang_flatpickr = 'fr';
if( $this->getData(['config', 'i18n', 'langAdmin']) ==='en'){
$lang_admin = 'en_GB';
$lang_flatpickr = 'default';
}
?>
<script>
var lang_admin = "<?php echo $lang_admin; ?>";
var lang_flatpickr = "<?php echo $lang_flatpickr; ?>";
</script>
<?php
// Valeurs en sortie
$this->addOutput([
'title' => $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(2), 'title']),
'vendor' => [
'flatpickr',
'tinymce'
'flatpickr'
],
'view' => 'edit'
]);
@ -781,67 +757,94 @@ class blog extends common {
else {
// Soumission du formulaire
if($this->isPost()) {
$detectBot ='';
// Check la captcha
if(
$this->getUser('password') !== $this->getInput('DELTA_USER_PASSWORD')
//AND $this->getInput('blogArticlecaptcha', helper::FILTER_INT) !== $this->getInput('blogArticlecaptchaFirstNumber', helper::FILTER_INT) + $this->getInput('blogArticlecaptchaSecondNumber', helper::FILTER_INT))
AND password_verify($this->getInput('blogArticleCaptcha', helper::FILTER_INT), $this->getInput('blogArticleCaptchaResult') ) === false )
{
self::$inputNotices['blogArticleCaptcha'] = 'Incorrect';
}
// Crée le commentaire
$key = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment']);
if( $key === null ) $key=array();
$commentId = helper::increment(uniqid(), $key);
$content = $this->getInput('blogArticleContent', false);
$this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentId, [
'author' => $this->getInput('blogArticleAuthor', helper::FILTER_STRING_SHORT, empty($this->getInput('blogArticleUserId')) ? TRUE : FALSE),
'content' => $content,
'createdOn' => time(),
'userId' => $this->getInput('blogArticleUserId'),
'approval' => !$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) // true commentaire publié false en attente de publication
]]);
// Envoi d'une notification aux administrateurs
// Init tableau
$to = [];
// Liste des destinataires
foreach($this->getData(['user']) as $userId => $user) {
if ($user['group'] >= $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentGroupNotification']) ) {
$to[] = $user['mail'];
$firstname[] = $user['firstname'];
$lastname[] = $user['lastname'];
if( $this->getUser('password') !== $this->getInput('DELTA_USER_PASSWORD') ){
$code = strtoupper($_REQUEST['codeCaptcha']);
// option de détection de robot en premier cochée et $_SESSION['humanBot']==='human'
if( $_SESSION['humanBot']==='human' && $this->getData(['config', 'connect', 'captchaBot'])=== true ) {
// Présence des 6 cookies et checkbox cochée ?
$detectBot ='bot';
if ( isset ($_COOKIE['evtC']) && isset ($_COOKIE['evtO']) && isset ($_COOKIE['evtV']) && isset ($_COOKIE['evtA'])
&& isset ($_COOKIE['evtH']) && isset ($_COOKIE['evtS']) && $this->getInput('blogHumanCheck', helper::FILTER_BOOLEAN) === true ) {
// Calcul des intervals de temps
$time1 = $_COOKIE['evtC'] - $_COOKIE['evtO']; // temps entre fin de saisie et ouverture de la page
$time2 = $_COOKIE['evtH'] - $_COOKIE['evtO']; // temps entre click checkbox et ouverture de la page
$time3 = $_COOKIE['evtV'] - $_COOKIE['evtH']; // temps entre validation formulaire et click checkbox
$time4 = $_COOKIE['evtS'] - $_COOKIE['evtA']; // temps passé sur la checkbox
if( $time1 >= 5000 && $time2 >= 1000 && $time3 >=300
&& $time4 >=300 && $this->getInput('blogInputBlue')==='' ) $detectBot = 'human';
}
// Bot présumé
if( $detectBot === 'bot') $_SESSION['humanBot']='bot';
}
// $_SESSION['humanBot']==='bot' ou option 'Pas de Captcha pour un humain' non validée
elseif( md5($code) !== $_SESSION['captcha'] )
{
self::$inputNotices['blogArticleCaptcha'] = $text['blog']['index'][24];
}
}
// Envoi du mail $sent code d'erreur ou de réussite
$notification = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) === true ? $this->getData(['module', $this->getUrl(0), 'texts', 'Waiting']): $this->getData(['module', $this->getUrl(0), 'texts', 'CommentOK']);
if ($this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentNotification']) === true) {
$error = 0;
foreach($to as $key => $adress){
$sent = $this->sendMail(
$adress,
$text['blog']['index'][4],
$text['blog']['index'][5] . ' <strong>' . $firstname[$key] . ' ' . $lastname[$key] . '</strong>,<br><br>' .
$text['blog']['index'][6].'<a href="' . helper::baseUrl() . $this->getUrl(0) . '/ ' . $this->getUrl(1) . '">' . $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'title']) . '</a>'.$text['blog']['index'][7].'<br><br>',
''
);
if( $sent === false) $error++;
if( $detectBot !== 'bot' ){
// Crée le commentaire
$key = $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment']);
if( $key === null ) $key=array();
$commentId = helper::increment(uniqid(), $key);
$content = $this->getInput('blogArticleContent', false);
$this->setData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'comment', $commentId, [
'author' => $this->getInput('blogArticleAuthor', helper::FILTER_STRING_SHORT, empty($this->getInput('blogArticleUserId')) ? TRUE : FALSE),
'content' => $content,
'createdOn' => time(),
'userId' => $this->getInput('blogArticleUserId'),
'approval' => !$this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentApproved']) // true commentaire publié false en attente de publication
]]);
// Envoi d'une notification aux administrateurs
// Init tableau
$to = [];
// Liste des destinataires
foreach($this->getData(['user']) as $userId => $user) {
if ($user['group'] >= $this->getData(['module', $this->getUrl(0), 'posts', $this->getUrl(1), 'commentGroupNotification']) ) {
$to[] = $user['mail'];
$firstname[] = $user['firstname'];
$lastname