diff --git a/core/class/jsondb/JsonDb.class.php b/core/class/jsondb/JsonDb.class.php index 11223ac..694eb7b 100644 --- a/core/class/jsondb/JsonDb.class.php +++ b/core/class/jsondb/JsonDb.class.php @@ -142,25 +142,38 @@ class JsonDb extends \Prowebcraft\Dot */ public function save() { - $v = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT); - // $v = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT); - $l = strlen($v); - $t = 0; - if ($v === false) { - error_log('Erreur d\'encodage JSON : ' . json_last_error_msg()); - exit ('Erreur d\'encodage JSON : ' . json_last_error_msg()); - } - while ($t < 5) { - $w = file_put_contents($this->db, $v); // Multi user get a locker - if ($w == $l) { + // Encode les données au format JSON avec les options spécifiées + $encoded_data = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT); + + // Vérifie la longueur de la chaîne JSON encodée + $encoded_length = strlen($encoded_data); + + // Initialise le compteur de tentatives + $attempt = 0; + + // Tente d'encoder les données en JSON et de les sauvegarder jusqu'à 5 fois en cas d'échec + while ($attempt < 5) { + // 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 + + // Vérifie si l'écriture a réussi + if ($write_result === $encoded_length) { + // Sort de la boucle si l'écriture a réussi break; } - $t++; - } - if ($w !== $l) { - error_log('Erreur d\'écriture, les données n\'ont pas été sauvegardées.'); - exit('Erreur d\'écriture, les données n\'ont pas été sauvegardées.'); + + // Incrémente le compteur de tentatives + $attempt++; } + // Vérifie si l'écriture a échoué même après plusieurs tentatives + 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.'); + } } + } \ No newline at end of file diff --git a/core/core.php b/core/core.php index 41aff3a..950f179 100644 --- a/core/core.php +++ b/core/core.php @@ -447,7 +447,7 @@ class common } // Mise à jour des données core - include('core/include/update.inc.php'); + include ('core/include/update.inc.php'); } @@ -595,7 +595,7 @@ class common public function setPage($page, $value, $path) { - return file_put_contents(self::DATA_DIR . $path . '/content/' . $page . '.html', $value); + return $this->secure_file_put_contents(self::DATA_DIR . $path . '/content/' . $page . '.html', $value); } @@ -611,6 +611,49 @@ class common } + /** + * Écrit les données dans un fichier avec plusieurs tentatives d'écriture et verrouillage + * + * @param string $filename Le nom du fichier + * @param string $data Les données à écrire dans le fichier + * @param int $flags Les drapeaux optionnels à passer à la fonction $this->secure_file_put_contents + * @return bool True si l'écriture a réussi, sinon false + */ + function secure_file_put_contents($filename, $data, $flags = 0) + { + // Vérifie si le fichier existe + if (!file_exists($filename)) { + // Crée le fichier s'il n'existe pas + $handle = fopen($filename, 'w'); + fclose($handle); + } + + // Initialise le compteur de tentatives + $attempts = 0; + + // Vérifie la longueur des données + $data_length = strlen($data); + + // Effectue jusqu'à 5 tentatives d'écriture + while ($attempts < 5) { + // Essaye d'écrire les données dans le fichier avec verrouillage exclusif + $write_result = $this->secure_file_put_contents($filename, $data, LOCK_EX | $flags); + + // Vérifie si l'écriture a réussi + if ($write_result !== false && $write_result === $data_length) { + // Sort de la boucle si l'écriture a réussi + return true; + } + + // Incrémente le compteur de tentatives + $attempts++; + } + + // Échec de l'écriture après plusieurs tentatives + return false; + } + + public function initDB($module, $path = '') { // Instanciation de la classe des entrées / sorties @@ -634,7 +677,7 @@ class common { // Tableau avec les données vierges - require_once('core/module/install/ressource/defaultdata.php'); + require_once ('core/module/install/ressource/defaultdata.php'); // L'arborescence if (!file_exists(self::DATA_DIR . $path)) { @@ -669,7 +712,7 @@ class common public function saveConfig($module) { // Tableau avec les données vierges - require_once('core/module/install/ressource/defaultdata.php'); + require_once ('core/module/install/ressource/defaultdata.php'); // Installation des données des autres modules cad theme profil font config, admin et core $this->setData([$module, init::$defaultData[$module]]); common::$coreNotices[] = $module; @@ -705,65 +748,65 @@ class common * Fonction pour construire le tableau des pages */ - private function buildHierarchy() - { - - $pages = helper::arrayColumn($this->getData(['page']), 'position', 'SORT_ASC'); - // Parents - foreach ($pages as $pageId => $pagePosition) { - if ( - // Page parent - $this->getData(['page', $pageId, 'parentPageId']) === "" - // Ignore les pages dont l'utilisateur n'a pas accès - and ($this->getData(['page', $pageId, 'group']) === self::GROUP_VISITOR - or ($this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD') - //and $this->getUser('group') >= $this->getData(['page', $pageId, 'group']) - // Modification qui tient compte du profil de la page - and ($this->getUser('group') * self::MAX_PROFILS + $this->getUser('profil')) >= ($this->getData(['page', $pageId, 'group']) * self::MAX_PROFILS + $this->getData(['page', $pageId, 'profil'])) - - ) - ) - ) { - if ($pagePosition !== 0) { - $this->hierarchy['visible'][$pageId] = []; - } - if ($this->getData(['page', $pageId, 'block']) === 'bar') { - $this->hierarchy['bar'][$pageId] = []; - } - $this->hierarchy['all'][$pageId] = []; - } - } - // Enfants - foreach ($pages as $pageId => $pagePosition) { - - if ( - // Page parent - $parentId = $this->getData(['page', $pageId, 'parentPageId']) - // Ignore les pages dont l'utilisateur n'a pas accès - and ( - ( - $this->getData(['page', $pageId, 'group']) === self::GROUP_VISITOR - and - $this->getData(['page', $parentId, 'group']) === self::GROUP_VISITOR - ) - or ( - $this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD') - and - $this->getUser('group') * self::MAX_PROFILS + $this->getUser('profil')) >= ($this->getData(['page', $pageId, 'group']) * self::MAX_PROFILS + $this->getData(['page', $pageId, 'profil']) - - ) - ) - ) { - if ($pagePosition !== 0) { - $this->hierarchy['visible'][$parentId][] = $pageId; - } - if ($this->getData(['page', $pageId, 'block']) === 'bar') { - $this->hierarchy['bar'][$pageId] = []; - } - $this->hierarchy['all'][$parentId][] = $pageId; - } - } - } + private function buildHierarchy() + { + + $pages = helper::arrayColumn($this->getData(['page']), 'position', 'SORT_ASC'); + // Parents + foreach ($pages as $pageId => $pagePosition) { + if ( + // Page parent + $this->getData(['page', $pageId, 'parentPageId']) === "" + // Ignore les pages dont l'utilisateur n'a pas accès + and ($this->getData(['page', $pageId, 'group']) === self::GROUP_VISITOR + or ($this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD') + //and $this->getUser('group') >= $this->getData(['page', $pageId, 'group']) + // Modification qui tient compte du profil de la page + and ($this->getUser('group') * self::MAX_PROFILS + $this->getUser('profil')) >= ($this->getData(['page', $pageId, 'group']) * self::MAX_PROFILS + $this->getData(['page', $pageId, 'profil'])) + + ) + ) + ) { + if ($pagePosition !== 0) { + $this->hierarchy['visible'][$pageId] = []; + } + if ($this->getData(['page', $pageId, 'block']) === 'bar') { + $this->hierarchy['bar'][$pageId] = []; + } + $this->hierarchy['all'][$pageId] = []; + } + } + // Enfants + foreach ($pages as $pageId => $pagePosition) { + + if ( + // Page parent + $parentId = $this->getData(['page', $pageId, 'parentPageId']) + // Ignore les pages dont l'utilisateur n'a pas accès + and ( + ( + $this->getData(['page', $pageId, 'group']) === self::GROUP_VISITOR + and + $this->getData(['page', $parentId, 'group']) === self::GROUP_VISITOR + ) + or ( + $this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD') + and + $this->getUser('group') * self::MAX_PROFILS + $this->getUser('profil')) >= ($this->getData(['page', $pageId, 'group']) * self::MAX_PROFILS + $this->getData(['page', $pageId, 'profil']) + + ) + ) + ) { + if ($pagePosition !== 0) { + $this->hierarchy['visible'][$parentId][] = $pageId; + } + if ($this->getData(['page', $pageId, 'block']) === 'bar') { + $this->hierarchy['bar'][$pageId] = []; + } + $this->hierarchy['all'][$parentId][] = $pageId; + } + } + } /** * Génère un fichier json avec la liste des pages @@ -1435,8 +1478,8 @@ class common foreach ($courses as $courseId => $value) { // Affiche les espaces gérés par l'éditeur, les espaces où il participe et les espaces anonymes if ( - // le membre est inscrit - ( $this->getData(['enrolment', $courseId]) && array_key_exists($this->getUser('id'), $this->getData(['enrolment', $courseId])) ) + // le membre est inscrit + ($this->getData(['enrolment', $courseId]) && array_key_exists($this->getUser('id'), $this->getData(['enrolment', $courseId]))) // Il est l'auteur || $this->getUser('id') === $this->getData(['course', $courseId, 'author']) // Le cours est ouvert @@ -1450,7 +1493,7 @@ class common foreach ($courses as $courseId => $value) { // Affiche les espaces du participant et les espaces anonymes if ( - ($this->getData(['enrolment', $courseId]) && array_key_exists($this->getUser('id'), $this->getData(['enrolment', $courseId])) ) + ($this->getData(['enrolment', $courseId]) && array_key_exists($this->getUser('id'), $this->getData(['enrolment', $courseId]))) || $this->getData(['course', $courseId, 'enrolment']) === self::COURSE_ENROLMENT_GUEST ) { $filter[$courseId] = $courses[$courseId]; diff --git a/core/module/course/course.php b/core/module/course/course.php index cc4d2bd..43522fc 100644 --- a/core/module/course/course.php +++ b/core/module/course/course.php @@ -1597,17 +1597,17 @@ class course extends common // Participants avec historiques $enrolment = $this->getData(['enrolment', $courseId]); // Générer un fichier dans le dossier de l'espace - file_put_contents(self::DATA_DIR . $courseId . '/enrolment.json', json_encode([$courseId => $enrolment], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + $this->secure_file_put_contents(self::DATA_DIR . $courseId . '/enrolment.json', json_encode([$courseId => $enrolment], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); // Idem pour les données du cours $course = $this->getData(['course', $courseId]); // Générer un fichier dans le dossier de l'espace - file_put_contents(self::DATA_DIR . $courseId . '/course.json', json_encode([$courseId => $course], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + $this->secure_file_put_contents(self::DATA_DIR . $courseId . '/course.json', json_encode([$courseId => $course], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); // Idem pour la catégorie $category = $this->getData(['category', $this->getData(['course', $courseId, 'category'])]); // Générer un fichier dans le dossier de l'espace - file_put_contents(self::DATA_DIR . $courseId . '/category.json', json_encode([$this->getData(['course', $courseId, 'category']) => $category], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + $this->secure_file_put_contents(self::DATA_DIR . $courseId . '/category.json', json_encode([$this->getData(['course', $courseId, 'category']) => $category], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); // Génère une archive ZIP diff --git a/core/module/install/install.php b/core/module/install/install.php index 23e455a..5522f12 100644 --- a/core/module/install/install.php +++ b/core/module/install/install.php @@ -387,7 +387,7 @@ class install extends common '' . PHP_EOL . '# URL rewriting' . PHP_EOL; $fileContent = str_replace('# URL rewriting', $rewriteData, $fileContent); - $success = file_put_contents( + $success = $this->secure_file_put_contents( '.htaccess', $fileContent ); diff --git a/core/module/language/language.php b/core/module/language/language.php index b6b3d9f..4ae152f 100644 --- a/core/module/language/language.php +++ b/core/module/language/language.php @@ -99,7 +99,7 @@ class language extends common is_array($descripteur['language'][$lang]) ) { if ($this->setData(['language', $lang, $descripteur['language'][$lang]])) { - $success = file_put_contents(self::I18N_DIR . $lang . '.json', json_encode($languageData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + $success = $this->secure_file_put_contents(self::I18N_DIR . $lang . '.json', json_encode($languageData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); $success = is_int($success) ? true : false; } }