diff --git a/CHANGES.md b/CHANGES.md index c0cef218..b429ac70 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,21 @@ # Changelog +## version 10.4.00 +- Modifications : + - Captcha arithmétique, activation recommandée dans la configuration. + - Module User + - Pour les articles de blog et de news, choix de la signature, nom+prenom ; nom+prenom ; id ; pseudo + - Importation d'un liste d'utilisateur dans un fichier plat (CSV). + - Module Blog : + - Texte du commentaire enrichi. + - Nombre maximal de caractère par commentaire. + - Gestion des commentaires article par article. + - Suppression des commentaires en masse. + - Limiter l'édition des articles et des commentaires à l'id de l'éditeur + - Approbation des commentaires + - Gestion des thèmes : + - Bouton de réinitialisation avec confirmation + ## version 10.3.06 - Correction : - Edition de page avec module, le changement de mise en page désactive le bouton d'option du module. @@ -40,11 +56,13 @@ - Barre de membre déplacée à droite de la barre de menu. ## version 10.3.02 -- Correction : +- Corrections : - Icône de pied de page github manquante. + - Mauvaise redirection après changement de mot de passe d'un membre. - Modifications : - Nouvelles images de captcha. - Option de configuration, captcha demandé à la connexion. + - Méthode d'encodage UTF8. ## version 10.3.01 diff --git a/README.md b/README.md index 673ce1f2..fa82bbbf 100755 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ ![](https://img.shields.io/github/last-commit/fredtempez/ZwiiCMS/master) ![](https://img.shields.io/github/release-date/fredtempez/ZwiiCMS) -# ZwiiCMS 10.3.05 +# ZwiiCMS 10.4.00 Zwii est un CMS sans base de données (flat-file) qui permet de créer et gérer facilement un site web sans aucune connaissance en programmation. -ZwiiCMS a été créé par un développeur de talent, [Rémi Jean](https://remijean.fr/). Il est désormais maintenu par Fred Tempez aidé de la communauté. +ZwiiCMS a été créé par un développeur de talent, [Rémi Jean](https://remijean.fr/). Il est désormais maintenu par Frédéric Tempez. [Site](http://zwiicms.fr/) - [Forum](http://forum.zwiicms.com/) - [Version initiale](https://github.com/remijean/ZwiiCMS/) - [GitHub](https://github.com/fredtempez/ZwiiCMS) diff --git a/core/class/template.class.php b/core/class/template.class.php index d86fa96e..02795ca2 100644 --- a/core/class/template.class.php +++ b/core/class/template.class.php @@ -32,7 +32,7 @@ class template { ); } - /** + /** * Crée un champ captcha * @param string $nameId Nom et id du champ * @param array $attributes Attributs ($key => $value) @@ -47,29 +47,85 @@ class template { 'id' => $nameId, 'name' => $nameId, 'value' => '', - 'limit' => false + 'limit' => false // captcha simple ], $attributes); - // Génère deux nombres pour le captcha - $numbers = array(0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20); - $letters = array('u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a'); - $limit = $attributes['limit'] ? count($letters)-1 : 10 ; - mt_srand((float) microtime()*1000000); - $firstNumber = mt_rand ( 0 , $limit ); - $secondNumber = mt_rand ( 0 , $limit ); - $result = $firstNumber + $secondNumber; + + // Captcha quatre opérations + // Limite addition et soustraction selon le type de captcha + $numbers = [0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20]; + $letters = ['u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a']; + $limit = $attributes['limit'] ? count($letters)-1 : 10; + + // Tirage de l'opération + mt_srand((float) microtime()*1000000); + // Captcha simple limité à l'addition + $operator = $attributes['limit'] ? mt_rand (1, 4) : 1; + + // Limite si multiplication ou division + if ($operator > 2) { + $limit = 10; + } + + // Tirage des nombres + mt_srand((float) microtime()*1000000); + $firstNumber = mt_rand (1, $limit); + mt_srand((float) microtime()*1000000); + $secondNumber = mt_rand (1, $limit); + + // Permutation si addition ou soustraction + if (($operator < 3) and ($firstNumber < $secondNumber)) { + $temp = $firstNumber; + $firstNumber = $secondNumber; + $secondNumber = $temp; + } + + // Icône de l'opérateur et calcul du résultat + switch ($operator) { + case 1: + $operator = template::ico('plus'); + $result = $firstNumber + $secondNumber; + break; + case 2: + $operator = template::ico('minus'); + $result = $firstNumber - $secondNumber; + break; + case 3: + $operator = template::ico('cancel'); + $result = $firstNumber * $secondNumber; + break; + case 4: + $operator = template::ico('divide'); + $limit2 = [10, 10, 6, 5, 4, 3, 2, 2, 2, 2]; + for ($i = 1; $i <= $firstNumber; $i++) { + $limit = $limit2[$i-1]; + } + mt_srand((float) microtime()*1000000); + $secondNumber = mt_rand(1, $limit); + $firstNumber = $firstNumber * $secondNumber; + $result = $firstNumber / $secondNumber; + break; + } + + // Hashage du résultat $result = password_hash($result, PASSWORD_BCRYPT); + + // Codage des valeurs de l'opération $firstLetter = uniqid(); $secondLetter = uniqid(); - // Masquage image source + + // Masquage image source pour éviter un décodage copy ('core/vendor/zwiico/png/'.$letters[$firstNumber] . '.png', 'site/tmp/' . $firstLetter . '.png'); copy ('core/vendor/zwiico/png/'.$letters[$secondNumber] . '.png', 'site/tmp/' . $secondLetter . '.png'); + // Début du wrapper $html = '
'; + // Label $html .= self::label($attributes['id'], - '' . template::ico('plus') . ' en chiffres ?', [ + ' ' . $operator . '  en chiffres ?', [ 'help' => $attributes['help'] ]); + // Notice $notice = ''; if(array_key_exists($attributes['id'], common::$inputNotices)) { @@ -77,29 +133,22 @@ class template { $attributes['class'] .= ' notice'; } $html .= self::notice($attributes['id'], $notice); + // captcha $html .= sprintf( '', helper::sprintAttributes($attributes) ); - // Champ résultat caché + + // Champ résultat codé $html .= self::hidden($attributes['id'] . 'Result', [ 'value' => $result, 'before' => false ]); - // Champs cachés contenant les nombres - /* - $html .= self::hidden($attributes['id'] . 'FirstNumber', [ - 'value' => $firstNumber, - 'before' => false - ]); - $html .= self::hidden($attributes['id'] . 'SecondNumber', [ - 'value' => $secondNumber, - 'before' => false - ]); - */ + // Fin du wrapper $html .= '
'; + // Retourne le html return $html; } diff --git a/core/core.php b/core/core.php index db9224a4..77b4225d 100644 --- a/core/core.php +++ b/core/core.php @@ -25,6 +25,10 @@ class common { const GROUP_MEMBER = 1; const GROUP_MODERATOR = 2; const GROUP_ADMIN = 3; + const SIGNATURE_ID = 1; + const SIGNATURE_PSEUDO = 2; + const SIGNATURE_FIRSTLASTNAME = 3; + const SIGNATURE_LASTFIRSTNAME = 4; // Dossier de travail const BACKUP_DIR = 'site/backup/'; const DATA_DIR = 'site/data/'; @@ -39,7 +43,7 @@ class common { const ACCESS_TIMER = 1800; // Numéro de version - const ZWII_VERSION = '10.3.06'; + const ZWII_VERSION = '10.4.00'; const ZWII_UPDATE_CHANNEL = "v10"; public static $actions = []; @@ -53,15 +57,6 @@ class common { 'user', 'translate' ]; - public static $dataStage = [ - 'config', - 'core', - 'module', - 'page', - 'user', - 'theme', - 'admin' - ]; public static $accessList = [ 'user', 'theme', @@ -148,9 +143,24 @@ class common { private $url = ''; // Données de site private $user = []; + private $core = []; + private $config = []; private $page = []; private $module = []; + // Descripteur de données Entrées / Sorties + // Liste ici tous les fichiers de données + private $dataFiles = [ + 'page' => '', + 'module' => '', + 'core' => '', + 'config' => '', + 'page' => '', + 'user' => '', + 'theme' => '', + 'admin' => '', + 'blacklist' => '' + ]; /** * Constructeur commun @@ -164,20 +174,32 @@ class common { $this->input['_COOKIE'] = $_COOKIE; } + // Instanciation de la classe des entrées / sorties + // Récupére les descripteurs + foreach ($this->dataFiles as $keys => $value) { + // Constructeur JsonDB + $this->dataFiles[$keys] = new \Prowebcraft\JsonDb([ + 'name' => $keys . '.json', + 'dir' => $this->dirData ($keys,'fr') + ]);; + } + + // Import version 9 if (file_exists(self::DATA_DIR . 'core.json') === true && - $this->getData(['core','dataVersion']) < 10000) { - $keepUsers = isset($_SESSION['KEEP_USERS']) ? $_SESSION['KEEP_USERS'] : false; - $this->importData($keepUsers); - unset ($_SESSION['KEEP_USERS']); - // Réinstaller htaccess - copy('core/module/install/ressource/.htaccess', self::DATA_DIR . '.htaccess'); - common::$importNotices [] = "Importation réalisée avec succès" ; - //echo ''; + $this->getData(['core','dataVersion']) < 10000) { + $keepUsers = isset($_SESSION['KEEP_USERS']) ? $_SESSION['KEEP_USERS'] : false; + $this->importData($keepUsers); + unset ($_SESSION['KEEP_USERS']); + // Réinstaller htaccess + copy('core/module/install/ressource/.htaccess', self::DATA_DIR . '.htaccess'); + common::$importNotices [] = "Importation réalisée avec succès" ; + //echo ''; } + // Installation fraîche, initialisation des modules manquants // La langue d'installation par défaut est fr - foreach (self::$dataStage as $stageId) { + foreach ($this->dataFiles as $stageId => $item) { $folder = $this->dirData ($stageId, 'fr'); if (file_exists($folder . $stageId .'.json') === false) { $this->initData($stageId,'fr'); @@ -194,6 +216,7 @@ class common { $this->page = $this->getCache('page'); $this->module = $this->getCache('module'); + // Auto traduction if ( $this->getData(['translate','active'])) { // Lire la langue du navigateur $lan = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); @@ -334,13 +357,9 @@ class common { * @param array $keys Clé(s) des données */ public function deleteData($keys) { - //Retourne une chaine contenant le dossier à créer - $folder = $this->dirData ($keys[0],'fr'); - // Constructeur JsonDB - $db = new \Prowebcraft\JsonDb([ - 'name' => $keys[0] . '.json', - 'dir' => $folder - ]); + // Descripteur + $db = $this->dataFiles[$keys[0]]; + // Aiguillage switch(count($keys)) { case 1: $db->delete($keys[0]); @@ -381,38 +400,10 @@ class common { public function getData($keys = []) { if (count($keys) >= 1) { - - /** - * Lecture dans le cache, page et module - */ - if ($keys[0] === 'page' || - $keys[0] === 'module' ) { - // Décent dans les niveaux de la variable $data - $data = array_merge ($this->page , $this->module); - foreach($keys as $key) { - // Si aucune donnée n'existe retourne null - if(isset($data[$key]) === false) { - return null; - } - // Sinon décent dans les niveaux - else { - $data = $data[$key]; - } - } - // Retourne les données - return $data; - } - /** * Lecture directe */ - //Retourne une chaine contenant le dossier à créer - $folder = $this->dirData ($keys[0],'fr'); - // Constructeur JsonDB - $db = new \Prowebcraft\JsonDb([ - 'name' => $keys[0] . '.json', - 'dir' => $folder - ]); + $db = $this->dataFiles[$keys[0]]; switch(count($keys)) { case 1: $tempData = $db->get($keys[0]); @@ -440,22 +431,6 @@ class common { } } - - /** - * Lecture des fichiers de données de page et mise ne cache - * @param @return string données des pages - */ - public function getCache($data) { - $folder = $this->dirData ($data,'fr'); - // Constructeur JsonDB - $db = new \Prowebcraft\JsonDb([ - 'name' => $data . '.json', - 'dir' => $folder - ]); - $tempData = $db->get($data); - return [$data => $tempData]; - } - /* * Dummy function * Compatibilité des modules avec v8 et v9 @@ -970,14 +945,10 @@ class common { return false; } - //Retourne une chaine contenant le dossier à créer - $folder = $this->dirData ($keys[0],'fr'); - // Constructeur JsonDB - $db = new \Prowebcraft\JsonDb([ - 'name' => $keys[0] . '.json', - 'dir' => $folder - ]); + // Descripteur + $db = $this->dataFiles[$keys[0]]; + // Aiguillage switch(count($keys)) { case 2: $db->set($keys[0],$keys[1]); @@ -1022,12 +993,7 @@ class common { if (!file_exists(self::DATA_DIR . '/' . $lang)) { mkdir (self::DATA_DIR . '/' . $lang); } - $folder = $this->dirData ($module,$lang); - // Constructeur JsonDB - $db = new \Prowebcraft\JsonDb([ - 'name' => $module . '.json', - 'dir' => $folder - ]); + $db = $this->dataFiles[$module]; if ($sampleSite === true) { $db->set($module,init::$siteData[$module]); } else { @@ -1463,6 +1429,51 @@ class common { } $this->setData(['core', 'dataVersion', 10304]); } + // Version 10.4.00 + if ($this->getData(['core', 'dataVersion']) < 10400) { + // Ajouter le prénom comme pseudo et le pseudo comme signature + foreach($this->getData(['user']) as $userId => $userIds){ + $this->setData(['user',$userId,'pseudo',$this->getData(['user',$userId,'firstname'])]); + $this->setData(['user',$userId,'signature',2]); + } + + // Ajouter les champs de blog v3 + // Liste des pages dans pageList + $pageList = array(); + foreach ($this->getHierarchy(null,null,null) as $parentKey=>$parentValue) { + $pageList [] = $parentKey; + foreach ($parentValue as $childKey) { + $pageList [] = $childKey; + } + } + // Parcourir pageList et rechercher les modules de blog + foreach ($pageList as $parentKey => $parent) { + //La page a une galerie + if ($this->getData(['page',$parent,'moduleId']) === 'blog' ) { + $articleIds = array_keys(helper::arrayCollumn($this->getData(['module',$parent]), 'publishedOn', 'SORT_DESC')); + foreach ($articleIds as $key => $article) { + // Droits les deux groupes + $this->setData(['module', $parent, $article,'editConsent', 3]); + // Limite de taille 500 + $this->setData(['module', $parent, $article,'commentMaxlength', '500']); + // Pas d'approbation des commentaires + $this->setData(['module', $parent, $article,'commentApproved', false ]); + // pas de notification + $this->setData(['module', $parent, $article,'commentNotification', false ]); + // groupe de notification + $this->setData(['module', $parent, $article,'commentGroupNotification', 3 ]); + } + // Traitement des commentaires + if ( is_array($this->getData(['module', $parent, $article,'comment'])) ) { + foreach($this->getData(['module', $parent, $article,'comment']) as $commentId => $comment) { + // Approbation + $this->setData(['module', $parent, $article,'comment', $commentId, 'approval', true ]); + } + } + } + } + $this->setData(['core', 'dataVersion', 10400]); + } } } @@ -1566,7 +1577,7 @@ class core extends common { //$css .= '.button.buttonGrey,.button.buttonGrey:hover{color:' . $this->getData(['theme', 'text', 'textColor']) . '}'; $css .= '.container{max-width:' . $this->getData(['theme', 'site', 'width']) . '}'; $margin = $this->getData(['theme', 'site', 'margin']) ? '0' : '20px'; - $css .= $this->getData(['theme', 'site', 'width']) === '100%' ? '#site.light{margin:150px auto !important;}#site{margin:0 auto !important;} body{margin:0 auto !important;} #bar{margin:0 auto !important;} body > header{margin:0 auto !important;} body > nav {margin: 0 auto !important;} body > footer {margin:0 auto !important;}': "#site.light{margin: 100px auto !important;}#site{margin: " . $margin . " auto !important;} body{margin:0px 10px;} #bar{margin: 0 -10px;} body > header{margin: 0 -10px;} body > nav {margin: 0 -10px;} body > footer {margin: 0 -10px;} "; + $css .= $this->getData(['theme', 'site', 'width']) === '100%' ? '#site.light{margin:5% auto !important;}#site{margin:0 auto !important;} body{margin:0 auto !important;} #bar{margin:0 auto !important;} body > header{margin:0 auto !important;} body > nav {margin: 0 auto !important;} body > footer {margin:0 auto !important;}': "#site.light{margin: 5% auto !important;}#site{margin: " . $margin . " auto !important;} body{margin:0px 10px;} #bar{margin: 0 -10px;} body > header{margin: 0 -10px;} body > nav {margin: 0 -10px;} body > footer {margin: 0 -10px;} "; $css .= $this->getData(['theme', 'site', 'width']) === '750px' ? '.button, button{font-size:0.8em;}' : ''; $css .= '#site{background-color:' . $this->getData(['theme', 'site', 'backgroundColor']) . ';border-radius:' . $this->getData(['theme', 'site', 'radius']) . ';box-shadow:' . $this->getData(['theme', 'site', 'shadow']) . ' #212223;}'; $css .= '.editorWysiwyg {background-color:' . $this->getData(['theme', 'site', 'backgroundColor']) . ';}'; @@ -1700,7 +1711,7 @@ class core extends common { $colors = helper::colorVariants($this->getData(['admin','backgroundColorButtonRed'])); $css .= '.button.buttonRed {background-color: ' . $colors['normal'] . ';color: ' . $colors['text'] . ';}.button.buttonRed:hover {background-color:' . $colors['darken'] . ';color:' . $colors['text'] . ';}.button.buttonRed:active {background-color:' . $colors['veryDarken'] . ';color:' . $colors['text'] . ';}'; $colors = helper::colorVariants($this->getData(['admin','backgroundColorButtonGreen'])); - $css .= 'button[type=submit] {background-color: ' . $colors['normal'] . ';color: ' . $colors['text'] . ';}button[type=submit]:hover {background-color: ' . $colors['darken'] . ';color: ' . $colors['text'] .';}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'])); $css .= '.block {border: 1px solid ' . $this->getData(['admin','borderBlockColor']) . ';}.block h4 {background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';}'; $css .= 'table tr,input[type=email],input[type=text],input[type=password],select:not(#barSelectPage),textarea:not(.editorWysiwyg),.inputFile{background-color: ' . $colors['normal'] . ';color:' . $colors['text'] . ';border: 1px solid ' . $this->getData(['admin','borderBlockColor']) . ';}'; @@ -2868,4 +2879,4 @@ class layout extends common { } } } -} \ No newline at end of file +} diff --git a/core/layout/common.css b/core/layout/common.css index 80a09f31..60f31f09 100755 --- a/core/layout/common.css +++ b/core/layout/common.css @@ -13,7 +13,7 @@ /** * Police des icônes */ - @import url(https://use.fontawesome.com/releases/v5.7.2/css/all.css); +@import url(https://use.fontawesome.com/releases/v5.7.2/css/all.css); @@ -28,7 +28,7 @@ body { } -@media (min-width: 768px) { +@media (min-width: 769px) { body { /*margin:0px 10px;*/ margin: 0; @@ -42,17 +42,21 @@ body { */ @media (max-width: 768px) { body { - margin:0px; + margin: 0px; } + #site { - margin:0px auto; + margin: 0px auto; } - body > header { - margin:0px 0px; + + body>header { + margin: 0px 0px; } + .tippy-tooltip { - font-size:.8rem !important; + font-size: .8rem !important; } + section { padding: 5px; } @@ -65,13 +69,16 @@ body { display: flex; flex-direction: column; } - .siteContainer > #contentLeft { + + .siteContainer>#contentLeft { order: 1; } - .siteContainer > #contentRight { + + .siteContainer>#contentRight { order: 3; } - .siteContainer > #contentSite { + + .siteContainer>#contentSite { order: 2; } } @@ -86,6 +93,7 @@ body { a { text-decoration: none; } + a:hover { text-decoration: underline; } @@ -94,6 +102,7 @@ a:hover { a:focus { outline: none; } + a:active { outline: none; } @@ -102,12 +111,15 @@ a:active { h1 { font-size: 1.8em; } + h2 { font-size: 1.6em; } + h3 { font-size: 1.4em; } + h4 { font-size: 1.0em; } @@ -116,6 +128,7 @@ h4 { ul { list-style: square; } + li ul { margin: 0; } @@ -147,9 +160,11 @@ img { max-width: 100%; height: auto; } + img[align='left'] { margin-right: 10px; } + img[align='right'] { margin-left: 10px; } @@ -180,61 +195,81 @@ Signature dans les articles blog et news .newsContent { clear: left; - margin-left: 10px; - /* padding: 5px;*/ + margin-left: 10px; + /* padding: 5px;*/ } + .newsSignature { - margin-right:10px; - float:right; - font-size:0.8em; + margin-right: 10px; + float: right; + font-size: 0.8em; font-style: italic; color: grey; } /* Tableau */ -:not([class^="col"]) > .tableWrapper { +:not([class^="col"])>.tableWrapper { margin: 10px 0; } + .table { width: 100%; border-spacing: 0; border-collapse: collapse; } + .table thead tr { background: #212223; color: #FFF; text-align: left; } + .table tbody tr { background: #F6F7F8; transition: background .3s ease-out; } + .table tbody tr:nth-child(2n + 2) { background: #ECEFF1; } + .table tbody tr:hover { background: #FCF2E8; } + .table th { font-weight: normal; padding: 15px 10px; } + /* Supprime le padding des cols dans les cellules des tableaux */ -td > .col1, -td > .col2, -td > .col3, -td > .col4, -td > .col5, -td > .col6, -td > .col7, -td > .col8, -td > .col9, -td > .col10, -td > .col11, -td > .col12 { +td>.col1, +td>.col2, +td>.col3, +td>.col4, +td>.col5, +td>.col6, +td>.col7, +td>.col8, +td>.col9, +td>.col10, +td>.col11, +td>.col12 { padding: 0 !important; } +/* Tableau sur les écrans de petites tailles */ +@media (max-width: 768px) { + .table thead { + font-size: 0.8em; + } +} +@media (max-width: 668px) { + .table thead { + display:none; + } +} + /* Notifications */ #notification { padding: 14px; @@ -250,20 +285,25 @@ td > .col12 { top: 30px; border-radius: 2px; } + #notification.notificationSuccess { background: #27AE60; } + #notification.notificationError { background: #E74C3C; } + #notification.notificationOther { background: #F39C12; } + #notificationClose { cursor: pointer; float: right; opacity: .8; } + #notificationProgress { position: absolute; left: 0; @@ -272,31 +312,40 @@ td > .col12 { height: 6px; border-radius: 2px; } + #notification.notificationSuccess #notificationProgress { background: #219251; } + #notification.notificationError #notificationProgress { background: #D62C1A; } + #notification.notificationOther #notificationProgress { background: #D8890B; } + #notificationClose:hover { opacity: 1; } + @keyframes notificationBounce { 0% { transform: translateX(-50%) translateY(-30px); } + 40% { transform: translateX(-50%) translateY(10px); } + 60% { transform: translateX(-50%) translateY(-10px); } + 80% { transform: translateX(-50%) translateY(5px); } + 100% { transform: translateX(-50%) translateY(-5px); } @@ -319,27 +368,32 @@ td > .col12 { .container-large-fixed { width: 100%; - position:fixed; - z-index: 15 ; - } + position: fixed; + z-index: 15; +} /* Barre de membre */ #bar { background: #212223; - padding-left: 5px; /* Pour éviter que le select touche le bord lorsque la fenêtre est redimensionnée */ - margin: 0; /*-10px;*/ + padding-left: 5px; + /* Pour éviter que le select touche le bord lorsque la fenêtre est redimensionnée */ + margin: 0; + /*-10px;*/ text-align: right; - position: -webkit-sticky; /* Safari */ + position: -webkit-sticky; + /* Safari */ position: sticky; top: 0; z-index: 19; } + /* fin barre pour les membres */ #bar:after { content: " "; clear: both; display: block; } + #bar ul { padding: 0; margin: 0; @@ -358,13 +412,16 @@ td > .col12 { color: #FFF; transition: background .3s ease-out; } + #bar a:hover { background: #191A1A; text-decoration: none; } + #bar a:active { background: #111112; } + #bar select { width: 250px; border: 0; @@ -373,10 +430,11 @@ td > .col12 { background-color: rgba(255, 255, 255, 1); } -@media (min-width: 768px) { +@media (min-width: 769px) { #bar #barLeft { float: left; } + #bar #barRight { float: right; font-size: 12px; @@ -389,20 +447,25 @@ td > .col12 { padding: 0 1; margin: 0 1; } + #bar ul { height: auto; } + #bar #barSelectPage { - width: 40% ; + width: 40%; font-size: 1em; } + #bar #barLeft { font-size: 1.2em; - float : none; + float: none; } + #bar #barRight { font-size: 1.4em; } + #bar #displayUsername { display: none; } @@ -414,7 +477,7 @@ td > .col12 { overflow: hidden; } -@media (min-width:769px) { +@media (min-width:768px) { #site { margin: 20px auto; } @@ -422,25 +485,29 @@ td > .col12 { /* Bannière */ -@media (min-width:769px) { - body > header { - margin: 0 ;/*-10px;*/ +@media (min-width:768px) { + body>header { + margin: 0; + /*-10px;*/ } + header { - margin:0; + margin: 0; } } header { position: relative; - padding:0; + padding: 0; } + header span { display: inline-block; vertical-align: middle; line-height: 1.2; margin: 0 10px; } + header .container { overflow: hidden; height: 100%; @@ -460,7 +527,7 @@ body > nav { */ /* Items du menu */ -nav a > img { +nav a>img { margin: -4px 0; vertical-align: middle; } @@ -552,35 +619,41 @@ nav::before { display: flex; } -@media (min-width: 768px) { +@media (min-width: 769px) { nav #menu { display: block; } } @media (max-width: 768px) { - body > nav { - margin:0; + body>nav { + margin: 0; } + nav #toggle, - nav #menuLeft { + nav #menuLeft { display: block; float: none; } - nav #menuLeft { + + nav #menuLeft { flex-direction: column; float: none; } + nav #menuRight { font-size: 2em; } + nav #menu { display: none; text-align: left; } + nav li { display: block; } + nav li ul { z-index: 1; opacity: 1; @@ -588,28 +661,32 @@ nav::before { min-width: inherit; width: auto; } + /* Taille du menu hamburger */ nav .zwiico-menu { - font-size:1.5em; + font-size: 1.5em; } + nav .zwiico-cancel { - font-size:1.5em; + font-size: 1.5em; } } /* Barre de navigation fixe quand le menu est en-dehors du site */ #navfixedlogout { - position: -webkit-sticky; /* Safari */ + position: -webkit-sticky; + /* Safari */ position: sticky; - top:0px; - z-index:10; + top: 0px; + z-index: 10; } #navfixedconnected { - top:45px; - z-index:10; - position: -webkit-sticky; /* Safari */ + top: 45px; + z-index: 10; + position: -webkit-sticky; + /* Safari */ position: sticky; } @@ -620,19 +697,21 @@ nav::before { /* Menu vertical */ -.menuSide, .menuSideChild { +.menuSide, +.menuSideChild { padding-left: 10px; margin: 0px; list-style: none; } -ul .menuSideChild, li .menuSideChild { - padding-left:10px; +ul .menuSideChild, +li .menuSideChild { + padding-left: 10px; } /* Corps */ -@media (min-width:769px) { +@media (min-width:768px) { section { padding: 20px; } @@ -646,7 +725,8 @@ section #sectionTitle { margin-top: 0; } -.userLogin, .updateForm { +.userLogin, +.updateForm { min-height: 0px; } @@ -664,8 +744,9 @@ section:after { } /* Pied de page */ -body > footer { - margin: 0;/* -10px;*/ +body>footer { + margin: 0; + /* -10px;*/ } /* @@ -674,36 +755,44 @@ footer { } */ -#footerbody, #footersite { +#footerbody, +#footersite { margin: 0; } .footerbodyFixed { position: fixed; bottom: 0; - left: 0; + left: 0; width: 100%; - z-index:50; + z-index: 50; background-color: inherit; padding: inherit; } -#footersiteRight, #footersiteLeft, #footersiteCenter, -#footerbodyRight, #footerbodyLeft, #footerbodyCenter { +#footersiteRight, +#footersiteLeft, +#footersiteCenter, +#footerbodyRight, +#footerbodyLeft, +#footerbodyCenter { vertical-align: middle; padding: 0; } -footer #footerbody > div { +footer #footerbody>div { margin: 0 } -footer #footersite > div { - padding:0 + +footer #footersite>div { + padding: 0 } -footer #footerbody > div { - padding:0 + +footer #footerbody>div { + padding: 0 } -#footerText > p { + +#footerText>p { margin-top: 0; margin-bottom: 0; } @@ -720,13 +809,17 @@ footer #footerbody > div { /* Conserve le pied de page sur une ligne */ @media (max-width: 768px) { - body > footer { - margin:0; + body>footer { + margin: 0; } + footer .col4 { - width:100%; + width: 100%; } - #footerCopyright, #footerText, #footerSocials { + + #footerCopyright, + #footerText, + #footerSocials { display: flex; justify-content: center; } @@ -740,45 +833,59 @@ footer #footerSocials span { border-radius: 2px; transition: background .3s ease-out; } + footer #footerSocials .zwiico-facebook { background: #3B5999; } + footer #footerSocials .zwiico-facebook:hover { background: #324B80; } + footer #footerSocials .zwiico-linkedin { background: #007BB6; } + footer #footerSocials .zwiico-linkedin:hover { background: #006881; } + footer #footerSocials .zwiico-instagram { background: #E4405F; } + footer #footerSocials .zwiico-instagram:hover { background: #E02246; } + footer #footerSocials .zwiico-pinterest { background: #BD081C; } + footer #footerSocials .zwiico-pinterest:hover { background: #9C0717; } + footer #footerSocials .zwiico-twitter { background: #55ACEE; } + footer #footerSocials .zwiico-twitter:hover { background: #369DEB; } + footer #footerSocials .zwiico-youtube { background: #CD201F; } + footer #footerSocials .zwiico-youtube:hover { background: #AF1B1B; } + footer #footerSocials .zwiico-github { background: #000; } + footer #footerSocials .zwiico-github:hover { background: #000; } @@ -788,10 +895,12 @@ footer #footerSocials .zwiico-github:hover { margin: 16px; text-align: center; } + .speechMimi { display: block; margin: auto; } + .speechBubble { display: block; padding: 20px; @@ -803,6 +912,7 @@ footer #footerSocials .zwiico-github:hover { border-radius: 2px; transition: background .3s ease-out; } + .speechBubble:before { content: " "; position: absolute; @@ -828,9 +938,11 @@ footer #footerSocials .zwiico-github:hover { border-radius: 50%; transition: background .3s ease-out; } + #backToTop:hover { background: rgba(33, 34, 35, .9); } + #backToTop:active { background: rgba(33, 34, 35, 1); } @@ -849,6 +961,7 @@ footer #footerSocials .zwiico-github:hover { text-align: center; font-size: .9em; } + #cookieConsentConfirm { cursor: pointer; margin-left: 10px; @@ -857,32 +970,36 @@ footer #footerSocials .zwiico-github:hover { display: inline-block; transition: background .3s ease-out; } + #cookieConsentConfirm:hover { background: #777; } /* Bloc */ .block { -/* border: 1px solid #D8DFE3;*/ + /* border: 1px solid #D8DFE3;*/ padding: 20px 20px 10px; margin: 20px 0; word-wrap: break-word; border-radius: 2px; } + .block:first-of-type { margin-top: 0; } + .block:last-of-type { margin-bottom: 0; } + .block h4 { margin: -20px -20px 10px -20px; padding: 10px; -/* background: #ECEFF1;*/ + /* background: #ECEFF1;*/ } .block h4 .openClose { - display: inline-flex ; + display: inline-flex; float: right; } @@ -904,13 +1021,16 @@ footer #footerSocials .zwiico-github:hover { padding: 20px; } -.lightbox > span { + +.lightbox>span { color: black; } + .lightbox .lightboxButtons { text-align: center; margin-top: 30px; } + .lightbox .lightboxButtons .button { width: 100%; max-width: 80px; @@ -928,7 +1048,8 @@ input[type='password'], .inputFile, select, textarea { - padding: 10px; /* -1px à cause des bordures */ + padding: 10px; + /* -1px à cause des bordures */ /*background: #FFF;*/ border: 1px solid #D8DFE3; width: 100%; @@ -936,9 +1057,11 @@ textarea { font-family: inherit; transition: border .3s ease-out; } + select { padding: 7px; } + input[type='email']:hover, input[type='text']:hover, input[type='password']:hover, @@ -947,6 +1070,7 @@ select:hover, textarea:hover { border: 1px solid; } + input[type='email'].notice, input[type='text'].notice, input[type='password'].notice, @@ -956,6 +1080,7 @@ textarea.notice { border: 1px solid #E74C3C; /*background: #FAD7D3;*/ } + input[type='email'].notice:hover, input[type='text'].notice:hover, input[type='password'].notice:hover, @@ -964,6 +1089,7 @@ select.notice:hover, textarea.notice:hover { border: 1px solid #A82315; } + button:disabled, input:disabled, select:disabled, @@ -971,9 +1097,12 @@ textarea:disabled { background: #F6F7F8 !important; color: #94A5B0 !important; } + button:disabled .zwiico-spin { - color: #50616C !important /* Icône de soumission unique */ + color: #50616C !important + /* Icône de soumission unique */ } + button { width: 100%; padding: 11px; @@ -983,20 +1112,24 @@ button { border-radius: 2px; transition: background .3s ease-out; } + textarea { height: 100px; resize: vertical; } + label { display: inline-block; margin-bottom: 4px; } + /* Simule le padding des cols pour les inputs en dehors des cols */ -:not([class^="col"]) > .inputWrapper { +:not([class^="col"])>.inputWrapper { padding: 10px 0; } + /* Supprime le padding d'une row dans un col */ -[class^="col"] > .row { +[class^="col"]>.row { margin: -10px; } @@ -1014,14 +1147,17 @@ label { border-radius: 2px; transition: background .3s ease-out; } + /* Bouton redimensionnable pour le formulaire*/ #formSubmit { width: max-content; float: right; } + .button:hover { text-decoration: none; } + .button.disabled { pointer-events: none; cursor: default; @@ -1035,6 +1171,7 @@ label { display: inline-block; width: 88%; } + .inputFileDelete { display: block; width: 10%; @@ -1042,44 +1179,51 @@ label { background: #F5F5F5; text-align: center; float: right; - min-height: 100%; + min-height: 100%; } + .inputFile:hover { text-decoration: none; } /* Empêche le débordement et les sauts de ligne */ .inputFileManagerWrapper { - display: inline; + display: inline; } -.inputFileManagerWrapper > .inputFile { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + +.inputFileManagerWrapper>.inputFile { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } /* Pagination */ .pagination { padding: 10px 0; } + .pagination a { display: inline-block; padding: 10px; margin: 5px; text-align: center; } + .pagination a:hover { text-decoration: none; } + .pagination a.disabled { pointer-events: none; cursor: default; background: #F6F7F8 !important; color: #94A5B0 !important; } + .pagination a:first-child { margin-left: 0; } + .pagination a:last-child { margin-right: 0; } @@ -1093,15 +1237,18 @@ input[type='checkbox'] { height: 0; width: 0; } -input[type='checkbox'] + label { + +input[type='checkbox']+label { display: inline-block; margin-right: 10px; cursor: pointer; } -input[type='checkbox'] + label span { + +input[type='checkbox']+label span { vertical-align: middle; } -input[type='checkbox'] + label:before { + +input[type='checkbox']+label:before { content: '\2713'; display: inline-block; text-align: center; @@ -1117,14 +1264,17 @@ input[type='checkbox'] + label:before { vertical-align: top; border-radius: 2px; } -input[type='checkbox'].notice + label:before { + +input[type='checkbox'].notice+label:before { background: #E74C3C; } -input[type='checkbox']:hover + label:before, -input[type='checkbox']:checked:active + label:before { + +input[type='checkbox']:hover+label:before, +input[type='checkbox']:checked:active+label:before { background: #ECEFF1; } -input[type='checkbox']:disabled + label:before{ + +input[type='checkbox']:disabled+label:before { background: #F6F7F8 !important; } @@ -1132,8 +1282,10 @@ input[type='checkbox']:disabled + label:before{ .datepicker { cursor: text; } + .pika-select { - padding: 0; /* À cause du padding ajouté aux selects */ + padding: 0; + /* À cause du padding ajouté aux selects */ } /* Paramètres de l'étiquette dans form */ @@ -1141,7 +1293,7 @@ input[type='checkbox']:disabled + label:before{ margin-top: 20px; } -.formLabel hr { +.formLabel hr { border: 1px solid; margin: 5px 0 5px; } @@ -1158,17 +1310,22 @@ input[type='checkbox']:disabled + label:before{ -moz-box-sizing: border-box; box-sizing: border-box; } + .row { margin: 0 -10px; - font-size: 0; /* Hack whitespace */ + font-size: 0; + /* Hack whitespace */ } + /* Supprime les margins du premier et du dernier élément d'un col, utile pour les cols générés depuis l'éditeur de texte. (Ne s'applique pas aux rows pour ne pas supprimer les margins négatifs) */ -.row > div > :first-child:not(.row) { +.row>div> :first-child:not(.row) { margin-top: 0; } -.row > div > :last-child:not(.row) { + +.row>div> :last-child:not(.row) { margin-bottom: 0; } + .col1, .col2, .col3, @@ -1187,73 +1344,96 @@ input[type='checkbox']:disabled + label:before{ min-height: 1px; display: inline-block; } -@media (min-width: 768px) { + +@media (min-width: 769px) { .col1 { width: 8.33333333%; } + .col2 { width: 16.66666667%; } + .col3 { width: 25%; } + .col4 { width: 33.33333333%; } + .col5 { width: 41.66666667%; } + .col6 { width: 50%; } + .col7 { width: 58.33333333%; } + .col8 { width: 66.66666667%; } + .col9 { width: 75%; } + .col10 { width: 83.33333333%; } + .col11 { width: 91.66666667%; } + .col12 { width: 100%; } + .offset1 { margin-left: 8.33333333%; } + .offset2 { margin-left: 16.66666667%; } + .offset3 { margin-left: 25%; } + .offset4 { margin-left: 33.33333333%; } + .offset5 { margin-left: 41.66666667%; } + .offset6 { margin-left: 50%; } + .offset7 { margin-left: 58.33333333%; } + .offset8 { margin-left: 66.66666667%; } + .offset9 { margin-left: 75%; } + .offset10 { margin-left: 83.33333333%; } + .offset11 { margin-left: 91.66666667%; } @@ -1292,55 +1472,79 @@ th.col12 { min-height: 1px; display: table-cell; } + td.col1, th.col1 { width: 8.33333333%; } + td.col2, th.col2 { width: 16.66666667%; } + td.col3, th.col3 { width: 25%; } + td.col4, th.col4 { width: 33.33333333%; } + td.col5, th.col5 { width: 41.66666667%; } + td.col6, th.col6 { width: 50%; } + td.col7, th.col7 { width: 58.33333333%; } + td.col8, th.col8 { width: 66.66666667%; } + td.col9, th.col9 { width: 75%; } + td.col10, th.col10 { width: 83.33333333%; } + td.col11, th.col11 { width: 91.66666667%; } + td.col12, th.col12 { width: 100%; } +/* Tableau sur les écrans de très petites tailles */ +@media (max-width: 480px){ + .table tr{ + display: block; + margin-bottom: 10px; + } + .table td { + display: block; + text-align: right; + width: auto; + } +} /** * Classes rapides @@ -1349,27 +1553,35 @@ th.col12 { .displayNone { display: none; } + .textAlignCenter { text-align: center; } + .textAlignRight { text-align: right; } + .verticalAlignBottom { vertical-align: bottom; } + .verticalAlignMiddle { vertical-align: middle; } + .clearBoth { clear: both; } + .colorGreen { color: #27AE60; } + .colorRed { color: #E74C3C; } + .colorOrange { color: #F39C12; } @@ -1384,6 +1596,7 @@ th.col12 { padding: 0; list-style: none; } + .accordion-title { display: block; margin: 0; @@ -1392,22 +1605,25 @@ th.col12 { text-decoration: none; cursor: pointer; } + .accordion-title:hover { background: lightgrey; } + .accordion-content { padding: 7px; } /* Captcha */ -.captcha img{ +.captcha img { height: 30px; vertical-align: middle; padding-left: 10px; padding-right: 10px; } -.captcha input[type='text'] { + +.captcha input[type='text'] { width: 4em; text-align: center; margin: auto auto auto 2em; @@ -1421,10 +1637,12 @@ th.col12 { color: #D8890B; font-size: 1.3em !important; } + .zwiico-minus-circled, .zwiico-plus-circled { transition: all 1s ease; - } +} + .zwiico-minus-circled:hover, .zwiico-plus-circled:hover { -webkit-transform:scale(1.25); /* Safari et Chrome */ diff --git a/core/module/config/view/index/index.php b/core/module/config/view/index/index.php index 3cdd86f4..ed0b90fe 100644 --- a/core/module/config/view/index/index.php +++ b/core/module/config/view/index/index.php @@ -49,7 +49,7 @@ 'Description du site', 'value' => $this->getData(['config', 'metaDescription']), - 'help' => 'La description participe au référencement, n\'oubliez pas de personnaliser la description de chaque page sans un copié collé.' + 'help' => 'La description d\'une page participe à son référencement, chaque page doit disposer d\'une description différente.' ]); ?> @@ -102,7 +102,7 @@
$this->getData(['config','captchaStrong']), - 'help' => 'Option recommandée pour sécuriser la connexion. S\'applique à tous les captchas du site.' + 'help' => 'Option recommandée pour sécuriser la connexion. S\'applique à tous les captchas du site. Le captcha simple se limite à une addition de nombres de 0 à 10. Le captcha renforcé utilise quatre opérations de nombres de 0 à 20.' ]); ?>
diff --git a/core/module/install/install.php b/core/module/install/install.php index 7dfdf89f..bc573824 100644 --- a/core/module/install/install.php +++ b/core/module/install/install.php @@ -61,6 +61,8 @@ class install extends common { 'forgot' => 0, 'group' => self::GROUP_ADMIN, 'lastname' => $userLastname, + 'pseudo' => 'Admin', + 'signature' => 1, 'mail' => $userMail, 'password' => $this->getInput('installPassword', helper::FILTER_PASSWORD, true) ] diff --git a/core/module/install/ressource/defaultdata.php b/core/module/install/ressource/defaultdata.php index 23237a3f..28808690 100644 --- a/core/module/install/ressource/defaultdata.php +++ b/core/module/install/ressource/defaultdata.php @@ -649,13 +649,13 @@ class init extends common { 'module' => [ 'blog' => [ 'mon-premier-article' => [ - 'closeComment' => false, 'comment' => [ '58e11d09e5aff' => [ 'author' => 'Rémi', 'content' => 'Article bien rédigé et très pertinent, bravo !', 'createdOn' => 1421748000, - 'userId' => '' + 'userId' => '', + 'approval' => true ] ], 'content' => '

Et eodem impetu Domitianum praecipitem per scalas itidem funibus constrinxerunt, eosque coniunctos per ampla spatia civitatis acri raptavere discursu. iamque artuum et membrorum divulsa conpage superscandentes corpora mortuorum ad ultimam truncata deformitatem velut exsaturati mox abiecerunt in flumen.

Ex his quidam aeternitati se commendari posse per statuas aestimantes eas ardenter adfectant quasi plus praemii de figmentis aereis sensu carentibus adepturi, quam ex conscientia honeste recteque factorum, easque auro curant inbracteari, quod Acilio Glabrioni delatum est primo, cum consiliis armisque regem superasset Antiochum. quam autem sit pulchrum exigua haec spernentem et minima ad ascensus verae gloriae tendere longos et arduos, ut memorat vates Ascraeus, Censorius Cato monstravit. qui interrogatus quam ob rem inter multos... statuam non haberet malo inquit ambigere bonos quam ob rem id non meruerim, quam quod est gravius cur inpetraverim mussitare.

Latius iam disseminata licentia onerosus bonis omnibus Caesar nullum post haec adhibens modum orientis latera cuncta vexabat nec honoratis parcens nec urbium primatibus nec plebeiis.

', @@ -665,10 +665,15 @@ class init extends common { 'publishedOn' => 1548790902, 'state' => true, 'title' => 'Mon premier article', - 'userId' => '' // Géré au moment de l'installation + 'userId' => '', // Géré au moment de l'installation + 'editConsent' => 'all', + 'commentMaxlength' => '500', + 'commentApproved' => false, + 'commentClose' => false, + 'commentNotification' => false, + 'commentGroupNotification' => 3 ], 'mon-deuxieme-article' => [ - 'closeComment' => false, 'comment' => [], 'content' => '

Et prima post Osdroenam quam, ut dictum est, ab hac descriptione discrevimus, Commagena, nunc Euphratensis, clementer adsurgit, Hierapoli, vetere Nino et Samosata civitatibus amplis inlustris.

Ob haec et huius modi multa, quae cernebantur in paucis, omnibus timeri sunt coepta. et ne tot malis dissimulatis paulatimque serpentibus acervi crescerent aerumnarum, nobilitatis decreto legati mittuntur: Praetextatus ex urbi praefecto et ex vicario Venustus et ex consulari Minervius oraturi, ne delictis supplicia sint grandiora, neve senator quisquam inusitato et inlicito more tormentis exponeretur.

Sed ut tum ad senem senex de senectute, sic hoc libro ad amicum amicissimus scripsi de amicitia. Tum est Cato locutus, quo erat nemo fere senior temporibus illis, nemo prudentior; nunc Laelius et sapiens (sic enim est habitus) et amicitiae gloria excellens de amicitia loquetur. Tu velim a me animum parumper avertas, Laelium loqui ipsum putes. C. Fannius et Q. Mucius ad socerum veniunt post mortem Africani; ab his sermo oritur, respondet Laelius, cuius tota disputatio est de amicitia, quam legens te ipse cognosces.

', 'picture' => 'galerie/landscape/desert.jpg', @@ -677,10 +682,15 @@ class init extends common { 'publishedOn' => 1550432502, 'state' => true, 'title' => 'Mon deuxième article', - 'userId' => '' // Géré au moment de l'installation + 'userId' => '', // Géré au moment de l'installation + 'editConsent' => 'all', + 'commentMaxlength' => '500', + 'commentApproved' => false, + 'commentClose' => false, + 'commentNotification' => false, + 'commentGroupNotification' => 3 ], 'mon-troisieme-article' => [ - 'closeComment' => true, 'comment' => [], 'content' => '

Rogatus ad ultimum admissusque in consistorium ambage nulla praegressa inconsiderate et leviter proficiscere inquit ut praeceptum est, Caesar sciens quod si cessaveris, et tuas et palatii tui auferri iubebo prope diem annonas. hocque solo contumaciter dicto subiratus abscessit nec in conspectum eius postea venit saepius arcessitus.

Proinde concepta rabie saeviore, quam desperatio incendebat et fames, amplificatis viribus ardore incohibili in excidium urbium matris Seleuciae efferebantur, quam comes tuebatur Castricius tresque legiones bellicis sudoribus induratae.

Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur abnuens et reclamans, adulatorum oblatrantibus turmis, bellicosus sane milesque semper et militum ductor sed forensibus iurgiis longe discretus, qui metu sui discriminis anxius cum accusatores quaesitoresque subditivos sibi consociatos ex isdem foveis cerneret emergentes, quae clam palamve agitabantur, occultis Constantium litteris edocebat inplorans subsidia, quorum metu tumor notissimus Caesaris exhalaret.

', 'picture' => 'galerie/landscape/iceberg.jpg', @@ -689,7 +699,13 @@ class init extends common { 'publishedOn' => 1550864502, 'state' => true, 'title' => 'Mon troisième article', - 'userId' => '' // Géré au moment de l'installation + 'userId' => '', // Géré au moment de l'installation + 'editConsent' => 'all', + 'commentMaxlength' => '500', + 'commentApproved' => false, + 'commentClose' => true, + 'commentNotification' => false, + 'commentGroupNotification' => 3 ] ], 'galeries' => [ diff --git a/core/module/theme/theme.php b/core/module/theme/theme.php index 83ed513e..a1820966 100644 --- a/core/module/theme/theme.php +++ b/core/module/theme/theme.php @@ -24,7 +24,6 @@ class theme extends common { 'index' => self::GROUP_ADMIN, 'menu' => self::GROUP_ADMIN, 'reset' => self::GROUP_ADMIN, - 'resetAdmin' => self::GROUP_ADMIN, 'site' => self::GROUP_ADMIN, 'admin' => self::GROUP_ADMIN, 'manage' => self::GROUP_ADMIN, @@ -535,29 +534,32 @@ class theme extends common { */ public function reset() { // Supprime le fichier de personnalisation avancée - unlink(self::DATA_DIR.'custom.css'); + $redirect =''; + switch ($this->getUrl(2)) { + case 'admin': + $this->initData('admin'); + $redirect = helper::baseUrl() . 'theme/admin'; + break; + case 'manage': + $this->initData('theme'); + $redirect = helper::baseUrl() . 'theme/manage'; + break; + case 'custom': + unlink(self::DATA_DIR.'custom.css'); + $redirect = helper::baseUrl() . 'theme/advanced'; + break; + default : + $redirect = helper::baseUrl() . 'theme'; + } + // Valeurs en sortie $this->addOutput([ - 'notification' => 'Personnalisation avancée réinitialisée', - 'redirect' => helper::baseUrl() . 'theme/advanced', + 'notification' => 'Réinitialisation effectuée', + 'redirect' => $redirect, 'state' => true ]); } - /** - * Réinitialisation de la personnalisation avancée - */ - public function resetAdmin() { - // Supprime le fichier de personnalisation avancée - //unlink(self::DATA_DIR.'admin.json'); - $this->initData('admin'); - // Valeurs en sortie - $this->addOutput([ - 'notification' => 'Thème réinitialisé', - 'redirect' => helper::baseUrl() . 'theme/admin', - 'state' => true - ]); - } /** * Options du site @@ -635,7 +637,7 @@ class theme extends common { ) { $modele = 'admin'; } - if (!empty($modele) + if (!empty($modele) ) { // traiter l'archive $success = $zip->extractTo('.'); diff --git a/core/module/theme/view/admin/admin.js.php b/core/module/theme/view/admin/admin.js.php index c23e98ed..e62011e2 100644 --- a/core/module/theme/view/admin/admin.js.php +++ b/core/module/theme/view/admin/admin.js.php @@ -32,7 +32,7 @@ $("input, select").on("change", function() { var colors = core.colorVariants($("#adminColorRed").val()); css += ".button.buttonRed {background-color: " + colors.normal + ";color:" + colors.text + ";}.button.buttonRed:hover {background-color:" + colors.darken + ";color:" + colors.text + "}.button.buttonRed:active {background-color:" + colors.veryDarken + ";color:" + colors.text + "}"; var colors = core.colorVariants($("#adminColorGreen").val()); - css += "button[type=submit] {background-color: " + colors.normal + ";color: " + ";color:" + colors.text + "}button[type=submit]:hover {background-color: " + colors.darken + ";color:" + colors.text + ";}button[type=submit]:active {background-color:" + colors.veryDarken + ";color:" + colors.text + "}"; + css += ".button.buttonGreen, button[type=submit] {background-color: " + colors.normal + ";color: " + ";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.veryDarken + ";color:" + colors.text + "}"; var colors = core.colorVariants($("#adminBackGroundBlockColor").val()); css += ".block {border: 1px solid " + $("#adminBorderBlockColor").val() + ";}.block h4 {background-color: " + colors.normal + ";color:" + colors.text + ";}"; css += "input[type=email],input[type=text],input[type=password],select:not(#barSelectPage),textarea:not(.editorWysiwyg),.inputFile{background-color: " + colors.normal + ";color:" + colors.text + ";border: 1px solid " + $("#adminBorderBlockColor").val() + ";}"; @@ -46,3 +46,13 @@ $("input, select").on("change", function() { .appendTo("head"); }); + +/** + * Confirmation de réinitialisation + */ +$("#configAdminReset").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir réinitialiser à son état d'origine le thème de l\'administration ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); diff --git a/core/module/theme/view/admin/admin.php b/core/module/theme/view/admin/admin.php index 97a57706..134e4bba 100644 --- a/core/module/theme/view/admin/admin.php +++ b/core/module/theme/view/admin/admin.php @@ -16,7 +16,7 @@
'buttonRed', - 'href' => helper::baseUrl() . 'theme/resetAdmin', + 'href' => helper::baseUrl() . 'theme/reset/admin', 'value' => 'Réinitialiser', 'ico' => 'cancel' ]); ?> diff --git a/core/module/theme/view/advanced/advanced.php b/core/module/theme/view/advanced/advanced.php index b3a82d33..c13fbd91 100644 --- a/core/module/theme/view/advanced/advanced.php +++ b/core/module/theme/view/advanced/advanced.php @@ -10,7 +10,7 @@
helper::baseUrl() . 'theme/reset', + 'href' => helper::baseUrl() . 'theme/reset/custom', 'class' => 'buttonRed', 'ico' => 'cancel', 'value' => 'Réinitialiser' diff --git a/core/module/theme/view/manage/manage.js.php b/core/module/theme/view/manage/manage.js.php new file mode 100644 index 00000000..765a9662 --- /dev/null +++ b/core/module/theme/view/manage/manage.js.php @@ -0,0 +1,21 @@ +/** + * This file is part of Zwii. + * + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @license GNU General Public License, version 3 + * @link http://zwiicms.fr/ + */ + +/** + * Confirmation de réinitialisation + */ +$("#configManageReset").on("click", function() { + var _this = $(this); + return core.confirm("Êtes-vous sûr de vouloir réinitialiser à son état d'origine le thème du site ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); \ No newline at end of file diff --git a/core/module/theme/view/manage/manage.php b/core/module/theme/view/manage/manage.php index ff7a28ba..68de0814 100644 --- a/core/module/theme/view/manage/manage.php +++ b/core/module/theme/view/manage/manage.php @@ -8,53 +8,59 @@ 'value' => 'Retour' ]); ?>
+
+ 'buttonRed', + 'href' => helper::baseUrl() . 'theme/reset/manage', + 'value' => 'Réinitialiser', + 'ico' => 'cancel' + ]); ?> +
+
+ 'Appliquer' + ]); ?> +
-
+
-

Installer un thème archivé

+

Installer un thème archivé (site ou administration)

-
+
'Archive ZIP :', 'type' => 2 ]); ?>
-
-
- 'Appliquer' - ]); ?> -
-
+
+
-

Sauvegarder le thème

+

Sauvegarde du thème dans les fichiers du site

helper::baseUrl() . 'theme/save/theme', 'ico' => 'download-cloud', - 'value' => 'Thème site' + 'value' => 'Thème du site' ]); ?>
helper::baseUrl() . 'theme/save/admin', 'ico' => 'download-cloud', - 'value' => 'Thème administration' + 'value' => 'Thème de l\'administration' ]); ?>
-
-
- Le fichier de sauvegarde est généré dans le dossier Thème du gestionnaire de fichiers. -
-
+
+ +

Télécharger le thème

@@ -62,14 +68,14 @@ helper::baseUrl() . 'theme/export/theme', 'ico' => 'download', - 'value' => 'Thème site' + 'value' => 'Thème du site' ]); ?>
helper::baseUrl() . 'theme/export/admin', 'ico' => 'download', - 'value' => 'Thème administration' + 'value' => 'Thème de l\'administration' ]); ?>
diff --git a/core/module/theme/view/menu/menu.php b/core/module/theme/view/menu/menu.php index f728bdfe..9ed051fd 100644 --- a/core/module/theme/view/menu/menu.php +++ b/core/module/theme/view/menu/menu.php @@ -178,8 +178,7 @@
$this->getData(['theme', 'menu', 'loginLink']), - 'help' => 'L\'activation de cette option n\'est pas recommandée' + 'checked' => $this->getData(['theme', 'menu', 'loginLink']) ]); ?>
diff --git a/core/module/user/ressource/.htaccess b/core/module/user/ressource/.htaccess new file mode 100644 index 00000000..e8a0ad3a --- /dev/null +++ b/core/module/user/ressource/.htaccess @@ -0,0 +1,8 @@ +# Bloque l'accès aux données +Order deny,allow +Deny from all +# Sauf l'accès au modèle csv + +Order Allow,Deny + Allow from all + \ No newline at end of file diff --git a/core/module/user/ressource/template.csv b/core/module/user/ressource/template.csv new file mode 100644 index 00000000..ff495590 --- /dev/null +++ b/core/module/user/ressource/template.csv @@ -0,0 +1,2 @@ +id;nom;prenom;email;groupe +jbon;Bon;Jean;jean.bon@email.fr;1 diff --git a/core/module/user/user.php b/core/module/user/user.php index 9d8ce8e5..eac48d9a 100644 --- a/core/module/user/user.php +++ b/core/module/user/user.php @@ -17,20 +17,35 @@ class user extends common { public static $actions = [ 'add' => self::GROUP_ADMIN, 'delete' => self::GROUP_ADMIN, - 'edit' => self::GROUP_MEMBER, - 'forgot' => self::GROUP_VISITOR, + 'import' => self::GROUP_ADMIN, 'index' => self::GROUP_ADMIN, - 'login' => self::GROUP_VISITOR, + 'edit' => self::GROUP_MEMBER, 'logout' => self::GROUP_MEMBER, + 'forgot' => self::GROUP_VISITOR, + 'login' => self::GROUP_VISITOR, 'reset' => self::GROUP_VISITOR ]; public static $users = []; + //Paramètres pour choix de la signature + public static $signature = [ + self::SIGNATURE_ID => 'Identifiant', + self::SIGNATURE_PSEUDO => 'Pseudo', + self::SIGNATURE_FIRSTLASTNAME => 'Prénom Nom', + self::SIGNATURE_LASTFIRSTNAME => 'Nom Prénom' + ]; + public static $userId = ''; public static $userLongtime = false; + public static $separators = [ + ';' => ';', + ',' => ',', + ':' => ':' + ]; + /** * Ajout */ @@ -63,8 +78,15 @@ class user extends common { 'forgot' => 0, 'group' => $this->getInput('userAddGroup', helper::FILTER_INT, true), 'lastname' => $userLastname, + 'pseudo' => $this->getInput('userAddPseudo', helper::FILTER_STRING_SHORT, true), + 'signature' => $this->getInput('userAddSignature', helper::FILTER_INT, true), 'mail' => $userMail, 'password' => $this->getInput('userAddPassword', helper::FILTER_PASSWORD, true), + "connectFail" => null, + "connectTimeout" => null, + "accessUrl" => null, + "accessTimer" => null, + "accessCsrf" => null ] ]); @@ -77,7 +99,6 @@ class user extends common { 'Bonjour ' . $userFirstname . ' ' . $userLastname . ',

' . 'Un administrateur vous a créé un compte sur le site ' . $this->getData(['config', 'title']) . '. Vous trouverez ci-dessous les détails de votre compte.

' . 'Identifiant du compte : ' . $this->getInput('userAddId') . '
' . - 'Mot de passe du compte : ' . $this->getInput('userAddPassword') . '

' . 'Nous ne conservons pas les mots de passe, en conséquence nous vous conseillons de conserver ce message tant que vous ne vous êtes pas connecté. Vous pourrez modifier votre mot de passe après votre première connexion.', null ); @@ -208,15 +229,26 @@ class user extends common { else { $newGroup = $this->getData(['user', $this->getUrl(2), 'group']); } + // Modification de nom Prénom + if($this->getUser('group') === self::GROUP_ADMIN){ + $newfirstname = $this->getInput('userEditFirstname', helper::FILTER_STRING_SHORT, true); + $newlastname = $this->getInput('userEditLastname', helper::FILTER_STRING_SHORT, true); + } + else{ + $newfirstname = $this->getData(['user', $this->getUrl(2), 'firstname']); + $newlastname = $this->getData(['user', $this->getUrl(2), 'lastname']); + } // Modifie l'utilisateur $this->setData([ 'user', $this->getUrl(2), [ - 'firstname' => $this->getInput('userEditFirstname', helper::FILTER_STRING_SHORT, true), + 'firstname' => $newfirstname, 'forgot' => 0, 'group' => $newGroup, - 'lastname' => $this->getInput('userEditLastname', helper::FILTER_STRING_SHORT, true), + 'lastname' => $newlastname, + 'pseudo' => $this->getInput('userEditPseudo', helper::FILTER_STRING_SHORT, true), + 'signature' => $this->getInput('userEditSignature', helper::FILTER_INT, true), 'mail' => $this->getInput('userEditMail', helper::FILTER_MAIL, true), 'password' => $newPassword, 'connectFail' => $this->getData(['user',$this->getUrl(2),'connectFail']), @@ -413,7 +445,8 @@ class user extends common { // Valeurs en sortie $this->addOutput([ 'notification' => 'Connexion réussie', - 'redirect' => helper::baseUrl() . str_replace('_', '/', str_replace('__', '#', $this->getUrl(2))), + 'redirect' => helper::baseUrl(), + //'redirect' => helper::baseUrl() . str_replace('_', '/', str_replace('__', '#', $this->getUrl(2))), 'state' => true ]); } @@ -532,9 +565,137 @@ class user extends common { } // Valeurs en sortie $this->addOutput([ - 'title' => 'Réinitialisation du mot de passe', + 'display' => self::DISPLAY_LAYOUT_LIGHT, + 'title' => 'Réinitialisation de votre mot de passe', 'view' => 'reset' ]); } } + + /** + * Importation CSV d'utilisateurs + */ + public function import() { + // Soumission du formulaire + $notification = ''; + $success = true; + if($this->isPost()) { + // Lecture du CSV et construction du tableau + $file = $this->getInput('userImportCSVFile',helper::FILTER_STRING_SHORT, true); + $filePath = self::FILE_DIR . 'source/' . $file; + if ($file AND file_exists($filePath)) { + // Analyse et extraction du CSV + $rows = array_map(function($row) { return str_getcsv($row, $this->getInput('userImportSeparator') ); }, file($filePath)); + $header = array_shift($rows); + $csv = array(); + foreach($rows as $row) { + $csv[] = array_combine($header, $row); + } + // Traitement des données + foreach($csv as $item ) { + // Données valides + if( array_key_exists('id', $item) + AND array_key_exists('prenom',$item) + AND array_key_exists('nom',$item) + AND array_key_exists('groupe',$item) + AND array_key_exists('email',$item) + AND $item['nom'] + AND $item['prenom'] + AND $item['id'] + AND $item['email'] + AND $item['groupe'] + ) { + // Validation du groupe + $item['groupe'] = (int) $item['groupe']; + $item['groupe'] = ( $item['groupe'] >= self::GROUP_BANNED AND $item['groupe'] <= self::GROUP_ADMIN ) + ? $item['groupe'] : 1; + // L'utilisateur existe + if ( $this->getData(['user',helper::filter($item['id'] , helper::FILTER_ID)])) + { + // Notification du doublon + $item['notification'] = template::ico('cancel'); + // Création du tableau de confirmation + self::$users[] = [ + helper::filter($item['id'] , helper::FILTER_ID), + $item['nom'], + $item['prenom'], + self::$groups[$item['groupe']], + $item['prenom'], + $item['email'], + $item['notification'] + ]; + // L'utilisateur n'existe pas + } else { + // Nettoyage de l'identifiant + $userId = helper::filter($item['id'] , helper::FILTER_ID); + // Enregistre le user + $create = $this->setData([ + 'user', + $userId, [ + 'firstname' => $item['prenom'], + 'forgot' => 0, + 'group' => $item['groupe'] , + 'lastname' => $item['nom'], + 'mail' => $item['email'], + 'pseudo' => $item['prenom'], + 'signature' => 1, // Pseudo + 'password' => uniqid(), // A modifier à la première connexion + "connectFail" => null, + "connectTimeout" => null, + "accessUrl" => null, + "accessTimer" => null, + "accessCsrf" => null + ]]); + // Icône de notification + $item['notification'] = $create ? template::ico('check') : template::ico('cancel'); + // Envoi du mail + if ($create + AND $this->getInput('userImportNotification',helper::FILTER_BOOLEAN) === true) { + $sent = $this->sendMail( + $item['email'], + 'Compte créé sur ' . $this->getData(['config', 'title']), + 'Bonjour ' . $item['prenom'] . ' ' . $item['nom'] . ',

' . + 'Un administrateur vous a créé un compte sur le site ' . $this->getData(['config', 'title']) . '. Vous trouverez ci-dessous les détails de votre compte.

' . + 'Identifiant du compte : ' . $userId . '
' . + 'Un mot de passe provisoire vous été attribué, à la première connexion cliquez sur Mot de passe Oublié.' + ); + if ($sent === true) { + // Mail envoyé changement de l'icône + $item['notification'] = template::ico('mail') ; + } + } + // Création du tableau de confirmation + self::$users[] = [ + $userId, + $item['nom'], + $item['prenom'], + self::$groups[$item['groupe']], + $item['prenom'], + $item['email'], + $item['notification'] + ]; + } + } + } + if (empty(self::$users)) { + $notification = 'Rien à importer, erreur de format ou fichier incorrect' ; + $success = false; + } else { + $notification = 'Importation effectuée' ; + $success = true; + } + } else { + $notification = 'Erreur de lecture, vérifiez les permissions'; + $success = false; + } + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Importation', + 'view' => 'import', + 'notification' => $notification, + 'state' => $success + ]); + } + } \ No newline at end of file diff --git a/core/module/user/view/add/add.php b/core/module/user/view/add/add.php index 18a52d5d..ad80e4d1 100644 --- a/core/module/user/view/add/add.php +++ b/core/module/user/view/add/add.php @@ -30,6 +30,14 @@ ]); ?>
+ 'off', + 'label' => 'Pseudo' + ]); ?> + 'Signature', + 'selected' => 1 + ]); ?> 'off', 'label' => 'Adresse mail' @@ -73,9 +81,9 @@ 'label' => 'Confirmation' ]); ?> + 'Prévenir l\'utilisateur par mail'); + ?>
- \ No newline at end of file + diff --git a/core/module/user/view/edit/edit.php b/core/module/user/view/edit/edit.php index a2e2c551..1e890fe7 100644 --- a/core/module/user/view/edit/edit.php +++ b/core/module/user/view/edit/edit.php @@ -1,7 +1,7 @@
- getUser('group') === self::GROUP_ADMIN): ?> + getUser('group') === self::GROUP_ADMIN): ?> 'buttonGrey', 'href' => helper::baseUrl() . 'user', @@ -29,6 +29,7 @@
'off', + 'disabled' => $this->getUser('group') > 2 ? false : true, 'label' => 'Prénom', 'value' => $this->getData(['user', $this->getUrl(2), 'firstname']) ]); ?> @@ -36,11 +37,21 @@
'off', + 'disabled' => $this->getUser('group') > 2 ? false : true, 'label' => 'Nom', 'value' => $this->getData(['user', $this->getUrl(2), 'lastname']) ]); ?>
+ 'off', + 'label' => 'Pseudo', + 'value' => $this->getData(['user', $this->getUrl(2), 'pseudo']) + ]); ?> + 'Signature', + 'selected' => $this->getData(['user', $this->getUrl(2), 'signature']) + ]); ?> 'off', 'label' => 'Adresse mail', @@ -98,4 +109,4 @@
- \ No newline at end of file + diff --git a/core/module/user/view/forgot/forgot.php b/core/module/user/view/forgot/forgot.php index 76931e3e..29dc17e8 100644 --- a/core/module/user/view/forgot/forgot.php +++ b/core/module/user/view/forgot/forgot.php @@ -5,7 +5,6 @@
'buttonGrey', 'href' => helper::baseUrl() . 'user/login/' . $this->getUrl(2), 'ico' => 'left', 'value' => 'Retour' diff --git a/core/module/user/view/import/import.css b/core/module/user/view/import/import.css new file mode 100644 index 00000000..2aa6edae --- /dev/null +++ b/core/module/user/view/import/import.css @@ -0,0 +1,16 @@ +/** + * This file is part of Zwii. + * + * For full copyright and license information, please see the LICENSE + * file that was distributed with this source code. + * + * @author Rémi Jean + * @copyright Copyright (C) 2008-2018, Rémi Jean + * @author Frédéric Tempez + * @copyright Copyright (C) 2018-2020, Frédéric Tempez + * @license GNU General Public License, version 3 + * @link http://zwiicms.fr/ + */ + + +@import url("site/data/admin.css"); \ No newline at end of file diff --git a/core/module/user/view/import/import.php b/core/module/user/view/import/import.php new file mode 100644 index 00000000..051aabba --- /dev/null +++ b/core/module/user/view/import/import.php @@ -0,0 +1,62 @@ + +
+
+ 'buttonGrey', + 'href' => helper::baseUrl() . 'user', + 'ico' => 'left', + 'value' => 'Retour' + ]); ?> +
+
+ 'Importer' + ]); ?> +
+
+
+
+
+

Importation de fichier plat CSV

+
+
+ 'Liste d\'utilisateurs :' + ]); ?> +
+
+ 'Séparateur' + ]); ?> +
+
+
+
+ false + ]); ?> +
+
+
+
+

Aide :

+
+
+

Les en-têtes obligatoires sont : id, nom, prenom, email et groupe + ( 1 : membre - 2 : éditeur - 3 : administrateur ) +

Voir ce modèle à compléter avec un tableur. + Enregistrement au format CSV, séparateur ; ou , ou :

+
+
+
+
+
+ + + +
+
+ Compte créé | Compte créé et notifié | ou manquant : erreur, compte non importé +
+
+ \ No newline at end of file diff --git a/core/module/user/view/index/index.php b/core/module/user/view/index/index.php index 351e095d..483ad22c 100644 --- a/core/module/user/view/index/index.php +++ b/core/module/user/view/index/index.php @@ -7,7 +7,14 @@ 'value' => 'Accueil' ]); ?>
-
+
+ helper::baseUrl() . 'user/import', + 'ico' => 'plus', + 'value' => 'Importation' + ]); ?> +
+
helper::baseUrl() . 'user/add', 'ico' => 'plus', diff --git a/core/vendor/filemanager/config/config.php b/core/vendor/filemanager/config/config.php index 29537313..73e03b8b 100644 --- a/core/vendor/filemanager/config/config.php +++ b/core/vendor/filemanager/config/config.php @@ -119,7 +119,7 @@ $config = array( | If you want to be forced to assign the extension starting from the mime type | */ - 'mime_extension_rename' => true, + 'mime_extension_rename' => false, /* diff --git a/core/vendor/tinymce/init.js b/core/vendor/tinymce/init.js index fafb074b..809ad9f2 100755 --- a/core/vendor/tinymce/init.js +++ b/core/vendor/tinymce/init.js @@ -4,6 +4,13 @@ */ + /** + * Quand tinyMCE est invoqué hors connexion, initialiser privateKey + */ + if ( typeof(privateKey) == 'undefined') { + var privateKey = null; +}; + tinymce.init({ // Classe où appliquer l'éditeur selector: ".editorWysiwyg", @@ -20,7 +27,7 @@ tinymce.init({ // Plugins plugins: "advlist anchor autolink autoresize autosave codemirror colorpicker contextmenu fullscreen hr image imagetools link lists media paste searchreplace stickytoolbar tabfocus table template textcolor emoticons nonbreaking", // Contenu de la barre d'outils - toolbar: "restoredraft | undo redo | bold italic underline forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist emoticons | table template | image media link | code fullscreen", + toolbar: "restoredraft | undo redo | formatselect bold italic underline forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist emoticons | table template | image media link | code fullscreen", // Emoticons emoticons_append: { custom_mind_explode: { @@ -118,12 +125,6 @@ tinymce.init({ external_plugins: { "filemanager": baseUrl + "core/vendor/filemanager/plugin.min.js" }, - // Thème mobile - // mobile: { - // theme: "mobile", - // plugins: [ 'autosave', 'lists', 'autolink' ], - // toolbar: [ 'undo', 'bold', 'italic', 'styleselect' ] - //}, // Contenu du bouton insérer insert_button_items: "anchor hr table", // Contenu du bouton formats @@ -206,6 +207,144 @@ tinymce.init({ ] }); + +tinymce.init({ + // Classe où appliquer l'éditeur + selector: ".editorWysiwygComment", + setup:function(ed) { + // Aperçu dans le pied de page + ed.on('change', function(e) { + if (ed.id === 'themeFooterText') { + $("#footerText").html(tinyMCE.get('themeFooterText').getContent()); + } + }); + // Limitation du nombre de caractères des commentaires à maxlength + var alarmCaraMin = 200; // alarme sur le nombre de caractères restants à partir de... + var maxlength = parseInt($("#" + (ed.id)).attr("maxlength")); + var id_alarm = "#blogArticleContentAlarm" + var contentLength = 0; + ed.on("keydown", function(e) { + contentLength = ed.getContent({format : 'text'}).length; + if (contentLength > maxlength) { + $(id_alarm).html("Vous avez atteint le maximum de " + maxlength + " caractères ! "); + if(e.keyCode != 8 && e.keyCode != 46){ + e.preventDefault(); + e.stopPropagation(); + return false; + } + } + else{ + if(maxlength - contentLength < alarmCaraMin){ + $(id_alarm).html((maxlength - contentLength) + " caractères restants"); + } + else{ + $(id_alarm).html(" "); + } + } + }); + // Limitation y compris lors d'un copier/coller + ed.on("paste", function(e){ + contentLeng = ed.getContent({format : 'text'}).length - 16; + var data = e.clipboardData.getData('Text'); + if (data.length > (maxlength - contentLeng)) { + $(id_alarm).html("Vous alliez dépasser le maximum de " + maxlength + " caractères ! "); + return false; + } else { + if(maxlength - contentLeng < alarmCaraMin){ + $(id_alarm).html((maxlength - contentLeng - data.length) + " caractères restants"); + } + else{ + $(id_alarm).html(" "); + } + return true; + } + }); + }, + // Langue + language: "fr_FR", + // Plugins + plugins: "advlist anchor autolink autoresize autosave colorpicker contextmenu fullscreen hr lists paste searchreplace stickytoolbar tabfocus template textcolor visualblocks emoticons", + // Contenu de la barre d'outils + toolbar: "restoredraft | undo redo | formatselect bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist emoticons | visualblocks fullscreen", + // Emoticons + emoticons_append: { + custom_mind_explode: { + keywords: ["brain", "mind", "explode", "blown"], + char: "🤯" + } + }, + // Titre des images + image_title: true, + // Pages internes + link_list: baseUrl + "core/vendor/tinymce/links.php", + // Contenu du menu contextuel + contextmenu: "cut copy paste pastetext | selectall searchreplace ", + // Fichiers CSS à intégrer à l'éditeur + content_css: [ + baseUrl + "core/layout/common.css", + baseUrl + "core/vendor/tinymce/content.css", + baseUrl + "site/data/theme.css", + baseUrl + "site/data/custom.css" + ], +// Classe à ajouter à la balise body dans l'iframe + body_class: "editorWysiwyg", + // Cache les menus + menubar: false, + // URL menu contextuel + link_context_toolbar: true, + // Cache la barre de statut + statusbar: false, + // Autorise le copié collé à partir du web + paste_data_images: true, + // Autorise tous les éléments + //valid_elements :"*[*]", + //valid_children : "*[*]", + // Autorise l'ajout de script + // extended_valid_elements: "script[language|type|src]", + // Bloque le dimensionnement des médias (car automatiquement en fullsize avec fitvids pour le responsive) + media_dimensions: true, + // Désactiver la dimension des images + image_dimensions: true, + // Active l'onglet avancé lors de l'ajout d'une image + image_advtab: true, + // Urls absolues + relative_urls: false, + // Url de base + document_base_url: baseUrl, + // Contenu du bouton formats + style_formats: [ + {title: "Headers", items: [ + {title: "Header 1", format: "h1"}, + {title: "Header 2", format: "h2"}, + {title: "Header 3", format: "h3"}, + {title: "Header 4", format: "h4"} + ]}, + {title: "Inline", items: [ + {title: "Bold", icon: "bold", format: "bold"}, + {title: "Italic", icon: "italic", format: "italic"}, + {title: "Underline", icon: "underline", format: "underline"}, + {title: "Strikethrough", icon: "strikethrough", format: "strikethrough"}, + {title: "Superscript", icon: "superscript", format: "superscript"}, + {title: "Subscript", icon: "subscript", format: "subscript"}, + {title: "Code", icon: "code", format: "code"} + ]}, + {title: "Blocks", items: [ + {title: "Paragraph", format: "p"}, + {title: "Blockquote", format: "blockquote"}, + {title: "Div", format: "div"}, + {title: "Pre", format: "pre"} + ]}, + {title: "Alignment", items: [ + {title: "Left", icon: "alignleft", format: "alignleft"}, + {title: "Center", icon: "aligncenter", format: "aligncenter"}, + {title: "Right", icon: "alignright", format: "alignright"}, + {title: "Justify", icon: "alignjustify", format: "alignjustify"} + ]} + ] +}); + + + tinymce.PluginManager.add('stickytoolbar', function(editor, url) { editor.on('init', function() { setSticky(); diff --git a/module/blog/blog.php b/module/blog/blog.php index a13c011c..c679d769 100644 --- a/module/blog/blog.php +++ b/module/blog/blog.php @@ -14,10 +14,16 @@ class blog extends common { + const EDIT_OWNER = 'owner'; + const EDIT_GROUP = 'group'; + const EDIT_ALL = 'all'; + public static $actions = [ 'add' => self::GROUP_MODERATOR, 'comment' => self::GROUP_MODERATOR, + 'commentApprove' => self::GROUP_MODERATOR, 'commentDelete' => self::GROUP_MODERATOR, + 'commentDeleteAll' => self::GROUP_MODERATOR, 'config' => self::GROUP_MODERATOR, 'delete' => self::GROUP_MODERATOR, 'edit' => self::GROUP_MODERATOR, @@ -26,8 +32,21 @@ class blog extends common { public static $articles = []; + // Signature de l'article + public static $articleSignature = ''; + + // Signature du commentaire + public static $editCommentSignature = ''; + public static $comments = []; + public static $nbCommentsApproved = 0; + + public static $commentsDelete; + + // Signatures des commentaires déjà saisis + public static $commentsSignature = []; + public static $pages; public static $states = [ @@ -48,10 +67,26 @@ class blog extends common { 'right' => 'À droite ', ]; + //Paramètre longueur maximale des commentaires en nb de caractères + public static $commentLength = [ + '500' => '500', + '1000' => '1000', + '2000' => '2000', + '5000' => '5000', + '10000' => '10000' + ]; + + // Permissions d'un article + public static $articleConsent = [ + self::EDIT_ALL => 'Tous les groupes', + self::EDIT_GROUP => 'Groupe du propriétaire', + self::EDIT_OWNER => 'Propiétaire' + ]; + public static $users = []; - const BLOG_VERSION = '2.02'; + const BLOG_VERSION = '3.1'; /** * Édition @@ -59,26 +94,39 @@ class blog extends common { public function add() { // Soumission du formulaire if($this->isPost()) { + // Modification de l'userId + if($this->getUser('group') === self::GROUP_ADMIN){ + $newuserid = $this->getInput('blogAddUserId', helper::FILTER_STRING_SHORT, true); + } + else{ + $newuserid = $this->getUser('id'); + } // Incrémente l'id de l'article $articleId = helper::increment($this->getInput('blogAddTitle', helper::FILTER_ID), $this->getData(['page'])); $articleId = helper::increment($articleId, (array) $this->getData(['module', $this->getUrl(0)])); $articleId = helper::increment($articleId, array_keys(self::$actions)); // Crée l'article - $this->setData(['module', $this->getUrl(0), $articleId, [ - 'closeComment' => $this->getInput('blogAddCloseComment', helper::FILTER_BOOLEAN), - 'mailNotification' => $this->getInput('blogAddMailNotification', helper::FILTER_BOOLEAN), - 'groupNotification' => $this->getInput('blogAddGroupNotification', helper::FILTER_INT), - 'comment' => [], - 'content' => $this->getInput('blogAddContent', null), - 'picture' => $this->getInput('blogAddPicture', helper::FILTER_STRING_SHORT, true), - 'hidePicture' => $this->getInput('blogAddHidePicture', helper::FILTER_BOOLEAN), - 'pictureSize' => $this->getInput('blogAddPictureSize', helper::FILTER_STRING_SHORT), - 'picturePosition' => $this->getInput('blogAddPicturePosition', helper::FILTER_STRING_SHORT), - 'publishedOn' => $this->getInput('blogAddPublishedOn', helper::FILTER_DATETIME, true), - 'state' => $this->getInput('blogAddState', helper::FILTER_BOOLEAN), - 'title' => $this->getInput('blogAddTitle', helper::FILTER_STRING_SHORT, true), - 'userId' => $this->getInput('blogAddUserId', helper::FILTER_ID, true) - ]]); + $this->setData(['module', + $this->getUrl(0), + $articleId, [ + 'comment' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment']), + 'content' => $this->getInput('blogAddContent', null), + 'picture' => $this->getInput('blogAddPicture', helper::FILTER_STRING_SHORT, true), + 'hidePicture' => $this->getInput('blogAddHidePicture', helper::FILTER_BOOLEAN), + 'pictureSize' => $this->getInput('blogAddPictureSize', helper::FILTER_STRING_SHORT), + 'picturePosition' => $this->getInput('blogAddPicturePosition', helper::FILTER_STRING_SHORT), + 'publishedOn' => $this->getInput('blogAddPublishedOn', helper::FILTER_DATETIME, true), + 'state' => $this->getInput('blogAddState', helper::FILTER_BOOLEAN), + 'title' => $this->getInput('blogAddTitle', helper::FILTER_STRING_SHORT, true), + 'userId' => $newuserid, + 'editConsent' => $this->getInput('blogAddConsent') === self::EDIT_GROUP ? $this->getUser('group') : $this->getInput('blogAddConsent'), + 'commentMaxlength' => $this->getInput('blogAddCommentMaxlength'), + 'commentApproved' => $this->getInput('blogAddCommentApproved', helper::FILTER_BOOLEAN), + 'commentClose' => $this->getInput('blogAddCommentClose', helper::FILTER_BOOLEAN), + 'commentNotification' => $this->getInput('blogAddCommentNotification', helper::FILTER_BOOLEAN), + 'commentGroupNotification' => $this->getInput('blogAddCommentGroupNotification', helper::FILTER_INT) + ] + ]); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', @@ -108,14 +156,13 @@ class blog extends common { * Liste des commentaires */ public function comment() { - // Liste les commentaires - $comments = []; - foreach((array) $this->getData(['module', $this->getUrl(0)]) as $articleId => $article) { - foreach($article['comment'] as &$comment) { - $comment['articleId'] = $articleId; - } - $comments += $article['comment']; - } + $comments = $this->getData(['module', $this->getUrl(0), $this->getUrl(2),'comment']); + self::$commentsDelete = template::button('blogCommentDeleteAll', [ + 'class' => 'blogCommentDeleteAll buttonRed', + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentDeleteAll/' . $this->getUrl(2).'/' . $_SESSION['csrf'] , + 'ico' => 'cancel', + 'value' => 'Tout effacer' + ]); // Ids des commentaires par ordre de création $commentIds = array_keys(helper::arrayCollumn($comments, 'createdOn', 'SORT_DESC')); // Pagination @@ -126,22 +173,34 @@ class blog extends common { for($i = $pagination['first']; $i < $pagination['last']; $i++) { // Met en forme le tableau $comment = $comments[$commentIds[$i]]; + // Bouton d'approbation + $buttonApproval = ''; + // Compatibilité avec les commentaires des versions précédentes, les valider + $comment['approval'] = array_key_exists('approval', $comment) === false ? true : $comment['approval'] ; + if ( $this->getData(['module', $this->getUrl(0), $this->getUrl(2),'commentApproved']) === true) { + $buttonApproval = template::button('blogCommentApproved' . $commentIds[$i], [ + 'class' => $comment['approval'] === true ? 'blogCommentRejected buttonGreen' : 'blogCommentApproved buttonRed' , + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentApprove/' . $this->getUrl(2) . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , + 'value' => $comment['approval'] === true ? 'A' : 'R' + ]); + } self::$comments[] = [ mb_detect_encoding(strftime('%d %B %Y - %H:%M', $comment['createdOn']), 'UTF-8', true) ? strftime('%d %B %Y - %H:%M', $comment['createdOn']) : utf8_encode(strftime('%d %B %Y - %H:%M', $comment['createdOn'])), $comment['content'], $comment['userId'] ? $this->getData(['user', $comment['userId'], 'firstname']) . ' ' . $this->getData(['user', $comment['userId'], 'lastname']) : $comment['author'], + $buttonApproval, template::button('blogCommentDelete' . $commentIds[$i], [ 'class' => 'blogCommentDelete buttonRed', - 'href' => helper::baseUrl() . $this->getUrl(0) . '/comment-delete/' . $comment['articleId'] . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , + 'href' => helper::baseUrl() . $this->getUrl(0) . '/commentDelete/' . $this->getUrl(2) . '/' . $commentIds[$i] . '/' . $_SESSION['csrf'] , 'value' => template::ico('cancel') ]) ]; } // Valeurs en sortie $this->addOutput([ - 'title' => 'Gestion des commentaires', + 'title' => 'Gestion des commentaires : '. $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'title']), 'view' => 'comment' ]); } @@ -170,25 +229,122 @@ class blog extends common { $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3)]); // Valeurs en sortie $this->addOutput([ - 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment', + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment/'.$this->getUrl(2), 'notification' => 'Commentaire supprimé', 'state' => true ]); } } + /** + * Suppression de tous les commentaires de l'article $this->getUrl(2) + */ + public function commentDeleteAll() { + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Suppression + else { + $this->setData(['module', $this->getUrl(0), $this->getUrl(2), 'comment',[] ]); + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment', + 'notification' => 'Commentaires supprimés', + 'state' => true + ]); + } + } + + /** + * Approbation oou désapprobation de commentaire + */ + public function commentApprove() { + // Le commentaire n'existe pas + if($this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3)]) === null) { + // Valeurs en sortie + $this->addOutput([ + 'access' => false + ]); + } + // Jeton incorrect + elseif ($this->getUrl(4) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/config', + 'notification' => 'Action non autorisée' + ]); + } + // Inversion du statut + else { + $approved = !$this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'approval']) ; + $this->setData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), [ + 'author' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'author']), + 'content' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'content']), + 'createdOn' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'createdOn']), + 'userId' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment', $this->getUrl(3), 'userId']), + 'approval' => $approved + ]]); + + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . $this->getUrl(0) . '/comment/'.$this->getUrl(2), + 'notification' => $approved ? 'Commentaire approuvé' : 'Commentaire rejeté', + 'state' => $approved + ]); + } + } + /** * Configuration */ public function config() { // Ids des articles par ordre de publication $articleIds = array_keys(helper::arrayCollumn($this->getData(['module', $this->getUrl(0)]), 'publishedOn', 'SORT_DESC')); + // Gestion des droits d'accès + $filterData=[]; + foreach ($articleIds as $key => $value) { + if ( + ( // Propriétaire + $this->getData(['module', $this->getUrl(0), $value,'editConsent']) === self::EDIT_OWNER + AND ( $this->getData(['module', $this->getUrl(0), $value,'userId']) === $this->getUser('id') + OR $this->getUser('group') === self::GROUP_ADMIN ) + ) + + OR ( + // Groupe + $this->getData(['module', $this->getUrl(0), $value,'editConsent']) !== self::EDIT_OWNER + AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), $value,'editConsent']) + ) + OR ( + // Tout le monde + $this->getData(['module', $this->getUrl(0), $value,'editConsent']) === self::EDIT_ALL + ) + ) { + $filterData[] = $value; + } + } + $articleIds = $filterData; // Pagination $pagination = helper::pagination($articleIds, $this->getUrl(),$this->getData(['config','itemsperPage'])); // Liste des pages self::$pages = $pagination['pages']; // Articles en fonction de la pagination for($i = $pagination['first']; $i < $pagination['last']; $i++) { + // Nombre de commentaires à approuver et approuvés + $approvals = helper::arrayCollumn($this->getData(['module', $this->getUrl(0), $articleIds[$i], 'comment' ]),'approval', 'SORT_DESC'); + if ( is_array($approvals) ) { + $a = array_values($approvals); + $toApprove = count(array_keys($a,false)); + $approved = count(array_keys($a,true)); + } else { + $toApprove = 0; + $approved = count($this->getData(['module', $this->getUrl(0), $articleIds[$i],'comment'])); + } // Met en forme le tableau $date = mb_detect_encoding(strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])), 'UTF-8', true) ? strftime('%d %B %Y', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])) @@ -197,9 +353,17 @@ class blog extends common { ? strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn'])) : utf8_encode(strftime('%H:%M', $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'publishedOn']))); self::$articles[] = [ - $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'title']), + '' . + $this->getData(['module', $this->getUrl(0), $articleIds[$i], 'title']) . + '', $date .' à '. $heure, self::$states[$this->getData(['module', $this->getUrl(0), $articleIds[$i], 'state'])], + // Bouton pour afficher les commentaires de l'article + template::button('blogConfigComment' . $articleIds[$i], [ + 'class' => ($toApprove || $approved ) > 0 ? 'buttonBlue' : 'buttonGrey' , + 'href' => ($toApprove || $approved ) > 0 ? helper::baseUrl() . $this->getUrl(0) . '/comment/' . $articleIds[$i] : '', + 'value' => $toApprove > 0 ? $toApprove . '/' . $approved : $approved + ]), template::button('blogConfigEdit' . $articleIds[$i], [ 'href' => helper::baseUrl() . $this->getUrl(0) . '/edit/' . $articleIds[$i] . '/' . $_SESSION['csrf'], 'value' => template::ico('pencil') @@ -271,6 +435,12 @@ class blog extends common { else { // Soumission du formulaire if($this->isPost()) { + if($this->getUser('group') === self::GROUP_ADMIN){ + $newuserid = $this->getInput('blogEditUserId', helper::FILTER_STRING_SHORT, true); + } + else{ + $newuserid = $this->getUser('id'); + } $articleId = $this->getInput('blogEditTitle', helper::FILTER_ID, true); // Incrémente le nouvel id de l'article if($articleId !== $this->getUrl(2)) { @@ -278,21 +448,27 @@ class blog extends common { $articleId = helper::increment($articleId, $this->getData(['module', $this->getUrl(0)])); $articleId = helper::increment($articleId, array_keys(self::$actions)); } - $this->setData(['module', $this->getUrl(0), $articleId, [ - 'closeComment' => $this->getInput('blogEditCloseComment'), - 'mailNotification' => $this->getInput('blogEditMailNotification', helper::FILTER_BOOLEAN), - 'groupNotification' => $this->getInput('blogEditGroupNotification', helper::FILTER_INT), - 'comment' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment']), - 'content' => $this->getInput('blogEditContent', null), - 'picture' => $this->getInput('blogEditPicture', helper::FILTER_STRING_SHORT, true), - 'hidePicture' => $this->getInput('blogEditHidePicture', helper::FILTER_BOOLEAN), - 'pictureSize' => $this->getInput('blogEditPictureSize', helper::FILTER_STRING_SHORT), - 'picturePosition' => $this->getInput('blogEditPicturePosition', helper::FILTER_STRING_SHORT), - 'publishedOn' => $this->getInput('blogEditPublishedOn', helper::FILTER_DATETIME, true), - 'state' => $this->getInput('blogEditState', helper::FILTER_BOOLEAN), - 'title' => $this->getInput('blogEditTitle', helper::FILTER_STRING_SHORT, true), - 'userId' => $this->getInput('blogEditUserId', helper::FILTER_ID, true) - ]]); + $this->setData(['module', + $this->getUrl(0), + $articleId, [ + 'comment' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment']), + 'content' => $this->getInput('blogEditContent', null), + 'picture' => $this->getInput('blogEditPicture', helper::FILTER_STRING_SHORT, true), + 'hidePicture' => $this->getInput('blogEditHidePicture', helper::FILTER_BOOLEAN), + 'pictureSize' => $this->getInput('blogEditPictureSize', helper::FILTER_STRING_SHORT), + 'picturePosition' => $this->getInput('blogEditPicturePosition', helper::FILTER_STRING_SHORT), + 'publishedOn' => $this->getInput('blogEditPublishedOn', helper::FILTER_DATETIME, true), + 'state' => $this->getInput('blogEditState', helper::FILTER_BOOLEAN), + 'title' => $this->getInput('blogEditTitle', helper::FILTER_STRING_SHORT, true), + 'userId' => $newuserid, + 'editConsent' => $this->getInput('blogEditConsent') === self::EDIT_GROUP ? $this->getUser('group') : $this->getInput('blogEditConsent'), + 'commentMaxlength' => $this->getInput('blogEditCommentMaxlength'), + 'commentApproved' => $this->getInput('blogEditCommentApproved', helper::FILTER_BOOLEAN), + 'commentClose' => $this->getInput('blogEditCommentClose', helper::FILTER_BOOLEAN), + 'commentNotification' => $this->getInput('blogEditCommentNotification', helper::FILTER_BOOLEAN), + 'commentGroupNotification' => $this->getInput('blogEditCommentGroupNotification', helper::FILTER_INT) + ] + ]); // Supprime l'ancien article if($articleId !== $this->getUrl(2)) { $this->deleteData(['module', $this->getUrl(0), $this->getUrl(2)]); @@ -308,7 +484,11 @@ class blog extends common { self::$users = helper::arrayCollumn($this->getData(['user']), 'firstname'); ksort(self::$users); foreach(self::$users as $userId => &$userFirstname) { - $userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']); + // Les membres ne sont pas éditeurs, les exclure de la liste + if ( $this->getData(['user', $userId, 'group']) < self::GROUP_MODERATOR) { + unset(self::$users[$userId]); + } + $userFirstname = $userFirstname . ' ' . $this->getData(['user', $userId, 'lastname']) . ' (' . self::$groupEdits[$this->getData(['user', $userId, 'group'])] . ')'; } unset($userFirstname); // Valeurs en sortie @@ -354,37 +534,38 @@ class blog extends common { } // Crée le commentaire $commentId = helper::increment(uniqid(), $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment'])); + $content = $this->getInput('blogArticleContent', false); $this->setData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentId, [ 'author' => $this->getInput('blogArticleAuthor', helper::FILTER_STRING_SHORT, empty($this->getInput('blogArticleUserId')) ? TRUE : FALSE), - 'content' => $this->getInput('blogArticleContent', helper::FILTER_STRING_SHORT, true), + 'content' => $content, 'createdOn' => time(), 'userId' => $this->getInput('blogArticleUserId'), + 'approval' => !$this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentApproved']) // true commentaire publié false en attente de publication ]]); - // Envoi d'une notification aux administrateurs // Init tableau $to = []; // Liste des destinataires foreach($this->getData(['user']) as $userId => $user) { - if ($user['group'] >= $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'groupNotification']) ) { + if ($user['group'] >= $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentGroupNotification']) ) { $to[] = $user['mail']; } } // Envoi du mail $sent code d'erreur ou de réussite - if ($this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'mailNotification']) === true) { + $notification = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentApproved']) === true ? 'Commentaire déposé en attente d\'approbation': 'Commentaire déposé'; + if ($this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentNotification']) === true) { $sent = $this->sendMail( $to, 'Nouveau commentaire', - 'Bonjour' . ' ' . $user['firstname'] . ' ' . $user['lastname'] . ',

' . - 'Nouveau commentaire déposé sur la page "' . $this->getData(['page', $this->getUrl(0), 'title']) . '" :

', + 'Bonjour,'.'
'. $notification. + ' sur la page "'. $this->getData(['page', $this->getUrl(0), 'title']). '" dans l\'article "'.$this->getUrl(1) .'" :
'. + $content, '' ); // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl() . '#comment', - //'notification' => 'Commentaire ajouté', - //'state' => true - 'notification' => ($sent === true ? 'Commentaire ajouté et une notification envoyée' : 'Commentaire ajouté, erreur de notification :
' . $sent), + 'notification' => ($sent === true ? $notification . '
Une notification a été envoyée.' : $notification . '
Erreur de notification : ' . $sent), 'state' => ($sent === true ? true : null) ]); @@ -392,26 +573,98 @@ class blog extends common { // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . $this->getUrl() . '#comment', - 'notification' => 'Commentaire ajouté', + 'notification' => $notification, 'state' => true ]); } } - // Ids des commentaires par ordre de publication - $commentIds = array_keys(helper::arrayCollumn($this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment']), 'createdOn', 'SORT_DESC')); + // Ids des commentaires approuvés par ordre de publication + $commentsApproved = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment']); + if ($commentsApproved) { + foreach( $commentsApproved as $key => $value){ + if($value['approval']===false) unset($commentsApproved[$key]); + } + // Ligne suivante si affichage du nombre total de commentaires approuvés sous l'article + self::$nbCommentsApproved = count($commentsApproved); + } + $commentIds = array_keys(helper::arrayCollumn($commentsApproved, 'createdOn', 'SORT_DESC')); // Pagination $pagination = helper::pagination($commentIds, $this->getUrl(),$this->getData(['config','itemsperPage']),'#comment'); // Liste des pages self::$pages = $pagination['pages']; + // Signature de l'article + $userIdArticle = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'userId']); + switch ($this->getData(['user', $userIdArticle, 'signature'])){ + case 1: + self::$articleSignature = $userIdArticle; + break; + case 2: + self::$articleSignature = $this->getData(['user', $userIdArticle, 'pseudo']); + break; + case 3: + self::$articleSignature = $this->getData(['user', $userIdArticle, 'firstname']) . ' ' . $this->getData(['user', $userIdArticle, 'lastname']); + break; + case 4: + self::$articleSignature = $this->getData(['user', $userIdArticle, 'lastname']) . ' ' . $this->getData(['user', $userIdArticle, 'firstname']); + break; + default: + self::$articleSignature = $this->getData(['user', $userIdArticle, 'firstname']); + } + // Signature du commentaire édité + if($this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD')) { + $useridcomment = $this->getUser('id'); + switch ($this->getData(['user', $useridcomment, 'signature'])){ + case 1: + self::$editCommentSignature = $useridcomment; + break; + case 2: + self::$editCommentSignature = $this->getData(['user', $useridcomment, 'pseudo']); + break; + case 3: + self::$editCommentSignature = $this->getData(['user', $useridcomment, 'firstname']) . ' ' . $this->getData(['user', $useridcomment, 'lastname']); + break; + case 4: + self::$editCommentSignature = $this->getData(['user', $useridcomment, 'lastname']) . ' ' . $this->getData(['user', $useridcomment, 'firstname']); + break; + default: + self::$editCommentSignature = $this->getData(['user', $useridcomment, 'firstname']); + } + } // Commentaires en fonction de la pagination for($i = $pagination['first']; $i < $pagination['last']; $i++) { - self::$comments[$commentIds[$i]] = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentIds[$i]]); + // Signatures des commentaires + $e = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentIds[$i],'userId']); + if ($e) { + switch ($this->getData(['user', $e, 'signature'])){ + case 1: + self::$commentsSignature[$commentIds[$i]] = $e; + break; + case 2: + self::$commentsSignature[$commentIds[$i]] = $this->getData(['user', $e, 'pseudo']); + break; + case 3: + self::$commentsSignature[$commentIds[$i]] = $this->getData(['user', $e, 'firstname']) . ' ' . $this->getData(['user', $e, 'lastname']); + break; + case 4: + self::$commentsSignature[$commentIds[$i]] = $this->getData(['user', $e, 'lastname']) . ' ' . $this->getData(['user', $e, 'firstname']); + break; + } + } else { + self::$commentsSignature[$commentIds[$i]] = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentIds[$i],'author']); + } + // Données du commentaire si approuvé + if ($this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentIds[$i],'approval']) === true ) { + self::$comments[$commentIds[$i]] = $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment', $commentIds[$i]]); + } } // Valeurs en sortie $this->addOutput([ 'showBarEditButton' => true, 'title' => $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'title']), + 'vendor' => [ + 'tinymce' + ], 'view' => 'article' ]); } @@ -444,4 +697,5 @@ class blog extends common { ]); } } -} \ No newline at end of file +} + diff --git a/module/blog/view/add/add.js.php b/module/blog/view/add/add.js.php index 1ade9625..6067ca10 100644 --- a/module/blog/view/add/add.js.php +++ b/module/blog/view/add/add.js.php @@ -16,4 +16,39 @@ $("#blogAddDraft").on("click", function() { $("#blogAddState").val(0); $("#blogAddForm").trigger("submit"); +}); + +/** + * Options de commentaires + */ +$("#blogAddCommentClose").on("change", function() { + if ($(this).is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } +}); + +$("#blogAddCommentNotification").on("change", function() { + if ($(this).is(':checked') ) { + $("#blogAddCommentGroupNotification").slideDown(); + } else { + $("#blogAddCommentGroupNotification").slideUp(); + } +}); + + +$( document).ready(function() { + + if ($("#blogAddCloseComment").is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } + + if ($("#blogAddCommentNotification").is(':checked') ) { + $("#blogAddCommentGroupNotification").slideDown(); + } else { + $("#blogAddCommentGroupNotification").slideUp(); + } }); \ No newline at end of file diff --git a/module/blog/view/add/add.php b/module/blog/view/add/add.php index 37230abd..f3ec7731 100644 --- a/module/blog/view/add/add.php +++ b/module/blog/view/add/add.php @@ -56,9 +56,7 @@
- $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'hidePicture']) - ]); ?> +
@@ -68,31 +66,62 @@ 'class' => 'editorWysiwyg' ]); ?>
-
+

Options de publication

- 'Auteur', - 'selected' => $this->getUser('id') - ]); ?> - 'L\'article n\'est visible qu\'après la date de publication prévue.', - 'label' => 'Date de publication', - 'value' => time() - ]); ?> -
-
-
-
-

Options avancées

- - 'Editeurs = éditeurs + administrateurs
Membres = membres + éditeurs + administrateurs' - ]); ?> - '' - ]); ?> +
+
+ 'Auteur', + 'selected' => $this->getUser('id'), + 'disabled' => $this->getUser('group') !== self::GROUP_ADMIN ? true : false + ]); ?> +
+
+ 'L\'article n\'est visible qu\'après la date de publication prévue.', + 'label' => 'Date de publication', + 'value' => time() + ]); ?> +
+
+ 'Edition / Suppression', + 'selected' => $module::EDIT_ALL, + 'help' => 'Les utilisateurs des groupes supérieurs accèdent à l\'article sans restriction' + ]); ?> +
+
- \ No newline at end of file +
+
+
+

Commentaires

+
+
+ +
+
+ +
+
+ 'Choix du nombre maximum de caractères pour chaque commentaire de l\'article, mise en forme html comprise.', + 'label' => 'Caractères par commentaire' + ]); ?> +
+
+
+
+ +
+
+ +
+
+
+
+
+ diff --git a/module/blog/view/article/article.css b/module/blog/view/article/article.css index e75691a1..7828afc9 100755 --- a/module/blog/view/article/article.css +++ b/module/blog/view/article/article.css @@ -50,6 +50,4 @@ .blogArticlePicture { height:auto; max-width: 100%;} - } - - +} diff --git a/module/blog/view/article/article.php b/module/blog/view/article/article.php index fda79bf6..527163ef 100644 --- a/module/blog/view/article/article.php +++ b/module/blog/view/article/article.php @@ -13,17 +13,35 @@ ?>
- getUser('group') >= self::GROUP_ADMIN - AND $this->getUser('password') === $this->getInput('ZWII_USER_PASSWORD') - ): ?>
- helper::baseUrl() . $this->getUrl(0) . '/edit/' . $this->getUrl(1) . '/' . $_SESSION['csrf'], - 'value' => 'Editer' - ]); ?> -
- + getUser('password') === $this->getInput('ZWII_USER_PASSWORD') + AND + ( // Propriétaire + ( + $this->getData(['module', $this->getUrl(0), $this->getUrl(1),'editConsent']) === $module::EDIT_OWNER + AND ( $this->getData(['module', $this->getUrl(0), $this->getUrl(1),'userId']) === $this->getUser('id') + OR $this->getUser('group') === self::GROUP_ADMIN ) + ) + OR ( + // Groupe + ( $this->getData(['module', $this->getUrl(0), $this->getUrl(1),'editConsent']) === self::GROUP_ADMIN + OR $this->getData(['module', $this->getUrl(0), $this->getUrl(1),'editConsent']) === self::GROUP_MODERATOR) + AND $this->getUser('group') >= $this->getData(['module',$this->getUrl(0), $this->getUrl(1),'editConsent']) + ) + OR ( + // Tout le monde + $this->getData(['module', $this->getUrl(0), $this->getUrl(1),'editConsent']) === $module::EDIT_ALL + AND $this->getUser('group') >= $module::$actions['config'] + ) + ) + ): ?> + helper::baseUrl() . $this->getUrl(0) . '/edit/' . $this->getUrl(1) . '/' . $_SESSION['csrf'], + 'value' => 'Editer' + ]); ?> + + getData(['module', $this->getUrl(0), $this->getUrl(1), 'pictureSize']) === null ? '100' : $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'pictureSize']); ?> getData(['module', $this->getUrl(0), $this->getUrl(1), 'hidePicture']) == false) { @@ -31,17 +49,14 @@ ' pict' . $pictureSize . '" src="' . helper::baseUrl(false) . self::FILE_DIR.'source/' . $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'picture']) . '" alt="' . $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'picture']) . '">'; } ?> - getData(['module', $this->getUrl(0), $this->getUrl(1), 'content']); ?> -

- getData(['user', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'userId']), 'firstname']); ?> - getData(['user', $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'userId']), 'lastname']); ?> -

- getData(['module', $this->getUrl(0), $this->getUrl(1), 'closeComment'])): ?> +

+ getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentClose'])): ?>

Cet article ne reçoit pas de commentaire.

- getData(['module', $this->getUrl(0), $this->getUrl(1), 'comment'])); ?> + + 0 ? $commentsNb . ' ' . 'commentaire' . $s : 'Pas encore de commentaire'; ?>

@@ -51,11 +66,11 @@ 'readonly' => true ]); ?>
- getUser('password') === $this->getInput('ZWII_USER_PASSWORD')): ?> + getUser('password') === $this->getInput('ZWII_USER_PASSWORD')): ?> 'Nom', 'readonly' => true, - 'value' => $this->getUser('firstname') . ' ' . $this->getUser('lastname') + 'value' => $module::$editCommentSignature ]); ?> $this->getUser('id') @@ -79,9 +94,12 @@
'Commentaire', - 'maxlength' => '500' + 'label' => 'Commentaire avec maximum '.$this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentMaxlength']).' caractères', + 'class' => 'editorWysiwygComment', + 'noDirty' => true, + 'maxlength' => $this->getData(['module', $this->getUrl(0), $this->getUrl(1), 'commentMaxlength']) ]); ?> +
getUser('password') !== $this->getInput('ZWII_USER_PASSWORD')): ?>
@@ -113,17 +131,11 @@
$comment): ?>
-

- - getData(['user', $comment['userId'], 'firstname']) . ' ' . $this->getData(['user', $comment['userId'], 'lastname']); ?> - - - - le + le -

+ ?>
diff --git a/module/blog/view/comment/comment.js.php b/module/blog/view/comment/comment.js.php index fa8ec3e3..fc6f7aaa 100644 --- a/module/blog/view/comment/comment.js.php +++ b/module/blog/view/comment/comment.js.php @@ -10,12 +10,53 @@ * @link http://zwiicms.fr/ */ + /** * Confirmation de suppression */ $(".blogCommentDelete").on("click", function() { var _this = $(this); - return core.confirm("Êtes-vous sûr de vouloir supprimer ce commentaire ?", function() { + var nom = "getData(['module', $this->getUrl(0), $this->getUrl(2), 'title' ]); ?>"; + return core.confirm("Supprimer le commentaire de l'article " + nom + " ?", function() { $(location).attr("href", _this.attr("href")); }); -}); \ No newline at end of file +}); + +/** + * Confirmation d'approbation + */ +$(".blogCommentApproved").on("click", function() { + var _this = $(this); + var nom = "getData(['module', $this->getUrl(0), $this->getUrl(2), 'title' ]); ?>"; + return core.confirm("Approuver le commentaire de l'article " + nom + " ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); + +/** + * Confirmation de rejet + */ +$(".blogCommentRejected").on("click", function() { + var _this = $(this); + var nom = "getData(['module', $this->getUrl(0), $this->getUrl(2), 'title' ]); ?>"; + return core.confirm("Rejeter le commentaire de l'article " + nom + " ?", function() { + $(location).attr("href", _this.attr("href")); + }); +}); + +/** + * Confirmation de suppression en masse + */ +$(".blogCommentDeleteAll").on("click", function() { + var _this = $(this); + var nombre = "getData(['module', $this->getUrl(0), $this->getUrl(2), 'comment' ])); ?>"; + var nom = "getData(['module', $this->getUrl(0), $this->getUrl(2), 'title' ]); ?>"; + if( nombre === "1"){ + var message = "Supprimer le commentaire de l'article " + nom + " ?"; + } else{ + var message = "Supprimer les " + nombre + " commentaires de l'article " + nom + " ?"; + } + return core.confirm(message, function() { + $(location).attr("href", _this.attr("href")); + }); +}); diff --git a/module/blog/view/comment/comment.php b/module/blog/view/comment/comment.php index d6b58853..08d5e9ee 100644 --- a/module/blog/view/comment/comment.php +++ b/module/blog/view/comment/comment.php @@ -7,10 +7,16 @@ 'value' => 'Retour' ]); ?>
-
+ - - +
+ +
+ +
+ + '; ?> + - \ No newline at end of file + diff --git a/module/blog/view/config/config.php b/module/blog/view/config/config.php index a8d472dd..d1e22ec0 100644 --- a/module/blog/view/config/config.php +++ b/module/blog/view/config/config.php @@ -7,13 +7,7 @@ 'value' => 'Retour' ]); ?> -
- helper::baseUrl() . $this->getUrl(0) . '/comment', - 'value' => 'Gérer les commentaires' - ]); ?> -
-
+
helper::baseUrl() . $this->getUrl(0) . '/add', 'ico' => 'plus', @@ -22,11 +16,11 @@
- +
Version n° -
\ No newline at end of file + diff --git a/module/blog/view/edit/edit.js.php b/module/blog/view/edit/edit.js.php index 8daaa07a..96e2872a 100644 --- a/module/blog/view/edit/edit.js.php +++ b/module/blog/view/edit/edit.js.php @@ -28,4 +28,39 @@ $("#blogEditMailNotification").on("change", function() { $("#blogEditDraft").on("click", function() { $("#blogEditState").val(0); $("#blogEditForm").trigger("submit"); +}); + +/** + * Options de commentaires + */ +$("#blogEditCommentClose").on("change", function() { + if ($(this).is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } +}); + +$("#blogEditCommentNotification").on("change", function() { + if ($(this).is(':checked') ) { + $("#blogEditCommentGroupNotification").slideDown(); + } else { + $("#blogEditCommentGroupNotification").slideUp(); + } +}); + + +$( document).ready(function() { + + if ($("#blogEditCloseComment").is(':checked') ) { + $(".commentOptionsWrapper").slideUp(); + } else { + $(".commentOptionsWrapper").slideDown(); + } + + if ($("#blogEditCommentNotification").is(':checked') ) { + $("#blogEditCommentGroupNotification").slideDown(); + } else { + $("#blogEditCommentGroupNotification").slideUp(); + } }); \ No newline at end of file diff --git a/module/blog/view/edit/edit.php b/module/blog/view/edit/edit.php index e964ef93..1b8c37a6 100644 --- a/module/blog/view/edit/edit.php +++ b/module/blog/view/edit/edit.php @@ -21,6 +21,7 @@ 'Publier' ]); ?> +
@@ -73,36 +74,74 @@ 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'content']) ]); ?>
-
+

Options de publication

- 'Auteur', - 'selected' => $this->getUser('id') - ]); ?> - 'L\'article n\'est visible qu\'après la date de publication prévue.', - 'label' => 'Date de publication', - 'value' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'publishedOn']) - ]); ?> -
-
-
-
-

Options avancées

- $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'closeComment']) - ]); ?> - $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'mailNotification']), - 'help' => 'Editeurs = éditeurs + administrateurs
Membres = membres + éditeurs + administrateurs' - - ]); ?> - '', - 'selected' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'groupNotification']) - ]); ?> +
+
+ 'Auteur', + 'selected' => $this->getUser('id'), + 'disabled' => $this->getUser('group') !== self::GROUP_ADMIN ? true : false + ]); ?> +
+
+ 'L\'article n\'est visible qu\'après la date de publication prévue.', + 'label' => 'Date de publication', + 'value' => time() + ]); ?> +
+
+ 'Edition / Suppression', + 'selected' => is_numeric($this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'editConsent'])) ? $module::EDIT_GROUP : $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'editConsent']), + 'help' => 'Les utilisateurs des groupes supérieurs accèdent à l\'article sans restriction' + ]); ?> +
+
- \ No newline at end of file +
+
+
+

Commentaires

+
+
+ $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'commentClose']) + ]); ?> +
+
+ $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'commentApproved']), + '' + ]); ?> +
+
+ 'Choix du nombre maximum de caractères pour chaque commentaire de l\'article, mise en forme html comprise.', + 'label' => 'Caractères par commentaire', + 'selected' => $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'commentMaxlength']) + ]); ?> +
+ +
+
+
+ $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'commentNotification']), + ]); ?> +
+
+ $this->getData(['module', $this->getUrl(0), $this->getUrl(2), 'commentGroupNotification']), + 'help' => 'Editeurs = éditeurs + administrateurs
Membres = membres + éditeurs + administrateurs' + ]); ?> +
+
+
+
+
+ diff --git a/module/blog/view/index/index.php b/module/blog/view/index/index.php index 271e2dba..b719040c 100644 --- a/module/blog/view/index/index.php +++ b/module/blog/view/index/index.php @@ -21,6 +21,7 @@
+
diff --git a/module/form/view/index/index.php b/module/form/view/index/index.php index 1a10764c..32dddf28 100644 --- a/module/form/view/index/index.php +++ b/module/form/view/index/index.php @@ -45,7 +45,7 @@ getData(['module', $this->getUrl(0), 'config', 'captcha'])): ?>
-
+
$this->getData(['config','captchaStrong']) ]); ?>