<?php /** * This file is part of Zwii. * * For full copyright and license information, please see the LICENSE * file that was distributed with this source code. * * @author Rémi Jean <remi.jean@outlook.com> * @copyright Copyright (C) 2008-2018, Rémi Jean * @license CC Attribution-NonCommercial-NoDerivatives 4.0 International * @link http://zwiicms.fr/ * @copyright : Frédéric Tempez <frederic.tempez@outlook.com> * @copyright Copyright (C) 2018-2022, Frédéric Tempez */ class theme extends common { public static $actions = [ 'advanced' => self::GROUP_ADMIN, 'body' => self::GROUP_ADMIN, 'footer' => self::GROUP_ADMIN, 'header' => self::GROUP_ADMIN, 'index' => self::GROUP_ADMIN, 'menu' => self::GROUP_ADMIN, 'reset' => self::GROUP_ADMIN, 'site' => self::GROUP_ADMIN, 'admin' => self::GROUP_ADMIN, 'manage' => self::GROUP_ADMIN, 'export' => self::GROUP_ADMIN, 'import' => self::GROUP_ADMIN, 'save' => self::GROUP_ADMIN, 'fonts' => self::GROUP_ADMIN, 'fontAdd' => self::GROUP_ADMIN, 'fontEdit' => self::GROUP_ADMIN, 'fontDelete' => self::GROUP_ADMIN ]; public static $aligns = [ 'left' => 'À gauche', 'center' => 'Au centre', 'right' => 'À droite' ]; public static $attachments = [ 'scroll' => 'Standard', 'fixed' => 'Fixe' ]; public static $containerWides = [ 'container' => 'Limitée au site', 'none' => 'Etendue sur la page' ]; public static $footerblocks = [ 1 => [ 'hide' => 'Masqué', 'center' => 'Affiché' ], 2 => [ 'hide' => 'Masqué', 'left' => 'À gauche', 'right' => 'À droite' ], 3 => [ 'hide' => 'Masqué', 'left' => 'À gauche', 'center' => 'Au centre', 'right' => 'À droite' ], 4 => [ 'hide' => 'Masqué', 'left' => 'En haut', 'center' => 'Au milieu', 'right' => 'En bas' ] ]; public static $fontWeights = [ 'normal' => 'Maigre', 'bold' => 'Gras' ]; public static $footerHeights = [ '0px' => 'Nulles (0px)', '5px' => 'Très petites (5px)', '10px' => 'Petites (10px)', '15px' => 'Moyennes (15px)', '20px' => 'Grandes (20px)' ]; public static $footerPositions = [ 'hide' => 'Caché', 'site' => 'Dans le site', 'body' => 'En dessous du site' ]; public static $footerFontSizes = [ '.8em' => 'Très petite (80%)', '.9em' => 'Petite (90%)', '1em' => 'Standard (100%)', '1.1em' => 'Moyenne (110%)', '1.2em' => 'Grande (120%)', '1.3em' => 'Très grande (130%)' ]; public static $headerFontSizes = [ '1.6em' => 'Très petite (160%)', '1.8em' => 'Petite (180%)', '2em' => 'Moyenne (200%)', '2.2em' => 'Grande (220%)', '2.4vmax' => 'Très grande (240%)' ]; public static $headerHeights = [ 'unset' => 'Libre', // texte dynamique cf header.js.php '100px' => 'Très petite (100px) ', '150px' => 'Petite (150px)', '200px' => 'Moyenne (200px)', '300px' => 'Grande (300px)', '400px' => 'Très grande (400px)', ]; public static $headerPositions = [ 'body' => 'Au dessus du site', 'site' => 'Dans le site', 'hide' => 'Cachée' ]; public static $headerFeatures = [ 'wallpaper' => 'Couleur unie ou papier-peint', 'feature' => 'Contenu personnalisé' ]; public static $imagePositions = [ 'top left' => 'En haut à gauche', 'top center' => 'En haut au centre', 'top right' => 'En haut à droite', 'center left' => 'Au milieu à gauche', 'center center' => 'Au milieu au centre', 'center right' => 'Au milieu à droite', 'bottom left' => 'En bas à gauche', 'bottom center' => 'En bas au centre', 'bottom right' => 'En bas à droite' ]; public static $menuFontSizes = [ '.8em' => 'Très petite (80%)', '.9em' => 'Petite (90%)', '1em' => 'Standard (100%)', '1.1em' => 'Moyenne (110%)', '1.2em' => 'Grande (120%)', '1.3em' => 'Très grande (130%)' ]; public static $menuHeights = [ '5px 10px' => 'Très petite', '10px' => 'Petite', '15px 10px' => 'Moyenne', '20px 15px' => 'Grande', '25px 15px' => 'Très grande' ]; public static $menuPositionsSite = [ 'top' => 'En-dehors du site', 'site-first' => 'Avant la bannière', 'site-second' => 'Après la bannière', 'hide' => 'Caché' ]; public static $menuPositionsBody = [ 'top' => 'En-dehors du site', 'body-first' => 'Avant la bannière', 'body-second' => 'Après la bannière', 'site' => 'Dans le site', 'hide' => 'Caché' ]; public static $menuRadius = [ '0px' => 'Aucun', '3px 3px 0px 0px' => 'Très léger', '6px 6px 0px 0px' => 'Léger', '9px 9px 0px 0px' => 'Moyen', '12px 12px 0px 0px' => 'Important', '15px 15px 0px 0px' => 'Très important' ]; public static $radius = [ '0px' => 'Aucun', '5px' => 'Très léger', '10px' => 'Léger', '15px' => 'Moyen', '25px' => 'Important', '50px' => 'Très important' ]; public static $repeats = [ 'no-repeat' => 'Ne pas répéter', 'repeat-x' => 'Sur l\'axe horizontal', 'repeat-y' => 'Sur l\'axe vertical', 'repeat' => 'Sur les deux axes' ]; public static $shadows = [ '0px' => 'Aucune', '1px 1px 5px' => 'Très légère', '1px 1px 10px' => 'Légère', '1px 1px 15px' => 'Moyenne', '1px 1px 25px' => 'Importante', '1px 1px 50px' => 'Très importante' ]; public static $siteFontSizes = [ '12px' => '12 pixels', '13px' => '13 pixels', '14px' => '14 pixels', '15px' => '15 pixels', '16px' => '16 pixels' ]; public static $bodySizes = [ 'auto' => 'Automatique', '100% 100%' => 'Image étirée (100% 100%)', 'cover' => 'Responsive (cover)', 'contain' => 'Responsive (contain)' ]; public static $textTransforms = [ 'none' => 'Standard', 'lowercase' => 'Minuscules', 'uppercase' => 'Majuscules', 'capitalize' => 'Majuscule à chaque mot' ]; public static $siteWidths = [ '750px' => 'Petite (750 pixels)', '960px' => 'Moyenne (960 pixels)', '1170px' => 'Grande (1170 pixels)', '100%' => 'Fluide (100%)' ]; public static $headerWide = [ 'auto auto' => 'Automatique', '100% 100%' => 'Image étirée (100% 100%)', 'cover' => 'Responsive (cover)', 'contain' => 'Responsive (contain)' ]; public static $footerTemplate = [ '1' => 'Une seule colonne', '2' => 'Deux colonnes : 1/2 - 1/2', '3' => 'Trois colonnes : 1/3 - 1/3 - 1/3', '4' => 'Trois lignes superposées' ]; public static $burgerContent = [ 'none' => 'Aucun', 'title' => 'Titre', 'logo' => 'Logo du site' ]; // Variable pour construire la liste des pages du site public static $pagesList = []; // Variable pour construire la liste des fontes installées public static $fontsNames = []; public static $fonts = []; // Variable pour détailler les fontes installées public static $fontsDetail = []; /** * Thème des écrans d'administration */ public function admin() { // Soumission du formulaire if ($this->isPost()) { $this->setData(['admin', [ 'backgroundColor' => $this->getInput('adminBackgroundColor'), 'colorTitle' => $this->getInput('adminColorTitle'), 'colorText' => $this->getInput('adminColorText'), 'colorButtonText' => $this->getInput('adminColorButtonText'), 'backgroundColorButton' => $this->getInput('adminColorButton'), 'backgroundColorButtonGrey' => $this->getInput('adminColorGrey'), 'backgroundColorButtonRed' => $this->getInput('adminColorRed'), 'backgroundColorButtonGreen' => $this->getInput('adminColorGreen'), 'backgroundColorButtonHelp' => $this->getInput('adminColorHelp'), 'fontText' => $this->getInput('adminFontText'), 'fontSize' => $this->getInput('adminFontTextSize'), 'fontTitle' => $this->getInput('adminFontTitle'), 'backgroundBlockColor' => $this->getInput('adminBackGroundBlockColor'), 'borderBlockColor' => $this->getInput('adminBorderBlockColor'), ]]); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme/admin', 'state' => true ]); } // Lire les fontes installées $this->enumFonts(); // Toutes les fontes installées sont chargées $this->setFonts('all'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Thème de l\'administration'), 'view' => 'admin', 'vendor' => [ 'tinycolorpicker' ], ]); } /** * Mode avancé */ public function advanced() { // Soumission du formulaire if ($this->isPost()) { // Enregistre le CSS file_put_contents(self::DATA_DIR . 'custom.css', $this->getInput('themeAdvancedCss', null)); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme/advanced', 'state' => true ]); } // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Éditeur CSS'), 'vendor' => [ 'codemirror' ], 'view' => 'advanced' ]); } /** * Options de l'arrière plan */ public function body() { // Soumission du formulaire if ($this->isPost()) { $this->setData(['theme', 'body', [ 'backgroundColor' => $this->getInput('themeBodyBackgroundColor'), 'image' => $this->getInput('themeBodyImage'), 'imageAttachment' => $this->getInput('themeBodyImageAttachment'), 'imagePosition' => $this->getInput('themeBodyImagePosition'), 'imageRepeat' => $this->getInput('themeBodyImageRepeat'), 'imageSize' => $this->getInput('themeBodyImageSize'), 'toTopbackgroundColor' => $this->getInput('themeBodyToTopBackground'), 'toTopColor' => $this->getInput('themeBodyToTopColor') ]]); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme', 'state' => true ]); } // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Arrière plan'), 'vendor' => [ 'tinycolorpicker' ], 'view' => 'body' ]); } /** * Options du pied de page */ public function footer() { // Soumission du formulaire if ($this->isPost()) { if ( $this->getInput('themeFooterCopyrightPosition') === 'hide' && $this->getInput('themeFooterSocialsPosition') === 'hide' && $this->getInput('themeFooterTextPosition') === 'hide' ) { // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Sélectionnez au moins un contenu à afficher'), 'redirect' => helper::baseUrl() . 'theme/footer', 'state' => false ]); } else { $this->setData(['theme', 'footer', [ 'backgroundColor' => $this->getInput('themeFooterBackgroundColor'), 'copyrightAlign' => $this->getInput('themeFooterCopyrightAlign'), 'height' => $this->getInput('themeFooterHeight'), 'loginLink' => $this->getInput('themeFooterLoginLink'), 'margin' => $this->getInput('themeFooterMargin', helper::FILTER_BOOLEAN), 'position' => $this->getInput('themeFooterPosition'), 'fixed' => $this->getInput('themeFooterFixed', helper::FILTER_BOOLEAN), 'socialsAlign' => $this->getInput('themeFooterSocialsAlign'), 'text' => $this->getInput('themeFooterText', null), 'textAlign' => $this->getInput('themeFooterTextAlign'), 'textColor' => $this->getInput('themeFooterTextColor'), 'copyrightPosition' => $this->getInput('themeFooterCopyrightPosition'), 'textPosition' => $this->getInput('themeFooterTextPosition'), 'socialsPosition' => $this->getInput('themeFooterSocialsPosition'), 'textTransform' => $this->getInput('themeFooterTextTransform'), 'font' => $this->getInput('themeFooterFont'), 'fontSize' => $this->getInput('themeFooterFontSize'), 'fontWeight' => $this->getInput('themeFooterFontWeight'), 'displayVersion' => $this->getInput('themefooterDisplayVersion', helper::FILTER_BOOLEAN), 'displaySiteMap' => $this->getInput('themefooterDisplaySiteMap', helper::FILTER_BOOLEAN), 'displayCopyright' => $this->getInput('themefooterDisplayCopyright', helper::FILTER_BOOLEAN), 'displayCookie' => $this->getInput('themefooterDisplayCookie', helper::FILTER_BOOLEAN), 'displayLegal' => $this->getInput('themeFooterDisplayLegal', helper::FILTER_BOOLEAN), 'displaySearch' => $this->getInput('themeFooterDisplaySearch', helper::FILTER_BOOLEAN), 'memberBar' => $this->getInput('themeFooterMemberBar', helper::FILTER_BOOLEAN), 'template' => $this->getInput('themeFooterTemplate') ]]); // Sauvegarder la configuration localisée $this->setData(['locale', 'legalPageId', $this->getInput('configLegalPageId')]); $this->setData(['locale', 'searchPageId', $this->getInput('configSearchPageId')]); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme', 'state' => true ]); } } // Liste des pages self::$pagesList = $this->getData(['page']); foreach (self::$pagesList as $page => $pageId) { if ( $this->getData(['page', $page, 'block']) === 'bar' || $this->getData(['page', $page, 'disable']) === true ) { unset(self::$pagesList[$page]); } } // Lire les fontes installées $this->enumFonts(); // Toutes les fontes installées sont chargées $this->setFonts('all'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Pied de page'), 'vendor' => [ 'tinycolorpicker', 'tinymce' ], 'view' => 'footer' ]); } /** * Options de la bannière */ public function header() { // Soumission du formulaire if ($this->isPost()) { // Modification des URL des images dans la bannière perso $featureContent = $this->getInput('themeHeaderText', null); /** * Stocker les images incluses dans la bannière perso dans un tableau */ $files = []; preg_match_all('/<img[^>]+>/i', $featureContent, $results); foreach ($results[0] as $value) { // Lire le contenu XML $sx = simplexml_load_string($value); // Élément à remplacer $files[] = str_replace('./site/file/source/', '', (string) $sx[0]['src']); } // Sauvegarder $this->setData(['theme', 'header', [ 'backgroundColor' => $this->getInput('themeHeaderBackgroundColor'), 'font' => $this->getInput('themeHeaderFont'), 'fontSize' => $this->getInput('themeHeaderFontSize'), 'fontWeight' => $this->getInput('themeHeaderFontWeight'), 'height' => $this->getInput('themeHeaderHeight'), 'wide' => $this->getInput('themeHeaderWide'), 'image' => $this->getInput('themeHeaderImage'), 'imagePosition' => $this->getInput('themeHeaderImagePosition'), 'imageRepeat' => $this->getInput('themeHeaderImageRepeat'), 'margin' => $this->getInput('themeHeaderMargin', helper::FILTER_BOOLEAN), 'position' => $this->getInput('themeHeaderPosition'), 'textAlign' => $this->getInput('themeHeaderTextAlign'), 'textColor' => $this->getInput('themeHeaderTextColor'), 'textHide' => $this->getInput('themeHeaderTextHide', helper::FILTER_BOOLEAN), 'textTransform' => $this->getInput('themeHeaderTextTransform'), 'linkHomePage' => $this->getInput('themeHeaderlinkHomePage', helper::FILTER_BOOLEAN), 'imageContainer' => $this->getInput('themeHeaderImageContainer'), 'tinyHidden' => $this->getInput('themeHeaderTinyHidden', helper::FILTER_BOOLEAN), 'feature' => $this->getInput('themeHeaderFeature'), 'featureContent' => $featureContent, 'featureFiles' => $files ]]); // Modification de la position du menu selon la position de la bannière if ($this->getData(['theme', 'header', 'position']) == 'site') { $this->setData(['theme', 'menu', 'position', str_replace('body-', 'site-', $this->getData(['theme', 'menu', 'position']))]); } if ($this->getData(['theme', 'header', 'position']) == 'body') { $this->setData(['theme', 'menu', 'position', str_replace('site-', 'body-', $this->getData(['theme', 'menu', 'position']))]); } // Menu accroché à la bannière qui devient cachée if ( $this->getData(['theme', 'header', 'position']) == 'hide' && in_array($this->getData(['theme', 'menu', 'position']), ['body-first', 'site-first', 'body-first', 'site-second']) ) { $this->setData(['theme', 'menu', 'position', 'site']); } // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme', 'state' => true ]); } // Lire les fontes installées $this->enumFonts(); // Toutes les fontes installées sont chargées $this->setFonts('all'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Bannière'), 'vendor' => [ 'tinycolorpicker', 'tinymce' ], 'view' => 'header' ]); } /** * Accueil de la personnalisation */ public function index() { // Restaurer les fontes utilisateurs $this->setFonts('user'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Thèmes'), 'view' => 'index' ]); } /** * Options du menu */ public function menu() { // Soumission du formulaire if ($this->isPost()) { $this->setData(['theme', 'menu', [ 'backgroundColor' => $this->getInput('themeMenuBackgroundColor'), 'backgroundColorSub' => $this->getInput('themeMenuBackgroundColorSub'), 'font' => $this->getInput('themeMenuFont'), 'fontSize' => $this->getInput('themeMenuFontSize'), 'fontWeight' => $this->getInput('themeMenuFontWeight'), 'height' => $this->getInput('themeMenuHeight'), 'wide' => $this->getInput('themeMenuWide'), 'loginLink' => $this->getInput('themeMenuLoginLink', helper::FILTER_BOOLEAN), 'margin' => $this->getInput('themeMenuMargin', helper::FILTER_BOOLEAN), 'position' => $this->getInput('themeMenuPosition'), 'textAlign' => $this->getInput('themeMenuTextAlign'), 'textColor' => $this->getInput('themeMenuTextColor'), 'textTransform' => $this->getInput('themeMenuTextTransform'), 'fixed' => $this->getInput('themeMenuFixed', helper::FILTER_BOOLEAN), 'activeColorAuto' => $this->getInput('themeMenuActiveColorAuto', helper::FILTER_BOOLEAN), 'activeColor' => $this->getInput('themeMenuActiveColor'), 'activeTextColor' => $this->getInput('themeMenuActiveTextColor'), 'radius' => $this->getInput('themeMenuRadius'), 'burgerTitle' => $this->getInput('themeMenuBurgerTitle', helper::FILTER_BOOLEAN), 'memberBar' => $this->getInput('themeMenuMemberBar', helper::FILTER_BOOLEAN), 'burgerLogo' => $this->getInput('themeMenuBurgerLogo'), 'burgerContent' => $this->getInput('themeMenuBurgerContent') ]]); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme', 'state' => true ]); } // Lire les fontes installées $this->enumFonts(); // Toutes les fontes installées sont chargées $this->setFonts('all'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Menu'), 'vendor' => [ 'tinycolorpicker' ], 'view' => 'menu' ]); } /** * Options des fontes */ public function fonts() { // Toutes les fontes installées sont chargées $this->setFonts('all'); // Polices liées au thème $used = [ 'Bannière' => $this->getData(['theme', 'header', 'font']), 'Menu' => $this->getData(['theme', 'menu', 'font']), 'Titre ' => $this->getData(['theme', 'title', 'font']), 'Texte' => $this->getData(['theme', 'text', 'font']), 'Pied de page' => $this->getData(['theme', 'footer', 'font']), 'Titre (admin)' => $this->getData(['admin', 'fontTitle']), 'Admin (texte)' => $this->getData(['admin', 'fontText']) ]; // Récupérer le détail des fontes installées //$f = $this->getFonts(); $f['files'] = $this->getData(['fonts', 'files']); $f['imported'] = $this->getData(['fonts', 'imported']); $f['websafe'] = self::$fontsWebSafe; // Parcourir les fontes disponibles et construire le tableau pour le formulaire foreach ($f as $type => $typeValue) { if (is_array($typeValue)) { foreach ($typeValue as $fontId => $fontValue) { // Fontes utilisées par les thèmes $fontUsed[$fontId] = ''; foreach ($used as $key => $value) { if ($value === $fontId) { $fontUsed[$fontId] .= $key . '<br/>'; } } self::$fontsDetail[] = [ $fontId, '<span style="font-family:' . $f[$type][$fontId]['font-family'] . '">' . $f[$type][$fontId]['name'] . '</span>', $f[$type][$fontId]['font-family'], $fontUsed[$fontId], $type, $type !== 'websafe' ? template::button('themeFontEdit' . $fontId, [ 'class' => 'themeFontEdit', 'href' => helper::baseUrl() . $this->getUrl(0) . '/fontEdit/' . $type . '/' . $fontId . '/' . $_SESSION['csrf'], 'value' => template::ico('pencil'), 'disabled' => !empty($fontUsed[$fontId]) ]) : '', $type !== 'websafe' ? template::button('themeFontDelete' . $fontId, [ 'class' => 'themeFontDelete buttonRed', 'href' => helper::baseUrl() . $this->getUrl(0) . '/fontDelete/' . $type . '/' . $fontId . '/' . $_SESSION['csrf'], 'value' => template::ico('cancel'), 'disabled' => !empty($fontUsed[$fontId]) ]) : '' ]; } } } sort(self::$fontsDetail); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Fontes'), 'view' => 'fonts' ]); } /** * Ajouter une fonte */ public function fontAdd() { // Soumission du formulaire if ($this->isPost()) { // Type d'import en ligne ou local $type = $this->getInput('fontAddFontImported', helper::FILTER_BOOLEAN) ? 'imported' : 'files'; $typeFlip = $type === 'files' ? 'imported' : 'files'; $ressource = $type === 'imported' ? $this->getInput('fontAddUrl', null) : $this->getInput('fontAddFile', null); if (!empty($ressource)) { $fontId = $this->getInput('fontAddFontId', null, true); $fontName = $this->getInput('fontAddFontName', null, true); $fontFamilyName = $this->getInput('fontAddFontFamilyName', null, true); // Remplace les doubles quotes par des simples quotes $fontFamilyName = str_replace('"', '\'', $fontFamilyName); // Supprime la fonte si elle existe dans le type inverse if (is_array($this->getData(['fonts', $typeFlip, $fontId]))) { $this->deleteData(['fonts', $typeFlip, $fontId]); } // Stocker la fonte $this->setData([ 'fonts', $type, $fontId, [ 'name' => $fontName, 'font-family' => $fontFamilyName, 'resource' => $ressource ] ]); // Copier la fonte si le nom du fichier est fourni if ( $type === 'files' && file_exists(self::FILE_DIR . 'source/' . $ressource) ) { copy(self::FILE_DIR . 'source/' . $ressource, self::DATA_DIR . 'fonts/' . $ressource); } // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Fonte créée'), 'redirect' => helper::baseUrl() . 'theme/fonts', 'state' => true ]); } else { // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Fonte non créée, ressource absente !'), 'redirect' => helper::baseUrl() . 'theme/fontAdd', 'state' => false ]); } } // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Ajouter une fonte'), 'view' => 'fontAdd' ]); } /** * Ajouter une fonte */ public function fontEdit() { // Soumission du formulaire if ($this->isPost()) { // Type d'import en ligne ou local $type = $this->getInput('fontEditFontImported', helper::FILTER_BOOLEAN) ? 'imported' : 'files'; $typeFlip = $type === 'files' ? 'imported' : 'files'; $ressource = $type === 'imported' ? $this->getInput('fontEditUrl', null) : $this->getInput('fontEditFile', null); $fontId = $this->getInput('fontEditFontId', null, true); $fontName = $this->getInput('fontEditFontName', null, true); $fontFamilyName = $this->getInput('fontEditFontFamilyName', null, true); // Remplace les doubles quotes par des simples quotes $fontFamilyName = str_replace('"', '\'', $fontFamilyName); // Supprime la fonte si elle existe dans le type inverse if (is_array($this->getData(['fonts', $typeFlip, $fontId]))) { $this->deleteData(['fonts', $typeFlip, $fontId]); } // Stocker les fontes $this->setData([ 'fonts', $type, $fontId, [ 'name' => $fontName, 'font-family' => $fontFamilyName, 'resource' => $ressource ] ]); // Copier la fonte si le nom du fichier est fourni if ( $type === 'files' && file_exists(self::FILE_DIR . 'source/' . $ressource) ) { copy(self::FILE_DIR . 'source/' . $ressource, self::DATA_DIR . 'fonts/' . $ressource); } // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Fonte actualisée'), 'redirect' => helper::baseUrl() . 'theme/fonts', 'state' => true ]); } // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Éditer une fonte'), 'view' => 'fontEdit' ]); } /** * Effacer une fonte */ public function fontDelete() { // Jeton incorrect if ($this->getUrl(4) !== $_SESSION['csrf']) { // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . 'theme/fonts', 'notification' => helper::translate('Action interdite') ]); } // Suppression else { // Effacer la fonte de la base $this->deleteData(['fonts', $this->getUrl(2), $this->getUrl(3)]); // Effacer le fichier existant if ( $this->getUrl(2) === 'file' && file_exists(self::DATA_DIR . $this->getUrl(2)) ) { unlink(self::DATA_DIR . $this->getUrl(2)); } // Valeurs en sortie $this->addOutput([ 'redirect' => helper::baseUrl() . 'theme/fonts', 'notification' => helper::translate('Fonte supprimée'), 'state' => true ]); } } /** * Réinitialisation de la personnalisation avancée */ public function reset() { // $url prend l'adresse sans le token $url = explode('&', $this->getUrl(2)); if ( isset($_GET['csrf']) and $_GET['csrf'] === $_SESSION['csrf'] ) { // Réinitialisation $redirect = ''; switch ($url[0]) { case 'admin': $this->initData('admin', self::$i18nUI); $redirect = helper::baseUrl() . 'theme/admin'; break; case 'manage': $this->initData('theme', self::$i18nUI); $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' => helper::translate('Thème réinitialisé'), 'redirect' => $redirect, 'state' => true ]); } else { // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Jeton incorrect') ]); } } /** * Options du site */ public function site() { // Soumission du formulaire if ($this->isPost()) { $this->setData(['theme', 'title', [ 'font' => $this->getInput('themeTitleFont'), 'textColor' => $this->getInput('themeTitleTextColor'), 'fontWeight' => $this->getInput('themeTitleFontWeight'), 'textTransform' => $this->getInput('themeTitleTextTransform') ]]); $this->setData(['theme', 'text', [ 'font' => $this->getInput('themeTextFont'), 'fontSize' => $this->getInput('themeTextFontSize'), 'textColor' => $this->getInput('themeTextTextColor'), 'linkColor' => $this->getInput('themeTextLinkColor') ]]); $this->setData(['theme', 'site', [ 'backgroundColor' => $this->getInput('themeSiteBackgroundColor'), 'radius' => $this->getInput('themeSiteRadius'), 'shadow' => $this->getInput('themeSiteShadow'), 'width' => $this->getInput('themeSiteWidth'), 'margin' => $this->getInput('themeSiteMargin', helper::FILTER_BOOLEAN) ]]); $this->setData(['theme', 'button', [ 'backgroundColor' => $this->getInput('themeButtonBackgroundColor') ]]); $this->setData(['theme', 'block', [ 'backgroundColor' => $this->getInput('themeBlockBackgroundColor'), 'borderColor' => $this->getInput('themeBlockBorderColor') ]]); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), 'redirect' => helper::baseUrl() . 'theme', 'state' => true ]); } // Lire les fontes installées $this->enumFonts(); // Toutes les fontes installées sont chargées $this->setFonts('all'); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Site'), 'vendor' => [ 'tinycolorpicker', 'tinymce' ], 'view' => 'site' ]); } /** * Import du thème */ public function manage() { if ($this->isPost()) { $zipFilename = $this->getInput('themeManageImport', helper::FILTER_STRING_SHORT, true); $data = $this->import(self::FILE_DIR . 'source/' . $zipFilename); if ($data['success']) { header("Refresh:0"); } else { // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Gestion des thèmes'), 'notification' => $data['notification'], 'state' => $data['success'], 'view' => 'manage' ]);; } } // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Gestion des thèmes'), 'view' => 'manage' ]); } /** * Importe un thème * @param string Url du thème à télécharger * @param @return array contenant $success = true ou false ; $ notification string message à afficher */ public function import($zipName = '') { if ( $zipName !== '' && file_exists($zipName) ) { // Init variables de retour $success = false; $notification = ''; // Dossier temporaire $tempFolder = uniqid(); // Ouvrir le zip $zip = new ZipArchive(); if ($zip->open($zipName) === TRUE) { mkdir(self::TEMP_DIR . $tempFolder, 0755); $zip->extractTo(self::TEMP_DIR . $tempFolder); $modele = ''; // Archive de thème ? if ( file_exists(self::TEMP_DIR . $tempFolder . '/site/data/custom.css') and file_exists(self::TEMP_DIR . $tempFolder . '/site/data/theme.css') and file_exists(self::TEMP_DIR . $tempFolder . '/site/data/theme.json') ) { $modele = 'theme'; } if ( file_exists(self::TEMP_DIR . $tempFolder . '/site/data/admin.json') and file_exists(self::TEMP_DIR . $tempFolder . '/site/data/admin.css') ) { $modele = 'admin'; } if (!empty($modele)) { // traiter l'archive $success = $zip->extractTo('.'); // Substitution des fontes Google if ($modele = 'theme') { $c = $this->subFonts(self::DATA_DIR . 'theme.json'); // Un remplacement nécessite la régénération de la feuille de style if ( $c > 0 and file_exists(self::DATA_DIR . 'theme.css') ) { unlink(self::DATA_DIR . 'theme.css'); } } if ($modele = 'admin') { $c = $this->subFonts(self::DATA_DIR . 'admin.json'); // Un remplacement nécessite la régénération de la feuille de style if ( $c > 0 and file_exists(self::DATA_DIR . 'admin.css') ) { unlink(self::DATA_DIR . 'admin.css'); } } // traitement d'erreur $notification = $success ? helper::translate('Thème importé') : helper::translate('Erreur lors de l\'extraction, vérifiez les permissions'); } else { // pas une archive de thème $success = false; $notification = helper::translate('Archive de thème invalide'); } // Supprimer le dossier temporaire même si le thème est invalide $this->removeDir(self::TEMP_DIR . $tempFolder); $zip->close(); } else { // erreur à l'ouverture $success = false; $notification = helper::translate('Impossible d\'ouvrir l\'archive'); } return (['success' => $success, 'notification' => $notification]); } return (['success' => false, 'notification' => helper::translate('Archive non spécifiée ou introuvable')]); } /** * Export du thème */ public function export() { // Make zip $zipFilename = $this->zipTheme($this->getUrl(2)); // Téléchargement du ZIP header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); header('Content-Disposition: attachment; filename="' . $zipFilename . '"'); header('Content-Length: ' . filesize(self::TEMP_DIR . $zipFilename)); readfile(self::TEMP_DIR . $zipFilename); // Nettoyage du dossier unlink(self::TEMP_DIR . $zipFilename); exit(); } /** * Export du thème */ public function save() { // Make zip $zipFilename = $this->zipTheme($this->getUrl(2)); // Téléchargement du ZIP if (!is_dir(self::FILE_DIR . 'source/theme')) { mkdir(self::FILE_DIR . 'source/theme', 0755); } copy(self::TEMP_DIR . $zipFilename, self::FILE_DIR . 'source/theme/' . $zipFilename); // Nettoyage du dossier unlink(self::TEMP_DIR . $zipFilename); // Valeurs en sortie $this->addOutput([ 'notification' => '<b>' . $zipFilename . '</b>'. helper::translate('sauvegardé avec succès'), 'redirect' => helper::baseUrl() . 'theme/manage', 'state' => true ]); } /** * construction du zip Fonction appelée par export() et save() * @param string $modele theme ou admin */ private function zipTheme($modele) { // Creation du dossier $zipFilename = $modele . date('Y-m-d-H-i-s', time()) . '.zip'; $zip = new ZipArchive(); if ($zip->open(self::TEMP_DIR . $zipFilename, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) { switch ($modele) { case 'admin': $zip->addFile(self::DATA_DIR . 'admin.json', self::DATA_DIR . 'admin.json'); $zip->addFile(self::DATA_DIR . 'admin.css', self::DATA_DIR . 'admin.css'); // Ajoute les fontes $zip->addEmptyDir(self::DATA_DIR . 'fonts'); $fonts = $this->getData(['fonts', 'files']); foreach ($fonts as $fontId => $fontName) { $zip->addFile(self::DATA_DIR . 'fonts/' . $fontName, self::DATA_DIR . 'fonts/' . $fontName); } if (file_exists(self::DATA_DIR . 'fonts/fonts.html')) { $zip->addFile(self::DATA_DIR . 'fonts/fonts.html', self::DATA_DIR . 'fonts/fonts.html'); } break; case 'theme': $zip->addFile(self::DATA_DIR . 'theme.json', self::DATA_DIR . 'theme.json'); $zip->addFile(self::DATA_DIR . 'theme.css', self::DATA_DIR . 'theme.css'); $zip->addFile(self::DATA_DIR . 'custom.css', self::DATA_DIR . 'custom.css'); // Traite l'image dans le body if ($this->getData(['theme', 'body', 'image']) !== '') { $zip->addFile( self::FILE_DIR . 'source/' . $this->getData(['theme', 'body', 'image']), self::FILE_DIR . 'source/' . $this->getData(['theme', 'body', 'image']) ); } // Traite l'image dans le header if ($this->getData(['theme', 'header', 'image']) !== '') { $zip->addFile( self::FILE_DIR . 'source/' . $this->getData(['theme', 'header', 'image']), self::FILE_DIR . 'source/' . $this->getData(['theme', 'header', 'image']) ); } // Traite les images du header perso if (!empty($this->getData(['theme', 'header', 'featureFiles']))) { foreach ($this->getData(['theme', 'header', 'featureFiles']) as $value) { $zip->addFile( self::FILE_DIR . 'source/' . $value, self::FILE_DIR . 'source/' . $value ); } } // Ajoute les fontes $zip->addEmptyDir(self::DATA_DIR . 'fonts'); $fonts = $this->getData(['fonts', 'files']); foreach ($fonts as $fontId => $fontName) { $zip->addFile(self::DATA_DIR . 'fonts/' . $fontName, self::DATA_DIR . 'fonts/' . $fontName); } if (file_exists(self::DATA_DIR . 'fonts/fonts.html')) { $zip->addFile(self::DATA_DIR . 'fonts/fonts.html', self::DATA_DIR . 'fonts/fonts.html'); } break; } $ret = $zip->close(); } return ($zipFilename); } /** * Substitution des fontes de Google Fonts vers CdnFont grâce à un tableau de conversion * Cette fonction est utilisée par l'import. * @param string $file, nom du fichier json à convertir * @return int nombre de substitution effectuées */ private function subFonts($file) { // Tableau de substitution des fontes $fonts = [ 'Abril+Fatface' => 'abril-fatface', 'Arimo' => 'arimo', 'Arvo' => 'arvo', 'Berkshire+Swash' => 'berkshire-swash', 'Cabin' => 'genera', 'Dancing+Script' => 'dancing-script', 'Droid+Sans' => 'droid-sans-2', 'Droid+Serif' => 'droid-serif-2', 'Fira+Sans' => 'fira-sans', 'Inconsolata' => 'inconsolata-2', 'Indie+Flower' => 'indie-flower', 'Josefin+Slab' => 'josefin-sans-std', 'Lobster' => 'lobster-2', 'Lora' => 'lora', 'Lato' => 'lato', 'Marvel' => 'montserrat-ace', 'Old+Standard+TT' => 'old-standard-tt-3', 'Open+Sans' => 'open-sans', // Corriger l'erreur de nom de police installée par défaut, il manquait un O en majuscule 'open+Sans' => 'open-sans', 'Oswald' => 'oswald-4', 'PT+Mono' => 'pt-mono', 'PT+Serif' => 'pt-serif', 'Raleway' => 'raleway-5', 'Rancho' => 'rancho', 'Roboto' => 'Roboto', 'Signika' => 'signika', 'Ubuntu' => 'ubuntu', 'Vollkorn' => 'vollkorn' ]; $data = file_get_contents($file); $count = 0; foreach ($fonts as $oldId => $newId) { $data = str_replace($oldId, $newId, $data, $c); $count = $count + (int) $c; } // Sauvegarder la chaîne modifiée if ($count > 0) { file_put_contents($file, $data); } // Retourner le nombre d'occurrences return ($count); } // Retourne un tableau simple des fonts installées idfont avec le nom // Cette fonction est utile aux sélecteurs de fonts dans les formulaires. public function enumFonts() { /** * Récupère la liste des fontes installées et construit deux tableaux * id - nom * id - font-family - resource */ $f['files'] = $this->getData(['fonts', 'files']); $f['imported'] = $this->getData(['fonts', 'imported']); $f['websafe'] = self::$fontsWebSafe; // Construit un tableau avec leur ID et leur famille foreach (['websafe', 'imported', 'files'] as $type) { if (is_array($f[$type])) { foreach ($f[$type] as $fontId => $fontValue) { self::$fonts['name'][$fontId] = $fontValue['name']; self::$fonts['family'][$fontId] = $fontValue['font-family']; } } } // Liste des fontes pour les sélecteurs ksort(self::$fonts['name']); ksort(self::$fonts['family']); } /** * Création d'un fichier de liens d'appel des fontes * @param string $scope vaut all pour toutes les fontes ; 'user' pour les fontes utilisateurs */ private function setFonts($scope = 'all') { // Filtrage par fontes installées $fontsInstalled = [ $this->getData(['theme', 'text', 'font']), $this->getData(['theme', 'title', 'font']), $this->getData(['theme', 'header', 'font']), $this->getData(['theme', 'menu', 'font']), $this->getData(['theme', 'footer', 'font']), $this->getData(['admin', 'fontText']), $this->getData(['admin', 'fontTitle']), ]; // Compression $fontsInstalled = array_unique($fontsInstalled); /** * Chargement des polices en ligne dans un fichier fonts.html inclus dans main.php */ $gf = false; $fileContent = '<!-- Fontes personnalisées -->'; if (!empty($this->getData(['fonts', 'imported']))) { foreach ($this->getData(['fonts', 'imported']) as $fontId => $fontValue) { if ( ($scope === 'user' && in_array($fontId, $fontsInstalled)) || $scope === 'all' ) { //Pré chargement à revoir //$fileContent .= '<link rel="preload" href="' . $fontValue['resource'] . '" crossorigin="anonymous" as="style">'; $fileContent .= '<link href="' . $fontValue['resource'] . '" rel="stylesheet">'; // Pré connect pour api.google $gf = strpos($fontValue['resource'], 'fonts.googleapis.com') === false ? $gf || false : $gf || true; } } } // Ajoute le préconnect des fontes Googles. $fileContent = $gf ? '<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>' . $fileContent : $fileContent; /** * Fontes installées localement */ $fileContentCss = ''; if (!empty($this->getData(['fonts', 'files']))) { foreach ($this->getData(['fonts', 'files']) as $fontId => $fontValue) { if ( ($scope === 'user' && in_array($fontId, $fontsInstalled)) || $scope === 'all' ) { if (file_exists(self::DATA_DIR . 'fonts/' . $fontValue['resource'])) { // Extension $path_parts = pathinfo(helper::baseUrl(false) . self::DATA_DIR . 'fonts/' . $fontValue['resource']); // Chargement de la police $fileContentCss .= '@font-face {'; $fileContentCss .= 'font-family:"' . $fontValue['name'] . '";'; $fileContentCss .= 'src: url("' . $fontValue['resource'] . '") format("' . $path_parts['extension'] . '");'; $fileContentCss .= '}'; // Préchargement //$fileContent = '<link rel="preload" href="' . self::DATA_DIR . 'fonts/' . $fontValue['resource'] . '" type="font/woff" crossorigin="anonymous" as="font">' . $fileContent; } } } } // Enregistre la personnalisation file_put_contents(self::DATA_DIR . 'fonts/fonts.html', $fileContent); // Enregistre la personnalisation file_put_contents(self::DATA_DIR . 'fonts/fonts.css', $fileContentCss); } }