Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
cbc7a1ad19 | |||
e7e0fef37e | |||
d0f14f0e60 | |||
cca646b1be | |||
6db9837238 | |||
ca313a2b18 | |||
547dea7350 | |||
9d6e229e0a | |||
22d69ccafb | |||
5e59984211 | |||
0301e1f31d | |||
1f0d490688 | |||
288393778a | |||
7773bc916f | |||
5ebbcfcb9d | |||
f237d0bca1 | |||
70fa6efee7 | |||
30ed4349d9 | |||
ad998c17d1 | |||
4d6b6f8d4f | |||
dd3582b9db | |||
0e26e71a50 | |||
9a56fc9572 | |||
ac8689b40a | |||
0f9a79411d | |||
b944a3bac7 | |||
4bab81c541 | |||
2f3dd5926b | |||
959139b239 | |||
e958287b9e | |||
e110e7b4f9 | |||
61ec8c04be | |||
f0ccf8eb2f | |||
b831275901 | |||
5217763afb | |||
6d19f6ebad | |||
ed2b2c2826 | |||
91e18a9408 | |||
9ae150f3aa | |||
88acbae810 | |||
162bb9a78d | |||
81a996c714 | |||
2e9cfaa991 | |||
f2df3743c6 | |||
83943c6b52 |
@ -1,4 +1,4 @@
|
|||||||
# ZwiiCampus 1.18.00
|
# ZwiiCampus 1.21.00
|
||||||
|
|
||||||
ZwiiCampus (Learning Management System) est logiciel auteur destiné à mettre en ligne des tutoriels. Il dispose de plusieurs modalités d'ouverture et d'accès des contenus. Basé sur la version 13 du CMS Zwii, la structure logicielle est solide, le framework de Zwii est éprouvé.
|
ZwiiCampus (Learning Management System) est logiciel auteur destiné à mettre en ligne des tutoriels. Il dispose de plusieurs modalités d'ouverture et d'accès des contenus. Basé sur la version 13 du CMS Zwii, la structure logicielle est solide, le framework de Zwii est éprouvé.
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PhpStorm.
|
* Created by PhpStorm.
|
||||||
* User: Andrey Mistulov
|
* User: Andrey Mistulov
|
||||||
@ -112,78 +113,97 @@ class JsonDb extends \Prowebcraft\Dot
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local database upload
|
* Charge les données depuis un fichier JSON.
|
||||||
* @param bool $reload Reboot data?
|
*
|
||||||
* @return array|mixed|null
|
* @param bool $reload Force le rechargement des données si true
|
||||||
|
*
|
||||||
|
* @return array|null Les données chargées ou null si le fichier n'existe pas
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException En cas d'erreur lors de la création de la sauvegarde
|
||||||
|
* @throws \InvalidArgumentException Si le fichier contient des données JSON invalides
|
||||||
*/
|
*/
|
||||||
protected function loadData($reload = false)
|
protected function loadData($reload = false): ?array
|
||||||
{
|
{
|
||||||
if ($this->data === null || $reload) {
|
if ($this->data === null || $reload) {
|
||||||
$this->db = $this->config['dir'] . $this->config['name'];
|
$this->db = $this->config['dir'] . $this->config['name'];
|
||||||
|
|
||||||
if (!file_exists($this->db)) {
|
if (!file_exists($this->db)) {
|
||||||
return null; // Rebuild database manage by CMS
|
return null; // Rebuild database managed by CMS
|
||||||
} else {
|
}
|
||||||
|
|
||||||
if ($this->config['backup']) {
|
if ($this->config['backup']) {
|
||||||
|
$backup_path = $this->config['dir'] . DIRECTORY_SEPARATOR . $this->config['name'] . '.backup';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copy($this->config['dir'] . DIRECTORY_SEPARATOR . $this->config['name'], $this->config['dir'] . DIRECTORY_SEPARATOR . $this->config['name'] . '.backup');
|
if (!copy($this->db, $backup_path)) {
|
||||||
|
throw new \RuntimeException('Échec de la création de la sauvegarde');
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
error_log('Erreur de chargement : ' . $e);
|
throw new \RuntimeException('Erreur de sauvegarde : ' . $e->getMessage());
|
||||||
exit('Erreur de chargement : ' . $e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$this->data = json_decode(file_get_contents($this->db), true);
|
$file_contents = file_get_contents($this->db);
|
||||||
if (!$this->data === null) {
|
|
||||||
throw new \InvalidArgumentException('Le fichier ' . $this->db
|
$this->data = json_decode($file_contents, true);
|
||||||
. ' contient des données invalides.');
|
|
||||||
|
if ($this->data === null) {
|
||||||
|
throw new \InvalidArgumentException('Le fichier ' . $this->db . ' contient des données invalides.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save database
|
* Charge les données depuis un fichier JSON.
|
||||||
|
*
|
||||||
|
* @param bool $reload Force le rechargement des données si true
|
||||||
|
*
|
||||||
|
* @return array|null Les données chargées ou null si le fichier n'existe pas
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException En cas d'erreur lors de la création de la sauvegarde
|
||||||
|
* @throws \InvalidArgumentException Si le fichier contient des données JSON invalides
|
||||||
*/
|
*/
|
||||||
public function save()
|
public function save(): void
|
||||||
{
|
{
|
||||||
// Encode les données au format JSON avec les options spécifiées
|
if ($this->data === null) {
|
||||||
//$encoded_data = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT);
|
throw new \RuntimeException('Tentative de sauvegarde de données nulles');
|
||||||
$encoded_data = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT);
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$encoded_data = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_THROW_ON_ERROR);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
throw new \RuntimeException('Erreur d\'encodage JSON : ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Vérifie la longueur de la chaîne JSON encodée
|
|
||||||
$encoded_length = strlen($encoded_data);
|
$encoded_length = strlen($encoded_data);
|
||||||
|
$max_attempts = 5;
|
||||||
|
|
||||||
// Initialise le compteur de tentatives
|
for ($attempt = 0; $attempt < $max_attempts; $attempt++) {
|
||||||
$attempt = 0;
|
$temp_file = $this->db . '.tmp' . uniqid();
|
||||||
|
|
||||||
// Tente d'encoder les données en JSON et de les sauvegarder jusqu'à 5 fois en cas d'échec
|
try {
|
||||||
while ($attempt < 5) {
|
$write_result = file_put_contents($temp_file, $encoded_data, LOCK_EX);
|
||||||
// Essaye d'écrire les données encodées dans le fichier de base de données
|
|
||||||
$write_result = file_put_contents($this->db, $encoded_data, LOCK_EX); // Les utilisateurs multiples obtiennent un verrou
|
|
||||||
|
|
||||||
//$now = \DateTime::createFromFormat('U.u', microtime(true));
|
|
||||||
//file_put_contents("tmplog.txt", '[JsonDb][' . $now->format('H:i:s.u') . ']--' . $this->db . "\r\n", FILE_APPEND);
|
|
||||||
|
|
||||||
// Vérifie si l'écriture a réussi
|
|
||||||
if ($write_result === $encoded_length) {
|
if ($write_result === $encoded_length) {
|
||||||
// Sort de la boucle si l'écriture a réussi
|
if (rename($temp_file, $this->db)) {
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incrémente le compteur de tentatives
|
error_log("Échec sauvegarde : longueur incorrecte ou renommage échoué (tentative " . ($attempt + 1) . ")");
|
||||||
$attempt++;
|
} catch (\Exception $e) {
|
||||||
|
error_log('Erreur de sauvegarde : ' . $e->getMessage());
|
||||||
|
|
||||||
// Attente 1/4 de seconde
|
if (file_exists($temp_file)) {
|
||||||
usleep(0.25);
|
unlink($temp_file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifie si l'écriture a échoué même après plusieurs tentatives
|
usleep(pow(2, $attempt) * 250000);
|
||||||
if ($write_result !== $encoded_length) {
|
|
||||||
// Enregistre un message d'erreur dans le journal des erreurs
|
|
||||||
error_log('Erreur d\'écriture, les données n\'ont pas été sauvegardées.');
|
|
||||||
|
|
||||||
// Affiche un message d'erreur et termine le script
|
|
||||||
exit('Erreur d\'écriture, les données n\'ont pas été sauvegardées.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException('Échec de sauvegarde après ' . $max_attempts . ' tentatives');
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -613,8 +613,7 @@ class layout extends common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retourne les items du menu
|
// Retourne les items du menu
|
||||||
echo '<ul class="navMain" id="menuLeft">' . $itemsLeft . '</ul><ul class="navMain" id="menuRight">' . $itemsRight;
|
echo '<ul class="navMain" id="menuLeft">' . $itemsLeft . '</ul><ul class="navMain" id="menuRight">' . $itemsRight . '</ul>';
|
||||||
echo '</ul>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,7 +140,7 @@ class core extends common
|
|||||||
$css .= 'span.mce-text{background-color: unset !important;}';
|
$css .= 'span.mce-text{background-color: unset !important;}';
|
||||||
$css .= 'body,.row > div{font-size:' . $this->getData(['theme', 'text', 'fontSize']) . '}';
|
$css .= 'body,.row > div{font-size:' . $this->getData(['theme', 'text', 'fontSize']) . '}';
|
||||||
$css .= 'body{color:' . $this->getData(['theme', 'text', 'textColor']) . '}';
|
$css .= 'body{color:' . $this->getData(['theme', 'text', 'textColor']) . '}';
|
||||||
$css .= 'select,input[type=password],input[type=email],input[type=text],input[type=date],input[type=time],input[type=week],input[type=month],input[type=datetime-local],.inputFile,select,textarea{color:' . $this->getData(['theme', 'text', 'textColor']) . ';background-color:' . $this->getData(['theme', 'site', 'backgroundColor']) . ';}';
|
$css .= 'select,input[type=password],input[type=email],input[type=text],input[type=date],input[type=time],input[type=week],input[type=month],input[type=datetime-local],input[type=number],.inputFile,select,textarea{color:' . $this->getData(['theme', 'text', 'textColor']) . ';background-color:' . $this->getData(['theme', 'site', 'backgroundColor']) . ';}';
|
||||||
// spécifiques au module de blog
|
// spécifiques au module de blog
|
||||||
$css .= '.blogDate {color:' . $this->getData(['theme', 'text', 'textColor']) . ';}.blogPicture img{border:1px solid ' . $this->getData(['theme', 'text', 'textColor']) . '; box-shadow: 1px 1px 5px ' . $this->getData(['theme', 'text', 'textColor']) . ';}';
|
$css .= '.blogDate {color:' . $this->getData(['theme', 'text', 'textColor']) . ';}.blogPicture img{border:1px solid ' . $this->getData(['theme', 'text', 'textColor']) . '; box-shadow: 1px 1px 5px ' . $this->getData(['theme', 'text', 'textColor']) . ';}';
|
||||||
// Couleur fixée dans admin.css
|
// Couleur fixée dans admin.css
|
||||||
@ -168,7 +168,7 @@ class core extends common
|
|||||||
$colors = helper::colorVariants($this->getData(['theme', 'button', 'backgroundColor']));
|
$colors = helper::colorVariants($this->getData(['theme', 'button', 'backgroundColor']));
|
||||||
$css .= '.speechBubble,.button,.button:hover,button[type=submit],.pagination a,.pagination a:hover,input[type=checkbox]:checked + label:before,input[type=radio]:checked + label:before,.helpContent{background-color:' . $colors['normal'] . ';color:' . $colors['text'] . '}';
|
$css .= '.speechBubble,.button,.button:hover,button[type=submit],.pagination a,.pagination a:hover,input[type=checkbox]:checked + label:before,input[type=radio]:checked + label:before,.helpContent{background-color:' . $colors['normal'] . ';color:' . $colors['text'] . '}';
|
||||||
$css .= '.helpButton span{color:' . $colors['normal'] . '}';
|
$css .= '.helpButton span{color:' . $colors['normal'] . '}';
|
||||||
$css .= 'input[type=text]:hover,input[type=date]:hover,input[type=time]:hover,input[type=week]:hover,input[type=month]:hover,input[type=datetime-local]:hover,input[type=password]:hover,.inputFile:hover,select:hover,textarea:hover{border-color:' . $colors['normal'] . '}';
|
$css .= 'input[type=text]:hover,input[type=date]:hover,input[type=time]:hover,input[type=week]:hover,input[type=month]:hover,input[type=datetime-local]:hover,input[type=number]:hover,input[type=password]:hover,.inputFile:hover,select:hover,textarea:hover{border-color:' . $colors['normal'] . '}';
|
||||||
$css .= '.speechBubble:before{border-color:' . $colors['normal'] . ' transparent transparent transparent}';
|
$css .= '.speechBubble:before{border-color:' . $colors['normal'] . ' transparent transparent transparent}';
|
||||||
$css .= '.button:hover,button[type=submit]:hover,.pagination a:hover,input[type=checkbox]:not(:active):checked:hover + label:before,input[type=checkbox]:active + label:before,input[type=radio]:checked:hover + label:before,input[type=radio]:not(:checked):active + label:before{background-color:' . $colors['darken'] . '}';
|
$css .= '.button:hover,button[type=submit]:hover,.pagination a:hover,input[type=checkbox]:not(:active):checked:hover + label:before,input[type=checkbox]:active + label:before,input[type=radio]:checked:hover + label:before,input[type=radio]:not(:checked):active + label:before{background-color:' . $colors['darken'] . '}';
|
||||||
$css .= '.helpButton span:hover{color:' . $colors['darken'] . '}';
|
$css .= '.helpButton span:hover{color:' . $colors['darken'] . '}';
|
||||||
@ -254,18 +254,18 @@ class core extends common
|
|||||||
|
|
||||||
|
|
||||||
// Déterminer la hauteur max du menu pour éviter les débordements
|
// Déterminer la hauteur max du menu pour éviter les débordements
|
||||||
$padding = $this->getData(['theme', 'menu', 'height']); // Par exemple, "10px 20px"
|
// $padding = $this->getData(['theme', 'menu', 'height']); // Par exemple, "10px 20px"
|
||||||
$fontSize = (float) $this->getData(['theme', 'text', 'fontSize']); // Taille de référence en pixels
|
// $fontSize = (float) $this->getData(['theme', 'text', 'fontSize']); // Taille de référence en pixels
|
||||||
$menuFontSize = (float) $this->getData(['theme', 'menu', 'fontSize']); // Taille du menu en em
|
// $menuFontSize = (float) $this->getData(['theme', 'menu', 'fontSize']); // Taille du menu en em
|
||||||
|
|
||||||
// Extraire la première valeur du padding (par exemple "10px 20px" -> "10px")
|
// Extraire la première valeur du padding (par exemple "10px 20px" -> "10px")
|
||||||
$firstPadding = (float) explode(" ", $padding)[0]; // Nous prenons la première valeur, supposée être en px
|
// $firstPadding = (float) explode(" ", $padding)[0]; // Nous prenons la première valeur, supposée être en px
|
||||||
|
|
||||||
// Convertir menuFontSize (en em) en pixels
|
// Convertir menuFontSize (en em) en pixels
|
||||||
$menuFontSizeInPx = $menuFontSize * $fontSize;
|
// $menuFontSizeInPx = $menuFontSize * $fontSize;
|
||||||
|
|
||||||
// Calculer la hauteur totale
|
// Calculer la hauteur totale
|
||||||
$totalHeight = $firstPadding + $fontSize + $menuFontSizeInPx;
|
// $totalHeight = $firstPadding + $fontSize + $menuFontSizeInPx;
|
||||||
|
|
||||||
// Fixer la hauteur maximale de la barre de menu
|
// Fixer la hauteur maximale de la barre de menu
|
||||||
// $css .= '#menuLeft, nav, a.active {max-height:' . $totalHeight . 'px}';
|
// $css .= '#menuLeft, nav, a.active {max-height:' . $totalHeight . 'px}';
|
||||||
@ -381,7 +381,7 @@ class core extends common
|
|||||||
$css .= '.button.buttonGreen, button[type=submit] {background-color: ' . $colors['normal'] . ';color: ' . $colors['text'] . ';}.button.buttonGreen:hover, button[type=submit]:hover {background-color: ' . $colors['darken'] . ';color: ' . $colors['text'] . ';}.button.buttonGreen:active, button[type=submit]:active {background-color: ' . $colors['darken'] . ';color: ' . $colors['text'] . ';}';
|
$css .= '.button.buttonGreen, button[type=submit] {background-color: ' . $colors['normal'] . ';color: ' . $colors['text'] . ';}.button.buttonGreen:hover, button[type=submit]:hover {background-color: ' . $colors['darken'] . ';color: ' . $colors['text'] . ';}.button.buttonGreen:active, button[type=submit]:active {background-color: ' . $colors['darken'] . ';color: ' . $colors['text'] . ';}';
|
||||||
$colors = helper::colorVariants($this->getData(['admin', 'backgroundBlockColor']));
|
$colors = helper::colorVariants($this->getData(['admin', 'backgroundBlockColor']));
|
||||||
$css .= '.buttonTab, .block {border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . ';}.buttonTab, .block h4 {background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';}';
|
$css .= '.buttonTab, .block {border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . ';}.buttonTab, .block h4 {background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';}';
|
||||||
$css .= 'table tr,input[type=email],input[type=date],input[type=time],input[type=month],input[type=week],input[type=datetime-local],input[type=text],input[type=password],select:not(#barSelectCourse),select:not(#menuSelectCourse),select:not(#barSelectPage),textarea:not(.editorWysiwyg), textarea:not(.editorWysiwygComment),.inputFile{background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . ';}';
|
$css .= 'table tr,input[type=email],input[type=date],input[type=time],input[type=month],input[type=week],input[type=datetime-local],input[type=text],input[type=number],input[type=password],select:not(#barSelectLanguage),select:not(#barSelectPage),textarea:not(.editorWysiwyg), textarea:not(.editorWysiwygComment),.inputFile{background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . ';}';
|
||||||
// Bordure du contour TinyMCE
|
// Bordure du contour TinyMCE
|
||||||
$css .= '.mce-tinymce{border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . '!important;}';
|
$css .= '.mce-tinymce{border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . '!important;}';
|
||||||
// Enregistre la personnalisation
|
// Enregistre la personnalisation
|
||||||
|
@ -884,6 +884,10 @@ class template
|
|||||||
$html = '<div id="' . $attributes['id'] . 'Wrapper" class="tableWrapper ' . $attributes['classWrapper'] . '">';
|
$html = '<div id="' . $attributes['id'] . 'Wrapper" class="tableWrapper ' . $attributes['classWrapper'] . '">';
|
||||||
// Début tableau
|
// Début tableau
|
||||||
$html .= '<table id="' . $attributes['id'] . '" class="table ' . $attributes['class'] . '">';
|
$html .= '<table id="' . $attributes['id'] . '" class="table ' . $attributes['class'] . '">';
|
||||||
|
// Pas de tableau d'Id transmis, générer une numérotation
|
||||||
|
if (empty($rowsId)) {
|
||||||
|
$rowsId = range(0, count($cols));
|
||||||
|
}
|
||||||
// Entêtes
|
// Entêtes
|
||||||
if ($head) {
|
if ($head) {
|
||||||
// Début des entêtes
|
// Début des entêtes
|
||||||
@ -891,21 +895,17 @@ class template
|
|||||||
$html .= '<tr class="nodrag">';
|
$html .= '<tr class="nodrag">';
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($head as $th) {
|
foreach ($head as $th) {
|
||||||
$html .= '<th class="col' . $cols[$i++] . '">' . $th . '</th>';
|
$html .= '<th id="' . $rowsId[$i] . '" class="col' . $cols[$i++] . '">' . $th . '</th>';
|
||||||
}
|
}
|
||||||
// Fin des entêtes
|
// Fin des entêtes
|
||||||
$html .= '</tr>';
|
$html .= '</tr>';
|
||||||
$html .= '</thead>';
|
$html .= '</thead>';
|
||||||
}
|
}
|
||||||
// Pas de tableau d'Id transmis, générer une numérotation
|
|
||||||
if (empty($rowsId)) {
|
|
||||||
$rowsId = range(0, count($body));
|
|
||||||
}
|
|
||||||
// Début contenu
|
// Début contenu
|
||||||
$j = 0;
|
$j = 0;
|
||||||
foreach ($body as $tr) {
|
foreach ($body as $tr) {
|
||||||
// Id de ligne pour les tableaux drag and drop
|
// Id de ligne pour les tableaux drag and drop
|
||||||
$html .= '<tr id="' . $rowsId[$j++] . '">';
|
$html .= '<tr>';
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($tr as $td) {
|
foreach ($tr as $td) {
|
||||||
$html .= '<td class="col' . $cols[$i++] . '">' . $td . '</td>';
|
$html .= '<td class="col' . $cols[$i++] . '">' . $td . '</td>';
|
||||||
@ -992,6 +992,117 @@ class template
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un champ de saisie de type number (input[type="number"])
|
||||||
|
*
|
||||||
|
* Cette méthode crée un champ numérique HTML complet avec son wrapper,
|
||||||
|
* son label et ses messages d'aide/erreur. Elle gère automatiquement
|
||||||
|
* la conversion des valeurs en nombres et les contraintes de validation.
|
||||||
|
*
|
||||||
|
* @param string $nameId Identifiant unique du champ, utilisé pour name et id
|
||||||
|
* @param array $attributes Tableau des attributs du champ avec les clés suivantes :
|
||||||
|
* @type boolean $before Active la récupération des données précédentes en cas d'erreur (défaut: true)
|
||||||
|
* @type string $class Classes CSS additionnelles pour l'input (défaut: '')
|
||||||
|
* @type string $classWrapper Classes CSS additionnelles pour le wrapper (défaut: '')
|
||||||
|
* @type boolean $noDirty Désactive le marquage dirty du champ (défaut: false)
|
||||||
|
* @type boolean $disabled Désactive le champ (défaut: false)
|
||||||
|
* @type string $help Texte d'aide affiché sous le label (défaut: '')
|
||||||
|
* @type string $label Texte du label (défaut: '')
|
||||||
|
* @type string $placeholder Texte de placeholder (défaut: '')
|
||||||
|
* @type boolean $readonly Rend le champ en lecture seule (défaut: false)
|
||||||
|
* @type mixed $value Valeur initiale du champ (défaut: '')
|
||||||
|
* @type number $min Valeur minimum autorisée (défaut: null)
|
||||||
|
* @type number $max Valeur maximum autorisée (défaut: null)
|
||||||
|
* @type number $step Pas d'incrémentation (ex: 1 pour entiers, 0.01 pour prix) (défaut: null)
|
||||||
|
* @type string $pattern Expression régulière de validation (défaut: null)
|
||||||
|
*
|
||||||
|
* @return string Code HTML du champ number complet
|
||||||
|
*/
|
||||||
|
public static function number($nameId, array $attributes = [])
|
||||||
|
{
|
||||||
|
// Attributs par défaut spécifiques aux champs numériques
|
||||||
|
$attributes = array_merge([
|
||||||
|
'type' => 'number',
|
||||||
|
'before' => true,
|
||||||
|
'class' => '',
|
||||||
|
'classWrapper' => '',
|
||||||
|
'noDirty' => false,
|
||||||
|
'disabled' => false,
|
||||||
|
'help' => '',
|
||||||
|
'id' => $nameId,
|
||||||
|
'label' => '',
|
||||||
|
'name' => $nameId,
|
||||||
|
'placeholder' => '',
|
||||||
|
'readonly' => false,
|
||||||
|
'required' => false,
|
||||||
|
'value' => '',
|
||||||
|
'min' => null,
|
||||||
|
'max' => null,
|
||||||
|
'step' => null,
|
||||||
|
'pattern' => null
|
||||||
|
], $attributes);
|
||||||
|
|
||||||
|
// Conversion de la valeur en nombre si elle n'est pas vide
|
||||||
|
if ($attributes['value'] !== '') {
|
||||||
|
$attributes['value'] = floatval($attributes['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nettoyage des attributs null pour ne pas les afficher dans le HTML
|
||||||
|
foreach (['min', 'max', 'step', 'pattern'] as $attr) {
|
||||||
|
if ($attributes[$attr] === null) {
|
||||||
|
unset($attributes[$attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traduction de l'aide et de l'étiquette
|
||||||
|
$attributes['label'] = helper::translate($attributes['label']);
|
||||||
|
$attributes['help'] = helper::translate($attributes['help']);
|
||||||
|
|
||||||
|
// Sauvegarde des données en cas d'erreur
|
||||||
|
if ($attributes['before'] && array_key_exists($attributes['id'], common::$inputBefore)) {
|
||||||
|
$attributes['value'] = common::$inputBefore[$attributes['id']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion du champ obligatoire
|
||||||
|
if (isset($attributes['required']) && $attributes['required']) {
|
||||||
|
// Affiche l'astérisque dans le label
|
||||||
|
$required = ' required-field';
|
||||||
|
// Ajoute l'attribut required au champ input
|
||||||
|
$attributes['required'] = 'required';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Début du wrapper
|
||||||
|
$html = '<div id="' . $attributes['id'] . 'Wrapper" class="inputWrapper ' . $attributes['classWrapper'] . '">';
|
||||||
|
|
||||||
|
// Label
|
||||||
|
if ($attributes['label']) {
|
||||||
|
$html .= self::label($attributes['id'], $attributes['label'], [
|
||||||
|
'help' => $attributes['help'],
|
||||||
|
// Ajoute la classe required-field si le champ est obligatoire
|
||||||
|
'class' => isset($required) ? $required : ''
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice
|
||||||
|
$notice = '';
|
||||||
|
if (array_key_exists($attributes['id'], common::$inputNotices)) {
|
||||||
|
$notice = common::$inputNotices[$attributes['id']];
|
||||||
|
$attributes['class'] .= ' notice';
|
||||||
|
}
|
||||||
|
$html .= self::notice($attributes['id'], $notice);
|
||||||
|
|
||||||
|
// Input number
|
||||||
|
$html .= sprintf(
|
||||||
|
'<input type="number" %s>',
|
||||||
|
helper::sprintAttributes($attributes)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fin du wrapper
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crée un champ texte long
|
* Crée un champ texte long
|
||||||
* @param string $nameId Nom et id du champ
|
* @param string $nameId Nom et id du champ
|
||||||
|
@ -116,30 +116,14 @@ core.confirm = function (text, yesCallback, noCallback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scripts à exécuter en dernier
|
* Scripts à exécuter en dernier
|
||||||
*/
|
|
||||||
core.end = function () {
|
core.end = function () {
|
||||||
/**
|
|
||||||
* Modifications non enregistrées du formulaire
|
|
||||||
*/
|
|
||||||
var formDOM = $("form");
|
|
||||||
// Ignore :
|
|
||||||
// - TinyMCE car il gère lui même le message
|
|
||||||
// - Les champs avec data-no-dirty
|
|
||||||
var inputsDOM = formDOM.find("input:not([data-no-dirty]), select:not([data-no-dirty]), textarea:not(.editorWysiwyg):not([data-no-dirty])");
|
|
||||||
var inputSerialize = inputsDOM.serialize();
|
|
||||||
$(window).on("beforeunload", function () {
|
|
||||||
if (inputsDOM.serialize() !== inputSerialize) {
|
|
||||||
message = "<?php echo helper::translate('Les modifications que vous avez apportées ne seront peut-être pas enregistrées.');?>";
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
formDOM.submit(function () {
|
|
||||||
$(window).off("beforeunload");
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
$(function () {
|
$(function () {
|
||||||
core.end();
|
core.end();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute une notice
|
* Ajoute une notice
|
||||||
@ -390,12 +374,11 @@ core.start = function () {
|
|||||||
var totalHeight = firstPadding + fontSize + menuFontSizeInPx;
|
var totalHeight = firstPadding + fontSize + menuFontSizeInPx;
|
||||||
$("#menuLeft").css({
|
$("#menuLeft").css({
|
||||||
"visibility": "hidden",
|
"visibility": "hidden",
|
||||||
"overflow": "hidden",
|
|
||||||
"max-width": "10px"
|
"max-width": "10px"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Par défaut pour tous les thèmes.
|
// Par défaut pour tous les thèmes.
|
||||||
$("#menuLeft, nav").css("max-height", totalHeight + "px");
|
$("#menuLeft").css("max-height", totalHeight + "px").css("min-height", totalHeight + "px");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class common
|
|||||||
const ACCESS_TIMER = 1800;
|
const ACCESS_TIMER = 1800;
|
||||||
|
|
||||||
// Numéro de version
|
// Numéro de version
|
||||||
const ZWII_VERSION = '1.18.00';
|
const ZWII_VERSION = '1.21.00';
|
||||||
|
|
||||||
// URL autoupdate
|
// URL autoupdate
|
||||||
const ZWII_UPDATE_URL = 'https://forge.chapril.org/ZwiiCMS-Team/campus-update/raw/branch/master/';
|
const ZWII_UPDATE_URL = 'https://forge.chapril.org/ZwiiCMS-Team/campus-update/raw/branch/master/';
|
||||||
@ -1251,23 +1251,35 @@ class common
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Création d'une miniature
|
* Crée une miniature à partir d'une image source.
|
||||||
* Fonction utilisée lors de la mise à jour d'une version 9 à une version 10
|
* Cette fonction prend en charge les formats raster (JPEG, PNG, GIF, WebP, AVIF) et vectoriels (SVG).
|
||||||
* @param string $src image source
|
* Pour les images vectorielles (SVG), aucune redimension n'est effectuée : une copie est réalisée.
|
||||||
* @param string $dets image destination
|
*
|
||||||
* @param integer $desired_width largeur demandée
|
* @param string $src Chemin de l'image source.
|
||||||
|
* @param string $dest Chemin de l'image destination (avec le nom du fichier et l'extension).
|
||||||
|
* @param int $desired_width Largeur demandée pour la miniature (ignorée pour les SVG).
|
||||||
|
* @return bool True si l'opération a réussi, false sinon.
|
||||||
*/
|
*/
|
||||||
function makeThumb($src, $dest, $desired_width)
|
function makeThumb($src, $dest, $desired_width)
|
||||||
{
|
{
|
||||||
// Vérifier l'existence du dossier de destination.
|
// Vérifier l'existence du dossier de destination.
|
||||||
$fileInfo = pathinfo($dest);
|
$fileInfo = pathinfo($dest);
|
||||||
if (!is_dir($fileInfo['dirname'])) {
|
if (!is_dir($fileInfo['dirname'])) {
|
||||||
mkdir($fileInfo['dirname'], 0755, true);
|
mkdir($fileInfo['dirname'], 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$extension = strtolower($fileInfo['extension']);
|
||||||
|
$mime_type = mime_content_type($src);
|
||||||
|
|
||||||
|
// Gestion des fichiers SVG (copie simple sans redimensionnement)
|
||||||
|
if ($extension === 'svg' || $mime_type === 'image/svg+xml') {
|
||||||
|
return copy($src, $dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chargement de l'image source selon le type
|
||||||
$source_image = '';
|
$source_image = '';
|
||||||
// Type d'image
|
switch ($extension) {
|
||||||
switch ($fileInfo['extension']) {
|
|
||||||
case 'jpeg':
|
case 'jpeg':
|
||||||
case 'jpg':
|
case 'jpg':
|
||||||
$source_image = imagecreatefromjpeg($src);
|
$source_image = imagecreatefromjpeg($src);
|
||||||
@ -1282,35 +1294,51 @@ class common
|
|||||||
$source_image = imagecreatefromwebp($src);
|
$source_image = imagecreatefromwebp($src);
|
||||||
break;
|
break;
|
||||||
case 'avif':
|
case 'avif':
|
||||||
|
if (function_exists('imagecreatefromavif')) {
|
||||||
$source_image = imagecreatefromavif($src);
|
$source_image = imagecreatefromavif($src);
|
||||||
|
} else {
|
||||||
|
return false; // AVIF non supporté
|
||||||
}
|
}
|
||||||
// Image valide
|
break;
|
||||||
if ($source_image) {
|
default:
|
||||||
|
return false; // Format non pris en charge
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image valide (formats raster uniquement)
|
||||||
|
if (is_resource($source_image) || (is_object($source_image) && $source_image instanceof GdImage)) {
|
||||||
$width = imagesx($source_image);
|
$width = imagesx($source_image);
|
||||||
$height = imagesy($source_image);
|
$height = imagesy($source_image);
|
||||||
/* find the "desired height" of this thumbnail, relative to the desired width */
|
|
||||||
|
// Calcul de la hauteur proportionnelle à la largeur demandée
|
||||||
$desired_height = floor($height * ($desired_width / $width));
|
$desired_height = floor($height * ($desired_width / $width));
|
||||||
/* create a new, "virtual" image */
|
|
||||||
|
// Création d'une nouvelle image virtuelle redimensionnée
|
||||||
$virtual_image = imagecreatetruecolor($desired_width, $desired_height);
|
$virtual_image = imagecreatetruecolor($desired_width, $desired_height);
|
||||||
/* copy source image at a resized size */
|
|
||||||
|
// Copie de l'image source dans l'image virtuelle avec redimensionnement
|
||||||
imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
|
imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
|
||||||
switch (mime_content_type($src)) {
|
|
||||||
|
// Enregistrement de l'image redimensionnée au format approprié
|
||||||
|
switch ($mime_type) {
|
||||||
case 'image/jpeg':
|
case 'image/jpeg':
|
||||||
case 'image/jpg':
|
case 'image/jpg':
|
||||||
return (imagejpeg($virtual_image, $dest));
|
return imagejpeg($virtual_image, $dest);
|
||||||
case 'image/png':
|
case 'image/png':
|
||||||
return (imagepng($virtual_image, $dest));
|
return imagepng($virtual_image, $dest);
|
||||||
case 'image/gif':
|
case 'image/gif':
|
||||||
return (imagegif($virtual_image, $dest));
|
return imagegif($virtual_image, $dest);
|
||||||
case 'image/webp':
|
case 'image/webp':
|
||||||
return (imagewebp($virtual_image, $dest));
|
return imagewebp($virtual_image, $dest);
|
||||||
case 'image/avif':
|
case 'image/avif':
|
||||||
return (imageavif($virtual_image, $dest));
|
if (function_exists('imageavif')) {
|
||||||
}
|
return imageavif($virtual_image, $dest);
|
||||||
} else {
|
|
||||||
return (false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // En cas d'échec
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,20 +3,17 @@
|
|||||||
/**
|
/**
|
||||||
* Vérification de la version de PHP
|
* Vérification de la version de PHP
|
||||||
*/
|
*/
|
||||||
|
if (version_compare(PHP_VERSION, '7.2.0', '<')) {
|
||||||
if(version_compare(PHP_VERSION, '7.2.0', '<') ) {
|
displayErrorPage('PHP 7.2+ mini requis - PHP 7.2+ mini required');
|
||||||
exit('PHP 7.2+ mini requis - PHP 7.2+ mini required');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( version_compare(PHP_VERSION, '8.3.999', '>') ) {
|
if (version_compare(PHP_VERSION, '8.3.999', '>')) {
|
||||||
exit('PHP 8.3 pas encore supporté, installez PHP 7.n ou PHP 8.1.n - PHP 8.3 not yet supported, install PHP 7.n or PHP 8.1.n');
|
displayErrorPage('PHP 8.3 pas encore supporté, installez PHP 7.n ou PHP 8.1.n - PHP 8.3 not yet supported, install PHP 7.n or PHP 8.1.n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check les modules installés
|
* Check les modules installés
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$e = [
|
$e = [
|
||||||
'gd',
|
'gd',
|
||||||
'json',
|
'json',
|
||||||
@ -31,18 +28,22 @@ $e = [
|
|||||||
];
|
];
|
||||||
$m = get_loaded_extensions();
|
$m = get_loaded_extensions();
|
||||||
$b = false;
|
$b = false;
|
||||||
|
$missingModules = [];
|
||||||
foreach ($e as $k => $v) {
|
foreach ($e as $k => $v) {
|
||||||
if (array_search($v,$m) === false) {
|
if (array_search($v, $m) === false) {
|
||||||
$b = true;
|
$b = true;
|
||||||
echo '<pre><p>Module PHP : ' . $v . ' manquant - Module PHP ' . $v . ' missing.</p></pre>';
|
$missingModules[] = $v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($b)
|
if ($b) {
|
||||||
exit('<pre><p>ZwiiCMS ne peut pas démarrer ; activez les extensions requises dans PHP.ini- ZwiiCMS cannot start, enabled PHP missing extensions into PHP.ini</p></pre>');
|
$errorMessage = 'ZwiiCMS ne peut pas démarrer ; les modules PHP suivants sont manquants : ' . implode(', ', $missingModules) . '<br />';
|
||||||
/**
|
$errorMessage .= 'ZwiiCMS cannot start, the following PHP modules are missing: ' . implode(', ', $missingModules);
|
||||||
* Contrôle les htacess
|
displayErrorPage($errorMessage);
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrôle les htaccess
|
||||||
|
*/
|
||||||
$d = [
|
$d = [
|
||||||
'',
|
'',
|
||||||
'site/data/',
|
'site/data/',
|
||||||
@ -51,6 +52,33 @@ $d = [
|
|||||||
// 'site/i18n/', pas contrôler pour éviter les pbs de mise à jour
|
// 'site/i18n/', pas contrôler pour éviter les pbs de mise à jour
|
||||||
];
|
];
|
||||||
foreach ($d as $key) {
|
foreach ($d as $key) {
|
||||||
if (file_exists($key . '.htaccess') === false)
|
if (file_exists($key . '.htaccess') === false) {
|
||||||
exit('<pre>ZwiiCMS ne peut pas démarrer, le fichier ' .$key . '.htaccess est manquant.<br />ZwiiCMS cannot start, file ' . $key . '.htaccess is missing.</pre>' );
|
$errorMessage = 'ZwiiCMS ne peut pas démarrer, le fichier ' . $key . '.htaccess est manquant.<br />';
|
||||||
|
$errorMessage .= 'ZwiiCMS cannot start, file ' . $key . '.htaccess is missing.';
|
||||||
|
displayErrorPage($errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction pour afficher une page d'erreur stylisée
|
||||||
|
*/
|
||||||
|
function displayErrorPage($message)
|
||||||
|
{
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Erreur - ZwiiCMS</title>
|
||||||
|
<link rel="stylesheet" href="core\layout\error.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="error-container">
|
||||||
|
<h1>Erreur</h1>
|
||||||
|
<p>' . $message . '</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ if (
|
|||||||
$this->setData(['core', 'dataVersion', 1700]);
|
$this->setData(['core', 'dataVersion', 1700]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$this->getData(['core', 'dataVersion']) < 1800
|
$this->getData(['core', 'dataVersion']) < 1800
|
||||||
) {
|
) {
|
||||||
@ -46,3 +45,32 @@ if (
|
|||||||
}
|
}
|
||||||
$this->setData(['core', 'dataVersion', 1800]);
|
$this->setData(['core', 'dataVersion', 1800]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$this->getData(['core', 'dataVersion']) < 12002
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installe dans le thème du menu la variable hidePages
|
||||||
|
**/
|
||||||
|
// Tableau à insérer
|
||||||
|
$a = [
|
||||||
|
'theme' =>
|
||||||
|
['menu' => [
|
||||||
|
'hidePages' => false
|
||||||
|
]]];
|
||||||
|
// Parcourir la structure pour écrire dans les fichiers JSON
|
||||||
|
foreach ($this->getData(['course']) as $courseId => $courseValues) {
|
||||||
|
$d = json_decode(file_get_contents(self::DATA_DIR . $courseId . '/theme.json'), true);
|
||||||
|
// Insérer la variable hidePages si elle n'existe pas
|
||||||
|
if (isset($d['theme']['menu']['hidePages']) === false) {
|
||||||
|
$result = array_replace_recursive($d, $a);
|
||||||
|
file_put_contents(self::DATA_DIR . $courseId . '/theme.json', json_encode($result,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
// Forcer la régénération du fichier theme.css
|
||||||
|
if (file_exists(self::DATA_DIR . $courseId . '/theme.css')) {
|
||||||
|
unlink(self::DATA_DIR . $courseId . '/theme.css');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->setData(['core', 'dataVersion', 12002]);
|
||||||
|
}
|
||||||
|
@ -657,6 +657,7 @@ nav a:hover {
|
|||||||
|
|
||||||
#menuLeft {
|
#menuLeft {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menuRight {
|
#menuRight {
|
||||||
@ -1200,6 +1201,7 @@ input[type='datetime-local'],
|
|||||||
input[type='time'],
|
input[type='time'],
|
||||||
input[type='month'],
|
input[type='month'],
|
||||||
input[type='week'],
|
input[type='week'],
|
||||||
|
input[type='number'],
|
||||||
.inputFile,
|
.inputFile,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
@ -1225,6 +1227,7 @@ input[type='datetime-local']:hover,
|
|||||||
input[type='time']:hover,
|
input[type='time']:hover,
|
||||||
input[type='month']:hover,
|
input[type='month']:hover,
|
||||||
input[type='week']:hover,
|
input[type='week']:hover,
|
||||||
|
input[type='number']:hover,
|
||||||
.inputFile:hover,
|
.inputFile:hover,
|
||||||
select:hover,
|
select:hover,
|
||||||
textarea:hover {
|
textarea:hover {
|
||||||
@ -1239,6 +1242,7 @@ input[type='datetime-local'].notice,
|
|||||||
input[type='time'].notice,
|
input[type='time'].notice,
|
||||||
input[type='month'].notice,
|
input[type='month'].notice,
|
||||||
input[type='week'].notice,
|
input[type='week'].notice,
|
||||||
|
input[type='number'].notice,
|
||||||
.inputFile.notice,
|
.inputFile.notice,
|
||||||
select.notice,
|
select.notice,
|
||||||
textarea.notice {
|
textarea.notice {
|
||||||
@ -1254,6 +1258,7 @@ input[type='datetime-local'].notice:hover,
|
|||||||
input[type='time'].notice:hover,
|
input[type='time'].notice:hover,
|
||||||
input[type='month'].notice:hover,
|
input[type='month'].notice:hover,
|
||||||
input[type='week'].notice:hover,
|
input[type='week'].notice:hover,
|
||||||
|
input[type='number'].notice:hover,
|
||||||
.inputFile.notice:hover,
|
.inputFile.notice:hover,
|
||||||
select.notice:hover,
|
select.notice:hover,
|
||||||
textarea.notice:hover {
|
textarea.notice:hover {
|
||||||
|
22
core/layout/error.css
Normal file
22
core/layout/error.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
body {
|
||||||
|
color: #000;
|
||||||
|
font: 75%/1.7em "Helvetica Neue", Helvetica, arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 80px;
|
||||||
|
background: url('../vendor/zwiico/png/error.png') 30px 30px no-repeat #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
font-size: 300%;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #777;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
@ -20,7 +20,7 @@
|
|||||||
]); ?>
|
]); ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<?php echo template::text('configProxyPort', [
|
<?php echo template::number('configProxyPort', [
|
||||||
'label' => 'Port du proxy',
|
'label' => 'Port du proxy',
|
||||||
'placeholder' => '6060',
|
'placeholder' => '6060',
|
||||||
'value' => $this->getData(['config', 'proxyPort'])
|
'value' => $this->getData(['config', 'proxyPort'])
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<?php echo template::checkbox('configRewrite', true, 'Apache URL intelligentes', [
|
<?php echo template::checkbox('configRewrite', true, 'Apache URL intelligentes', [
|
||||||
'checked' => helper::checkRewrite(),
|
'checked' => helper::checkRewrite(),
|
||||||
'help' => 'Supprime le point d\'interrogation dans les URL, l\'option est indisponible avec les autres serveurs Web',
|
'help' => 'Supprime le point d\'interrogation dans les URL, l\'option est indisponible avec les autres serveurs Web',
|
||||||
'disabled' => helper::checkServerSoftware() === false and config->isModRewriteEnabled()
|
'disabled' => helper::checkServerSoftware() === false and self::isModRewriteEnabled()
|
||||||
]); ?>
|
]); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -180,7 +180,7 @@ class course extends common
|
|||||||
$this->initData('theme', $courseId);
|
$this->initData('theme', $courseId);
|
||||||
|
|
||||||
// Pointer RFM sur le dossier de l'espace
|
// Pointer RFM sur le dossier de l'espace
|
||||||
self::$siteContent = $courseId;
|
// self::$siteContent = $courseId;
|
||||||
// Ordonne les pages par position
|
// Ordonne les pages par position
|
||||||
$this->buildHierarchy();
|
$this->buildHierarchy();
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ class course extends common
|
|||||||
$this->initDB('page', $courseId);
|
$this->initDB('page', $courseId);
|
||||||
|
|
||||||
// Pointer RFM sur le dossier de l'espace
|
// Pointer RFM sur le dossier de l'espace
|
||||||
self::$siteContent = $courseId;
|
// self::$siteContent = $courseId;
|
||||||
|
|
||||||
// Ordonne les pages par position
|
// Ordonne les pages par position
|
||||||
$this->buildHierarchy();
|
$this->buildHierarchy();
|
||||||
@ -339,7 +339,7 @@ class course extends common
|
|||||||
|
|
||||||
// Valeurs en sortie
|
// Valeurs en sortie
|
||||||
$this->addOutput([
|
$this->addOutput([
|
||||||
'title' => sprintf('%s %s (%s)', helper::translate('Editer l\'espace'), $this->getData(['course', $courseId, 'title' ]), $this->getUrl(2)),
|
'title' => sprintf('%s %s (%s)', helper::translate('Editer l\'espace'), $this->getData(['course', $courseId, 'title']), $this->getUrl(2)),
|
||||||
'view' => 'edit'
|
'view' => 'edit'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -378,7 +378,7 @@ class course extends common
|
|||||||
$this->initDB('page', $courseId);
|
$this->initDB('page', $courseId);
|
||||||
|
|
||||||
// Pointer RFM sur le dossier de l'espace
|
// Pointer RFM sur le dossier de l'espace
|
||||||
self::$siteContent = $courseId;
|
// self::$siteContent = $courseId;
|
||||||
|
|
||||||
// Ordonne les pages par position
|
// Ordonne les pages par position
|
||||||
$this->buildHierarchy();
|
$this->buildHierarchy();
|
||||||
@ -398,7 +398,7 @@ class course extends common
|
|||||||
|
|
||||||
// Valeurs en sortie
|
// Valeurs en sortie
|
||||||
$this->addOutput([
|
$this->addOutput([
|
||||||
'title' => sprintf('%s %s (%s)', helper::translate('Gérer l\'espace'), $this->getData(['course', $courseId, 'title' ]), $this->getUrl(2)),
|
'title' => sprintf('%s %s (%s)', helper::translate('Gérer l\'espace'), $this->getData(['course', $courseId, 'title']), $this->getUrl(2)),
|
||||||
'view' => 'manage'
|
'view' => 'manage'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -736,17 +736,15 @@ class course extends common
|
|||||||
}
|
}
|
||||||
self::$courseUsers[] = [
|
self::$courseUsers[] = [
|
||||||
//$userId,
|
//$userId,
|
||||||
$this->getData(['user', $userId, 'firstname']) . ' ' . $this->getData(['user', $userId, 'lastname']),
|
sprintf('%s %s', $this->getData(['user', $userId, 'lastname']), $this->getData(['user', $userId, 'firstname'])),
|
||||||
array_key_exists('lastPageView', $userValue) && isset($pages[$userValue['lastPageView']]['title'])
|
array_key_exists('lastPageView', $userValue) && isset($pages['page'][$userValue['lastPageView']]['title'])
|
||||||
? $pages[$userValue['lastPageView']]['title']
|
? $pages['page'][$userValue['lastPageView']]['title']
|
||||||
: '',
|
|
||||||
array_key_exists('lastPageView', $userValue)
|
|
||||||
? helper::dateUTF8('%d/%m/%Y', $userValue['datePageView'])
|
|
||||||
: '',
|
|
||||||
array_key_exists('datePageView', $userValue)
|
|
||||||
? helper::dateUTF8('%H:%M', $userValue['datePageView'])
|
|
||||||
: '',
|
: '',
|
||||||
$this->getData(['user', $userId, 'tags']),
|
$this->getData(['user', $userId, 'tags']),
|
||||||
|
array_key_exists('lastPageView', $userValue)
|
||||||
|
// ? helper::dateUTF8('%d/%m/%Y', $userValue['datePageView'])
|
||||||
|
? $userValue['datePageView']
|
||||||
|
: '',
|
||||||
$reportButton,
|
$reportButton,
|
||||||
template::button('userDelete' . $userId, [
|
template::button('userDelete' . $userId, [
|
||||||
'class' => 'userDelete buttonRed',
|
'class' => 'userDelete buttonRed',
|
||||||
@ -1740,7 +1738,7 @@ class course extends common
|
|||||||
$this->initDB('page', $courseId);
|
$this->initDB('page', $courseId);
|
||||||
|
|
||||||
// Pointer RFM sur le dossier de l'espace
|
// Pointer RFM sur le dossier de l'espace
|
||||||
self::$siteContent = $courseId;
|
// self::$siteContent = $courseId;
|
||||||
|
|
||||||
// Ordonne les pages par position
|
// Ordonne les pages par position
|
||||||
$this->buildHierarchy();
|
$this->buildHierarchy();
|
||||||
@ -1846,7 +1844,7 @@ class course extends common
|
|||||||
|
|
||||||
// Valeurs en sortie
|
// Valeurs en sortie
|
||||||
$this->addOutput([
|
$this->addOutput([
|
||||||
'title' => sprintf('%s %s (%s)', helper::translate('Export des pages de l\'espace'), $this->getData(['course', $courseId, 'title' ]), $this->getUrl(2)),
|
'title' => sprintf('%s %s (%s)', helper::translate('Export des pages de l\'espace'), $this->getData(['course', $courseId, 'title']), $this->getUrl(2)),
|
||||||
'view' => 'export'
|
'view' => 'export'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ $(document).ready((function () {
|
|||||||
$(location).attr("href", _this.attr("href"))
|
$(location).attr("href", _this.attr("href"))
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
$.fn.dataTable.moment( 'DD/MM/YYYY' );
|
|
||||||
$('#dataTables').DataTable({
|
$('#dataTables').DataTable({
|
||||||
language: {
|
language: {
|
||||||
url: "core/vendor/datatables/french.json"
|
url: "core/vendor/datatables/french.json"
|
||||||
@ -32,11 +31,17 @@ $(document).ready((function () {
|
|||||||
"lengthMenu": [[10, 25, 50, 100, 299, -1], [10, 25, 50, 100, 200, "Tout"]],
|
"lengthMenu": [[10, 25, 50, 100, 299, -1], [10, 25, 50, 100, 200, "Tout"]],
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
target: 6,
|
targets: 3,
|
||||||
|
type: 'numeric',
|
||||||
|
render: function (data) {
|
||||||
|
return moment(data * 1000).format('DD/MM/YYYY HH:mm');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: 5,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
searchable: false
|
searchable: false
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php echo template::formClose(); ?>
|
<?php echo template::formClose(); ?>
|
||||||
<?php if (course::$courseUsers): ?>
|
<?php if (course::$courseUsers): ?>
|
||||||
<?php echo template::table([3, 4, 1, 1, 1, 1, 1], course::$courseUsers, ['Nom Prénom', 'Dernière page vue', 'Date' , 'Heure', 'Étiquettes', 'Progression', ''], ['id' => 'dataTables']); ?>
|
<?php echo template::table([3, 3, 2, 2, 1, 1], course::$courseUsers, ['Nom Prénom', 'Dernière page vue', 'Date' , 'Étiquettes', 'Progression', ''], ['id' => 'dataTables']); ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php echo template::speech('Aucun participant'); ?>
|
<?php echo template::speech('Aucun participant'); ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
@ -64,7 +64,7 @@ class init extends common
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'core' => [
|
'core' => [
|
||||||
'dataVersion' => 1700,
|
'dataVersion' => 12002,
|
||||||
'lastBackup' => 0,
|
'lastBackup' => 0,
|
||||||
'lastClearTmp' => 0,
|
'lastClearTmp' => 0,
|
||||||
'lastAutoUpdate' => 0,
|
'lastAutoUpdate' => 0,
|
||||||
@ -903,7 +903,8 @@ class init extends common
|
|||||||
'selectSpace' => true,
|
'selectSpace' => true,
|
||||||
'burgerLogo' => '',
|
'burgerLogo' => '',
|
||||||
'burgerContent' => 'title',
|
'burgerContent' => 'title',
|
||||||
'width' => 'container'
|
'width' => 'container',
|
||||||
|
'hidePages' => false,
|
||||||
],
|
],
|
||||||
'site' => [
|
'site' => [
|
||||||
'backgroundColor' => 'rgba(255, 255, 255, 1)',
|
'backgroundColor' => 'rgba(255, 255, 255, 1)',
|
||||||
|
@ -16,3 +16,7 @@
|
|||||||
/** NE PAS EFFACER
|
/** NE PAS EFFACER
|
||||||
* admin.css
|
* admin.css
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.container.light {
|
||||||
|
filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
@ -20,3 +20,7 @@
|
|||||||
.title {
|
.title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container.light {
|
||||||
|
filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
@ -24,7 +24,6 @@ class page extends common
|
|||||||
'duplicate' => self::GROUP_EDITOR,
|
'duplicate' => self::GROUP_EDITOR,
|
||||||
'jsEditor' => self::GROUP_EDITOR,
|
'jsEditor' => self::GROUP_EDITOR,
|
||||||
'cssEditor' => self::GROUP_EDITOR,
|
'cssEditor' => self::GROUP_EDITOR,
|
||||||
'register' => self::GROUP_EDITOR,
|
|
||||||
];
|
];
|
||||||
public static $pagesNoParentId = [
|
public static $pagesNoParentId = [
|
||||||
'' => 'Aucune'
|
'' => 'Aucune'
|
||||||
@ -474,7 +473,7 @@ class page extends common
|
|||||||
$this->setData(['config', 'page302', $pageId], false);
|
$this->setData(['config', 'page302', $pageId], false);
|
||||||
}
|
}
|
||||||
// Sauvegarde la base manuellement
|
// Sauvegarde la base manuellement
|
||||||
$this->saveDB(module: 'config');
|
$this->saveDB('config');
|
||||||
// Si la page est une page enfant, actualise les positions des autres enfants du parent, sinon actualise les pages sans parents
|
// Si la page est une page enfant, actualise les positions des autres enfants du parent, sinon actualise les pages sans parents
|
||||||
$lastPosition = 1;
|
$lastPosition = 1;
|
||||||
$hierarchy = $this->getInput('pageEditParentPageId') ? $this->getHierarchy($this->getInput('pageEditParentPageId')) : array_keys($this->getHierarchy());
|
$hierarchy = $this->getInput('pageEditParentPageId') ? $this->getHierarchy($this->getInput('pageEditParentPageId')) : array_keys($this->getHierarchy());
|
||||||
@ -594,6 +593,19 @@ class page extends common
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sauvegarde l'onglet de l'utilisateur
|
||||||
|
*/
|
||||||
|
$this->setData([
|
||||||
|
'user',
|
||||||
|
$this->getUser('id'),
|
||||||
|
'view',
|
||||||
|
[
|
||||||
|
'page' => $this->getInput('containerSelected'),
|
||||||
|
'config' => $this->getData(['user', $this->getUser('id'), 'view', 'config']),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
// Creation du contenu de la page
|
// Creation du contenu de la page
|
||||||
if (!is_dir(self::DATA_DIR . self::$siteContent . '/content')) {
|
if (!is_dir(self::DATA_DIR . self::$siteContent . '/content')) {
|
||||||
mkdir(self::DATA_DIR . self::$siteContent . '/content', 0755);
|
mkdir(self::DATA_DIR . self::$siteContent . '/content', 0755);
|
||||||
@ -760,25 +772,4 @@ class page extends common
|
|||||||
return json_encode($d);
|
return json_encode($d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stocke la variable dans les paramètres de l'utilisateur pour activer la tab à sa prochaine visite
|
|
||||||
* @return never
|
|
||||||
*/
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
$this->setData([
|
|
||||||
'user',
|
|
||||||
$this->getUser('id'),
|
|
||||||
'view',
|
|
||||||
[
|
|
||||||
'page' => $this->getUrl(2),
|
|
||||||
'config' => $this->getData(['user', $this->getUser('id'), 'view', 'config']),
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
// Valeurs en sortie
|
|
||||||
$this->addOutput([
|
|
||||||
'redirect' => helper::baseUrl() . 'page/edit/' . $this->getUrl(3) . '/' . self::$siteContent,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -283,10 +283,18 @@ $( document ).ready(function() {
|
|||||||
// Gestion des évènements
|
// Gestion des évènements
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmet le bouton de l'onglet sélectionné avant la soumission
|
||||||
|
*/
|
||||||
|
$('#pageEditForm').on('submit', function () {
|
||||||
|
$('#containerSelected').val(pageLayout);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sélection de la page de configuration à afficher
|
* Sélection de la page de configuration à afficher
|
||||||
*/
|
*/
|
||||||
$("#pageEditContentButton").on("click", function () {
|
$("#pageEditContentButton").on("click", function () {
|
||||||
|
pageLayout = "locale";
|
||||||
$("#pageEditContentContainer").show();
|
$("#pageEditContentContainer").show();
|
||||||
$("#pageEditExtensionContainer").hide();
|
$("#pageEditExtensionContainer").hide();
|
||||||
$("#pageEditPositionContainer").hide();
|
$("#pageEditPositionContainer").hide();
|
||||||
@ -294,23 +302,12 @@ $( document ).ready(function() {
|
|||||||
$("#pageEditPermissionContainer").hide();
|
$("#pageEditPermissionContainer").hide();
|
||||||
$("#pageEditContentButton").addClass("activeButton");
|
$("#pageEditContentButton").addClass("activeButton");
|
||||||
$("#pageEditExtensionButton").removeClass("activeButton");
|
$("#pageEditExtensionButton").removeClass("activeButton");
|
||||||
$("#PageEditPositionButton").removeClass("activeButton");
|
$("#pageEditPositionButton").removeClass("activeButton");
|
||||||
$("#pageEditLayoutButton").removeClass("activeButton");
|
$("#pageEditLayoutButton").removeClass("activeButton");
|
||||||
$("#pageEditPermissionButton").removeClass("activeButton");
|
$("#pageEditPermissionButton").removeClass("activeButton");
|
||||||
});
|
});
|
||||||
$("#pageEditExtensionButton").on("click", function () {
|
$("#pageEditPositionButton").on("click", function () {
|
||||||
$("#pageEditContentContainer").hide();
|
pageLayout = "position";
|
||||||
$("#pageEditExtensionContainer").show();
|
|
||||||
$("#pageEditPositionContainer").hide();
|
|
||||||
$("#pageEditLayoutContainer").hide();
|
|
||||||
$("#pageEditPermissionContainer").hide();
|
|
||||||
$("#pageEditContentButton").removeClass("activeButton");
|
|
||||||
$("#pageEditExtensionButton").addClass("activeButton");
|
|
||||||
$("#PageEditPositionButton").removeClass("activeButton");
|
|
||||||
$("#pageEditLayoutButton").removeClass("activeButton");
|
|
||||||
$("#pageEditPermissionButton").removeClass("activeButton");
|
|
||||||
});
|
|
||||||
$("#PageEditPositionButton").on("click", function () {
|
|
||||||
$("#pageEditContentContainer").hide();
|
$("#pageEditContentContainer").hide();
|
||||||
$("#pageEditExtensionContainer").hide();
|
$("#pageEditExtensionContainer").hide();
|
||||||
$("#pageEditPositionContainer").show();
|
$("#pageEditPositionContainer").show();
|
||||||
@ -318,11 +315,25 @@ $( document ).ready(function() {
|
|||||||
$("#pageEditPermissionContainer").hide();
|
$("#pageEditPermissionContainer").hide();
|
||||||
$("#pageEditContentButton").removeClass("activeButton");
|
$("#pageEditContentButton").removeClass("activeButton");
|
||||||
$("#pageEditExtensionButton").removeClass("activeButton");
|
$("#pageEditExtensionButton").removeClass("activeButton");
|
||||||
$("#PageEditPositionButton").addClass("activeButton");
|
$("#pageEditPositionButton").addClass("activeButton");
|
||||||
|
$("#pageEditLayoutButton").removeClass("activeButton");
|
||||||
|
$("#pageEditPermissionButton").removeClass("activeButton");
|
||||||
|
});
|
||||||
|
$("#pageEditExtensionButton").on("click", function () {
|
||||||
|
pageLayout = "extension";
|
||||||
|
$("#pageEditContentContainer").hide();
|
||||||
|
$("#pageEditExtensionContainer").show();
|
||||||
|
$("#pageEditPositionContainer").hide();
|
||||||
|
$("#pageEditLayoutContainer").hide();
|
||||||
|
$("#pageEditPermissionContainer").hide();
|
||||||
|
$("#pageEditContentButton").removeClass("activeButton");
|
||||||
|
$("#pageEditExtensionButton").addClass("activeButton");
|
||||||
|
$("#pageEditPositionButton").removeClass("activeButton");
|
||||||
$("#pageEditLayoutButton").removeClass("activeButton");
|
$("#pageEditLayoutButton").removeClass("activeButton");
|
||||||
$("#pageEditPermissionButton").removeClass("activeButton");
|
$("#pageEditPermissionButton").removeClass("activeButton");
|
||||||
});
|
});
|
||||||
$("#pageEditLayoutButton").on("click", function () {
|
$("#pageEditLayoutButton").on("click", function () {
|
||||||
|
pageLayout = "layout";
|
||||||
$("#pageEditContentContainer").hide();
|
$("#pageEditContentContainer").hide();
|
||||||
$("#pageEditExtensionContainer").hide();
|
$("#pageEditExtensionContainer").hide();
|
||||||
$("#pageEditPositionContainer").hide();
|
$("#pageEditPositionContainer").hide();
|
||||||
@ -330,11 +341,12 @@ $( document ).ready(function() {
|
|||||||
$("#pageEditPermissionContainer").hide();
|
$("#pageEditPermissionContainer").hide();
|
||||||
$("#pageEditContentButton").removeClass("activeButton");
|
$("#pageEditContentButton").removeClass("activeButton");
|
||||||
$("#pageEditExtensionButton").removeClass("activeButton");
|
$("#pageEditExtensionButton").removeClass("activeButton");
|
||||||
$("#PageEditPositionButton").removeClass("activeButton");
|
$("#pageEditPositionButton").removeClass("activeButton");
|
||||||
$("#pageEditLayoutButton").addClass("activeButton");
|
$("#pageEditLayoutButton").addClass("activeButton");
|
||||||
$("#pageEditPermissionButton").removeClass("activeButton");
|
$("#pageEditPermissionButton").removeClass("activeButton");
|
||||||
});
|
});
|
||||||
$("#pageEditPermissionButton").on("click", function () {
|
$("#pageEditPermissionButton").on("click", function () {
|
||||||
|
pageLayout = "permission";
|
||||||
$("#pageEditContentContainer").hide();
|
$("#pageEditContentContainer").hide();
|
||||||
$("#pageEditExtensionContainer").hide();
|
$("#pageEditExtensionContainer").hide();
|
||||||
$("#pageEditPositionContainer").hide();
|
$("#pageEditPositionContainer").hide();
|
||||||
|
@ -35,30 +35,33 @@
|
|||||||
<?php echo template::button('pageEditContentButton', [
|
<?php echo template::button('pageEditContentButton', [
|
||||||
'value' => 'Contenu',
|
'value' => 'Contenu',
|
||||||
'class' => 'buttonTab',
|
'class' => 'buttonTab',
|
||||||
'href' => helper::baseUrl() . 'page/register/content/' . $this->geturl(2)
|
//'href' => helper::baseUrl() . 'page/register/content/' . $this->geturl(2)
|
||||||
]); ?>
|
]); ?>
|
||||||
<?php echo template::button('pageEditPositionButton', [
|
<?php echo template::button('pageEditPositionButton', [
|
||||||
'value' => 'Menu',
|
'value' => 'Menu',
|
||||||
'class' => 'buttonTab',
|
'class' => 'buttonTab',
|
||||||
'href' => helper::baseUrl() . 'page/register/position/' . $this->geturl(2)
|
//'href' => helper::baseUrl() . 'page/register/position/' . $this->geturl(2)
|
||||||
]); ?>
|
]); ?>
|
||||||
<?php echo template::button('pageEditExtensionButton', [
|
<?php echo template::button('pageEditExtensionButton', [
|
||||||
'value' => 'Extension',
|
'value' => 'Extension',
|
||||||
'class' => 'buttonTab',
|
'class' => 'buttonTab',
|
||||||
'href' => helper::baseUrl() . 'page/register/extension/' . $this->geturl(2)
|
//'href' => helper::baseUrl() . 'page/register/extension/' . $this->geturl(2)
|
||||||
]); ?>
|
]); ?>
|
||||||
<?php echo template::button('pageEditLayoutButton', [
|
<?php echo template::button('pageEditLayoutButton', [
|
||||||
'value' => 'Mise en page',
|
'value' => 'Mise en page',
|
||||||
'class' => 'buttonTab',
|
'class' => 'buttonTab',
|
||||||
'href' => helper::baseUrl() . 'page/register/layout/' . $this->geturl(2)
|
//'href' => helper::baseUrl() . 'page/register/layout/' . $this->geturl(2)
|
||||||
]); ?>
|
]); ?>
|
||||||
<?php echo template::button('pageEditPermissionButton', [
|
<?php echo template::button('pageEditPermissionButton', [
|
||||||
'value' => 'Permission',
|
'value' => 'Permission',
|
||||||
'class' => 'buttonTab',
|
'class' => 'buttonTab',
|
||||||
'href' => helper::baseUrl() . 'page/register/permission/' . $this->geturl(2)
|
//'href' => helper::baseUrl() . 'page/register/permission/' . $this->geturl(2)
|
||||||
]); ?>
|
]); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Champ caché pour transmettre l'onglet-->
|
||||||
|
<?php echo template::hidden('containerSelected'); ?>
|
||||||
|
|
||||||
<div id="pageEditContentContainer" class="tabContent">
|
<div id="pageEditContentContainer" class="tabContent">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col12">
|
<div class="col12">
|
||||||
|
@ -893,11 +893,11 @@ class theme extends common
|
|||||||
$redirect = '';
|
$redirect = '';
|
||||||
switch ($this->getUrl(2)) {
|
switch ($this->getUrl(2)) {
|
||||||
case 'admin':
|
case 'admin':
|
||||||
$this->initData('admin', self::$i18nUI);
|
unlink(self::DATA_DIR . 'admin.css');
|
||||||
$redirect = helper::baseUrl() . 'theme/admin';
|
$redirect = helper::baseUrl() . 'theme/admin';
|
||||||
break;
|
break;
|
||||||
case 'manage':
|
case 'manage':
|
||||||
$this->initData('theme', self::$i18nUI);
|
$this->initData('theme', self::$siteContent);
|
||||||
$redirect = helper::baseUrl() . 'theme/manage';
|
$redirect = helper::baseUrl() . 'theme/manage';
|
||||||
break;
|
break;
|
||||||
case 'custom':
|
case 'custom':
|
||||||
|
@ -284,7 +284,6 @@ class user extends common
|
|||||||
'notification' => sprintf($count > 1 ? $notification . 's' : $notification, $count),
|
'notification' => sprintf($count > 1 ? $notification . 's' : $notification, $count),
|
||||||
'state' => $success
|
'state' => $success
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Liste des groupes et des profils
|
// Liste des groupes et des profils
|
||||||
@ -367,7 +366,6 @@ class user extends common
|
|||||||
$this->getData(['user', $userId, 'lastname']),
|
$this->getData(['user', $userId, 'lastname']),
|
||||||
$this->getData(['user', $userId, 'tags']),
|
$this->getData(['user', $userId, 'tags']),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,16 +569,18 @@ class user extends common
|
|||||||
public function forgot()
|
public function forgot()
|
||||||
{
|
{
|
||||||
// Soumission du formulaire
|
// Soumission du formulaire
|
||||||
if (
|
if ($this->isPost()) {
|
||||||
$this->isPost()
|
|
||||||
) {
|
|
||||||
$userId = $this->getInput('userForgotId', helper::FILTER_ID, true);
|
$userId = $this->getInput('userForgotId', helper::FILTER_ID, true);
|
||||||
$sent = false;
|
$sent = false;
|
||||||
if ($this->getData(['user', $userId])) {
|
if ($this->getData(['user', $userId])) {
|
||||||
// Enregistre la date de la demande dans le compte utilisateur
|
// Génère une clé unique avec timestamp et partie aléatoire
|
||||||
$this->setData(['user', $userId, 'forgot', time()]);
|
$timestamp = time(); // Timestamp actuel
|
||||||
// Crée un id unique pour la réinitialisation
|
$randomPart = bin2hex(random_bytes(8)); // Partie aléatoire (16 caractères hexadécimaux)
|
||||||
$uniqId = md5(json_encode($this->getData(['user', $userId, 'forgot'])));
|
$uniqId = $timestamp . '_' . $randomPart; // Combine les deux
|
||||||
|
|
||||||
|
// Enregistre la clé unique dans le compte utilisateur
|
||||||
|
$this->setData(['user', $userId, 'forgot', $uniqId]);
|
||||||
|
|
||||||
// Envoi le mail
|
// Envoi le mail
|
||||||
$sent = $this->sendMail(
|
$sent = $this->sendMail(
|
||||||
$this->getData(['user', $userId, 'mail']),
|
$this->getData(['user', $userId, 'mail']),
|
||||||
@ -592,13 +592,13 @@ class user extends common
|
|||||||
null,
|
null,
|
||||||
$this->getData(['config', 'smtp', 'from'])
|
$this->getData(['config', 'smtp', 'from'])
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
// L'utilisateur n'existe pas, on ne le précise pas
|
|
||||||
// Valeurs en sortie
|
// Valeurs en sortie
|
||||||
$this->addOutput([
|
$this->addOutput([
|
||||||
'notification' => helper::translate('Un mail a été envoyé pour confirmer la réinitialisation'),
|
'notification' => $sent === true ? helper::translate('Un mail a été envoyé pour confirmer la réinitialisation') : helper::translate('Le mail de réinitialisation ne peut pas être envoyé, contactez l\'administrateur'),
|
||||||
'state' => ($sent === true ? true : null)
|
'state' => ($sent === true ? true : false),
|
||||||
|
'redirect' => helper::baseUrl()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
// Valeurs en sortie
|
// Valeurs en sortie
|
||||||
@ -679,18 +679,19 @@ class user extends common
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Formatage de la liste
|
// Formatage de la liste
|
||||||
self::$users[] = [
|
self::$users[] = [
|
||||||
//$userId,
|
//$userId,
|
||||||
$this->getData(['user', $userId, 'firstname']) . ' ' . $userLastNames,
|
sprintf('%s %s', $userLastNames, $this->getData(['user', $userId, 'firstname'])),
|
||||||
helper::translate(self::$groups[(int) $this->getData(['user', $userId, 'group'])]),
|
helper::translate(self::$groups[(int) $this->getData(['user', $userId, 'group'])]),
|
||||||
empty($this->getData(['profil', $this->getData(['user', $userId, 'group']), $this->getData(['user', $userId, 'profil']), 'name']))
|
empty($this->getData(['profil', $this->getData(['user', $userId, 'group']), $this->getData(['user', $userId, 'profil']), 'name']))
|
||||||
? helper::translate(self::$groups[(int) $this->getData(['user', $userId, 'group'])])
|
? helper::translate(self::$groups[(int) $this->getData(['user', $userId, 'group'])])
|
||||||
: $this->getData(['profil', $this->getData(['user', $userId, 'group']), $this->getData(['user', $userId, 'profil']), 'name']),
|
: $this->getData(['profil', $this->getData(['user', $userId, 'group']), $this->getData(['user', $userId, 'profil']), 'name']),
|
||||||
$this->getData(['user', $userId, 'tags']),
|
$this->getData(['user', $userId, 'tags']),
|
||||||
helper::dateUTF8('%d/%m/%Y', $this->getData(['user', $userId, 'accessTimer']), self::$i18nUI),
|
is_null($this->getData(['user', $userId, 'accessTimer']))
|
||||||
|
? 'Jamais'
|
||||||
|
: $this->getData(['user', $userId, 'accessTimer']),
|
||||||
|
//helper::dateUTF8('%d/%m/%Y', $this->getData(['user', $userId, 'accessTimer']), self::$i18nUI),
|
||||||
//helper::dateUTF8('%H:%M', $this->getData(['user', $userId, 'accessTimer']), self::$i18nUI),
|
//helper::dateUTF8('%H:%M', $this->getData(['user', $userId, 'accessTimer']), self::$i18nUI),
|
||||||
template::button('userEdit' . $userId, [
|
template::button('userEdit' . $userId, [
|
||||||
'href' => helper::baseUrl() . 'user/edit/' . $userId,
|
'href' => helper::baseUrl() . 'user/edit/' . $userId,
|
||||||
@ -704,7 +705,6 @@ class user extends common
|
|||||||
'help' => 'Supprimer'
|
'help' => 'Supprimer'
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1473,7 +1473,7 @@ class user extends common
|
|||||||
$inputKey = $this->getInput('userAuthKey', helper::FILTER_INT);
|
$inputKey = $this->getInput('userAuthKey', helper::FILTER_INT);
|
||||||
// Redirection
|
// Redirection
|
||||||
$pageId = $this->getUrl(2);
|
$pageId = $this->getUrl(2);
|
||||||
$redirect = $pageId? helper::baseUrl() . $pageId : helper::baseUrl() ;
|
$redirect = $pageId ? helper::baseUrl() . $pageId : helper::baseUrl();
|
||||||
if (
|
if (
|
||||||
// La clé est valide ou le message n'ayant pas été expédié, la double authentification est désactivée
|
// La clé est valide ou le message n'ayant pas été expédié, la double authentification est désactivée
|
||||||
$targetKey === $inputKey || $this->getData(['config', 'connect', 'mailAuth', 0]) === 0
|
$targetKey === $inputKey || $this->getData(['config', 'connect', 'mailAuth', 0]) === 0
|
||||||
@ -1554,25 +1554,30 @@ class user extends common
|
|||||||
if (
|
if (
|
||||||
// L'utilisateur n'existe pas
|
// L'utilisateur n'existe pas
|
||||||
$this->getData(['user', $this->getUrl(2)]) === null
|
$this->getData(['user', $this->getUrl(2)]) === null
|
||||||
// Lien de réinitialisation trop vieux
|
// Lien de réinitialisation trop vieux (24 heures)
|
||||||
or $this->getData(['user', $this->getUrl(2), 'forgot']) + 86400 < time()
|
or $this->getData(['user', $this->getUrl(2), 'forgot']) === null
|
||||||
// Id unique incorrecte
|
or (int) explode('_', $this->getData(['user', $this->getUrl(2), 'forgot']))[0] + 86400 < time()
|
||||||
or $this->getUrl(3) !== md5(json_encode($this->getData(['user', $this->getUrl(2), 'forgot'])))
|
// Clé unique incorrecte
|
||||||
|
or $this->getUrl(3) !== $this->getData(['user', $this->getUrl(2), 'forgot'])
|
||||||
) {
|
) {
|
||||||
$this->saveLog(
|
$this->saveLog(
|
||||||
' Erreur de réinitialisation de mot de passe ' . $this->getUrl(2) .
|
' Erreur de réinitialisation de mot de passe ' . $this->getUrl(2) .
|
||||||
' Compte : ' . $this->getData(['user', $this->getUrl(2)]) .
|
' Compte : ' . $this->getData(['user', $this->getUrl(2)]) .
|
||||||
' Temps : ' . $this->getData(['user', $this->getUrl(2), 'forgot']) + 86400 < time() .
|
' Temps : ' . ($this->getData(['user', $this->getUrl(2), 'forgot']) === null ? 'Clé manquante' : ((int) explode('_', $this->getData(['user', $this->getUrl(2), 'forgot']))[0] + 86400 < time() ? 'Temps dépassé' : 'Temps valide')) .
|
||||||
' Clé : ' . $this->getUrl(3) !== md5(json_encode($this->getData(['user', $this->getUrl(2), 'forgot'])))
|
' Clé : ' . ($this->getUrl(3) !== $this->getData(['user', $this->getUrl(2), 'forgot']) ? 'Clé invalide' : 'Clé valide')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Message d'erreur en cas de problème de réinitialisation de mot de passe
|
// Message d'erreur en cas de problème de réinitialisation de mot de passe
|
||||||
$message = $this->getData(['user', $this->getUrl(2)]) === null
|
$message = $this->getData(['user', $this->getUrl(2)]) === null
|
||||||
? ' Utilisateur inconnu '
|
? ' Utilisateur inconnu '
|
||||||
: '';
|
: '';
|
||||||
$message = $this->getData(['user', $this->getUrl(2), 'forgot']) + 86400 < time()
|
$message = $this->getData(['user', $this->getUrl(2), 'forgot']) === null
|
||||||
|
? ' Clé manquante '
|
||||||
|
: $message;
|
||||||
|
$message = (int) explode('_', $this->getData(['user', $this->getUrl(2), 'forgot']))[0] + 86400 < time()
|
||||||
? ' Temps dépassé '
|
? ' Temps dépassé '
|
||||||
: $message;
|
: $message;
|
||||||
$message = $this->getUrl(3) !== md5(json_encode($this->getData(['user', $this->getUrl(2)])))
|
$message = $this->getUrl(3) !== $this->getData(['user', $this->getUrl(2), 'forgot'])
|
||||||
? ' Clé invalide '
|
? ' Clé invalide '
|
||||||
: $message;
|
: $message;
|
||||||
|
|
||||||
@ -1581,17 +1586,12 @@ class user extends common
|
|||||||
'redirect' => helper::baseurl(),
|
'redirect' => helper::baseurl(),
|
||||||
'notification' => helper::translate('Impossible de réinitialiser le mot de passe de ce compte !') . $message,
|
'notification' => helper::translate('Impossible de réinitialiser le mot de passe de ce compte !') . $message,
|
||||||
'state' => false
|
'state' => false
|
||||||
//'access' => false
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
// Accès autorisé
|
// Accès autorisé
|
||||||
else {
|
else {
|
||||||
// Soumission du formulaire
|
// Soumission du formulaire
|
||||||
if (
|
if ($this->isPost()) {
|
||||||
// Tous les users peuvent réinitialiser
|
|
||||||
// $this->getUser('permission', __CLASS__, __FUNCTION__) === true &&
|
|
||||||
$this->isPost()
|
|
||||||
) {
|
|
||||||
// Double vérification pour le mot de passe
|
// Double vérification pour le mot de passe
|
||||||
if ($this->getInput('userResetNewPassword')) {
|
if ($this->getInput('userResetNewPassword')) {
|
||||||
// La confirmation ne correspond pas au mot de passe
|
// La confirmation ne correspond pas au mot de passe
|
||||||
@ -1762,7 +1762,6 @@ class user extends common
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// Sauvegarde la base manuellement
|
// Sauvegarde la base manuellement
|
||||||
$this->saveDB('user');
|
$this->saveDB('user');
|
||||||
@ -1804,7 +1803,6 @@ class user extends common
|
|||||||
readfile($path . $file);
|
readfile($path . $file);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tag()
|
public function tag()
|
||||||
@ -1847,7 +1845,6 @@ class user extends common
|
|||||||
'notification' => sprintf($count > 1 ? $notification . 's' : $notification, $count),
|
'notification' => sprintf($count > 1 ? $notification . 's' : $notification, $count),
|
||||||
'state' => $success
|
'state' => $success
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1931,7 +1928,6 @@ class user extends common
|
|||||||
$this->getData(['user', $userId, 'lastname']),
|
$this->getData(['user', $userId, 'lastname']),
|
||||||
$this->getData(['user', $userId, 'tags']),
|
$this->getData(['user', $userId, 'tags']),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1952,7 +1948,6 @@ class user extends common
|
|||||||
'datatables'
|
'datatables'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1986,5 +1981,4 @@ class user extends common
|
|||||||
closedir($dh);
|
closedir($dh);
|
||||||
return $subdirs;
|
return $subdirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -16,3 +16,13 @@
|
|||||||
/** NE PAS EFFACER
|
/** NE PAS EFFACER
|
||||||
* admin.css
|
* admin.css
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** Hide the timestamp column in the user list
|
||||||
|
*/
|
||||||
|
tbody td:nth-child(5) {
|
||||||
|
color: transparent; /* Masquer le texte par défaut */
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td.visible-text {
|
||||||
|
color: inherit; /* Rétablir la couleur du texte */
|
||||||
|
}
|
@ -31,6 +31,20 @@ $(document).ready((function () {
|
|||||||
stateSave: true,
|
stateSave: true,
|
||||||
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "Tout"]],
|
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "Tout"]],
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
|
{
|
||||||
|
target: 4,
|
||||||
|
type: 'num', // Utilisez 'num' pour le tri
|
||||||
|
render: function (data) {
|
||||||
|
// Si data est un nombre, formatez-le en date
|
||||||
|
if (typeof data === 'number' || !isNaN(data)) {
|
||||||
|
return moment(Number(data) * 1000).format('DD/MM/YYYY HH:mm');
|
||||||
|
} else {
|
||||||
|
return data; // Sinon, affichez le texte tel quel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderable: false,
|
||||||
|
searchable: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
target: 5,
|
target: 5,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
@ -43,4 +57,15 @@ $(document).ready((function () {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Injecter la règle CSS pour la colonne cible
|
||||||
|
$('<style>')
|
||||||
|
.prop('type', 'text/css')
|
||||||
|
.html(`
|
||||||
|
table.dataTable tbody td:nth-child(5) {
|
||||||
|
color: inherit !important; /* Rétablir la couleur du texte */
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
.appendTo('head');
|
||||||
|
|
||||||
}));
|
}));
|
@ -68,4 +68,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php echo template::formClose(); ?>
|
<?php echo template::formClose(); ?>
|
||||||
<?php echo template::table([3, 2, 2, 2, 2, 1, 1], user::$users, ['Nom', 'Groupe', 'Profil', 'Étiquettes', 'Date dernière vue', '', ''], ['id' => 'dataTables']); ?>
|
<?php echo template::table([3, 2, 2, 1, 3, 1, 1], user::$users, ['Nom', 'Groupe', 'Profil', 'Étiquettes', 'Dernière connexion', '', ''], ['id' => 'dataTables'], ['name','group','profile','tag','data-timestamp','edit','delete']); ?>
|
4
core/vendor/datatables/datatables.min.js
vendored
4
core/vendor/datatables/datatables.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
core/vendor/zwiico/png/error.png
vendored
Normal file
BIN
core/vendor/zwiico/png/error.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
@ -1,3 +1,5 @@
|
|||||||
|
# Version 4.6
|
||||||
|
- Correction de syntaxe.
|
||||||
# Version 4.5
|
# Version 4.5
|
||||||
- Remplacement du nom générique de classe dans les vues.
|
- Remplacement du nom générique de classe dans les vues.
|
||||||
# Version 4.4
|
# Version 4.4
|
||||||
|
@ -1 +1 @@
|
|||||||
{"name":"form","realName":"Formulaire","version":"4.1","update":"0.0","delete":true,"dataDirectory":""}
|
{"name":"form","realName":"Formulaire","version":"4.6","update":"0.0","delete":true,"dataDirectory":""}
|
@ -17,7 +17,7 @@
|
|||||||
class form extends common
|
class form extends common
|
||||||
{
|
{
|
||||||
|
|
||||||
const VERSION = '4.5';
|
const VERSION = '4.6';
|
||||||
const REALNAME = 'Formulaire';
|
const REALNAME = 'Formulaire';
|
||||||
const DATADIRECTORY = ''; // Contenu localisé inclus par défaut (page.json et module.json)
|
const DATADIRECTORY = ''; // Contenu localisé inclus par défaut (page.json et module.json)
|
||||||
|
|
||||||
@ -479,7 +479,7 @@ class form extends common
|
|||||||
if (!empty($singlemail)) {
|
if (!empty($singlemail)) {
|
||||||
$to[] = $singlemail;
|
$to[] = $singlemail;
|
||||||
}
|
}
|
||||||
if ($to) {
|
if (empty($to)=== false) {
|
||||||
// Sujet du mail
|
// Sujet du mail
|
||||||
$subject = $this->getData(['module', $this->getUrl(0), 'config', 'subject']);
|
$subject = $this->getData(['module', $this->getUrl(0), 'config', 'subject']);
|
||||||
if ($subject === '') {
|
if ($subject === '') {
|
||||||
@ -495,6 +495,7 @@ class form extends common
|
|||||||
$this->getData(['config', 'smtp', 'from'])
|
$this->getData(['config', 'smtp', 'from'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// Redirection
|
// Redirection
|
||||||
$redirect = $this->getData(['module', $this->getUrl(0), 'config', 'pageId']);
|
$redirect = $this->getData(['module', $this->getUrl(0), 'config', 'pageId']);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user